zmodel_to_dart_builder 1.1.1
zmodel_to_dart_builder: ^1.1.1 copied to clipboard
A Dart builder to generate DTO classes from ZModel schema files
zmodel_to_dart_builder #
A Dart builder that generates DTO classes from .zmodel files.
For an overview of the parser, renderer, CLI flow, and RPC generation internals, see the repository architecture guide.
This package is the generator side of the split. Generated code depends on zmodel_to_dart_runtime; the original zmodel_to_dart pub package name is left for the legacy 0.x package.
Features #
- Parses
model,abstract model, andenumdeclarations - Generates DTO classes with
fromJson - Generates static model metadata for dynamic selects, filters, includes, omits, and ordering
- Generates local validators from schema metadata
- Generates sparse mutation payload classes with
toJsonand serialization metadata - Supports inherited fields from
extends - Handles scalar values, enums, bytes, arrays, and nested model references
- Optionally generates schema-scoped RPC clients backed by runtime payload contracts
- Automatically unwraps
superjsonRPC responses before model parsing
Usage #
- Add to your
pubspec.yaml:
dependencies:
zmodel_to_dart_runtime: ^1.1.0
dev_dependencies:
zmodel_to_dart_builder: ^1.1.0
build_runner: ^2.7.0
- Create a
.zmodelfile:
enum role {
admin
reader
}
model user {
id String @id
email String @email
name String @db.VarChar(80) @length(2, 80)
age Int @gte(18)
role role
}
- Optionally create
zmodel_to_dart.yamlin the package root:
input_globs:
- schema/*.zmodel
output_suffix: .zmodel.dart
output_dir: lib/data/dtos
banner: |
// AUTO GENERATED FILE, DO NOT EDIT
// ignore_for_file: type=lint
// cspell: disable
generate_rpc_clients: true
rpc_base_path: /api/model
rpc_client_class_name: AppRpcClient
- Run:
dart run build_runner build
This generates a sibling file like user.zmodel.dart.
The generated library imports the runtime package. DTOs extend ZModel, and payload builders extend runtime contracts such as ZModelCreate, ZModelWhere, ZModelSelect, ZModelInclude, ZModelOrderBy, and ZModelDistinct.
Generated models expose static metadata for dynamic query construction:
final select = UserSelect().setEntries([
User.metadata.id.select(),
User.metadata.email.select(),
]);
final where = UserWhere().setEntries([
User.metadata.email.contains('example.com'),
]);
final orderBy = UserOrderBy([
User.metadata.createdAt.desc(),
User.metadata.id.asc(),
]);
Generated models also expose local validators:
final result = User.validators.validateMap({
'id': 'user_1',
'name': null,
'role': 'admin',
});
TextFormField(
validator: User.validators.name.required(),
);
Validation errors are structured and can be translated globally by the app:
ZValidationMessages.resolver = (error) {
return switch (error.code) {
ZValidationErrorCode.required => 'Campo obrigatório',
ZValidationErrorCode.invalidType => 'Valor inválido',
ZValidationErrorCode.minLength => 'Valor muito curto',
ZValidationErrorCode.maxLength =>
"Use no máximo ${error.metadata['maxLength']} caracteres",
ZValidationErrorCode.email => 'E-mail inválido',
_ => 'Valor inválido',
};
};
Generated local validators support ZenStack input validation attributes such as
@length, @startsWith, @endsWith, @contains, @email, @url,
@datetime, @regex, @lt, @lte, @gt, and @gte. Constraints that
require database state, like @unique, remain metadata for app-managed checks.
Use validator composition for form-specific rules without changing the .zmodel schema:
final formValidator = User.validators.model.withFields({
'name': User.validators.name.required(),
});
When generate_rpc_clients is enabled, the generated library also contains a schema-scoped RPC client extending ZRpcClient. Each concrete model is exposed as a ZModelRpcApi<[Model]> getter, and operation data is passed through named parameters with generated payload builders:
final client = AppRpcClient(transport);
final users = await client.user.findMany(
where: UserWhere().id('user_1'),
orderBy: UserOrderBy().id.order(false),
take: 20,
);
final created = await client.user.create(
data: UserCreate().name('New user'),
);
final updated = await client.user.update(
data: UserUpdate().name('Updated name').role(null),
where: UserWhereUnique().id('user_1'),
);
Each concrete model gets generated create and update payload classes. A field method that is not called is omitted from the request body, a field method called with null sends JSON null, and a field method called with a value sends that value. For dynamic field names, use set(name, value). Payloads keep raw Dart values until RPC serialization, so DateTime, BigInt, Uint8List, enums, nested payloads, lists, and maps are encoded with ZenStack-compatible meta.serialization.
Relation fields expose nested payload helpers:
final usersWithPosts = await client.user.findMany(
include: UserInclude().postsWith(
select: PostSelect().id().title(),
where: PostWhere().published(true),
),
);
final where = UserWhere().posts.some(
PostWhere().published(true),
);
Example using Dio:
import 'package:dio/dio.dart';
import 'package:zmodel_to_dart_runtime/zmodel_to_dart_runtime.dart';
class DioZRpcTransport implements ZRpcTransport {
DioZRpcTransport(this._dio);
final Dio _dio;
@override
Future<ZHttpResponse> send(
ZHttpMethod method,
String path, {
Map<String, String>? queryParameters,
Object? body,
}) async {
late final Response<dynamic> response;
switch (method) {
case ZHttpMethod.get:
response = await _dio.get<dynamic>(
path,
queryParameters: queryParameters,
);
break;
case ZHttpMethod.patch:
response = await _dio.patch<dynamic>(
path,
queryParameters: queryParameters,
data: body,
);
break;
case ZHttpMethod.delete:
response = await _dio.delete<dynamic>(
path,
queryParameters: queryParameters,
data: body,
);
break;
case ZHttpMethod.post:
response = await _dio.post<dynamic>(
path,
queryParameters: queryParameters,
data: body,
);
break;
case ZHttpMethod.put:
response = await _dio.put<dynamic>(
path,
queryParameters: queryParameters,
data: body,
);
break;
}
return ZHttpResponse(
statusCode: response.statusCode ?? 0,
statusMessage: response.statusMessage,
headers: response.headers.map,
data: response.data,
);
}
}
final dio = Dio(BaseOptions(baseUrl: 'http://localhost:3000'));
final transport = DioZRpcTransport(dio);
final client = AppRpcClient(transport);
final users = await client.user.findMany(
where: UserWhere().id('user_1'),
take: 20,
);
The builder registration is shipped by this package. Consumers usually only need zmodel_to_dart.yaml for input filtering, output naming, banners, and RPC options.
Suggestions #
- Keep your
.zmodelfiles close to the modules that consume the generated DTOs. - Use the builder when you want generated files to stay synchronized automatically.
To run standalone #
dart run zmodel_to_dart_builder:zmodel_to_dart <path_to_zmodel_file> [output_dir]
If you omit the CLI arguments, the command will try to resolve a single source file from input_globs in zmodel_to_dart.yaml and will use output_dir from that file.
License #
MIT