cindel_generator 1.0.0 copy "cindel_generator: ^1.0.0" to clipboard
cindel_generator: ^1.0.0 copied to clipboard

Source generator for Cindel schemas, serializers, typed collections, and query builders.

Cindel Generator #

Source generator for Cindel schemas, serializers, typed collections, query builders, filters, projections, and native typed document hooks.

Overview | Setup | Model Shape | Freezed Models | Generated API | Indexes | Embedded Objects | Enums

Most applications use cindel_generator as a dev_dependency together with build_runner. It reads annotations from your model classes and emits the *.g.dart files consumed by the cindel runtime.

Overview #

cindel_generator turns annotated Dart classes into the code Cindel needs for typed database access:

  • Collection schema metadata.
  • Generated typed document serializers and deserializers.
  • Compact binary serializers and deserializers.
  • Native typed document readers and writers when the field layout supports it.
  • Typed collection accessors on CindelDatabase.
  • Indexed where() query helpers.
  • filter() query helpers for persisted fields.
  • Sorting, distinct, property projection, and aggregate helpers.
  • Composite index equality helpers.
  • Embedded object conversion helpers.
  • Embedded object and embedded object list native reader/writer hooks when the model layout supports native typed documents.
  • Nested filter helpers for single embedded objects and embedded object list elements.

The package is a build-time tool. It does not open databases and it does not ship native binaries.

Setup #

For Flutter apps, depend on Cindel and the native library package at runtime, then add the generator as a dev dependency:

dependencies:
  cindel: ^x.y.z
  cindel_flutter_libs: ^x.y.z

dev_dependencies:
  build_runner: ^2.15.0
  cindel_generator: ^x.y.z

Pure Dart packages can depend on cindel directly and provide a native library path with CINDEL_NATIVE_LIBRARY when needed.

Basic Usage #

Create a model file with a part directive and Cindel annotations:

import 'package:cindel/cindel.dart';

part 'user.g.dart';

@Collection(name: 'users')
class User {
  Id dbId = autoIncrement;

  @Index(unique: true)
  late String email;

  @index
  late String name;

  bool active = true;
}

Run the generator:

dart run build_runner build --delete-conflicting-outputs

Then use the generated schema and typed collection API:

final db = await Cindel.open(
  directory: directory.path,
  schemas: [UserSchema],
);

final user = User()
  ..name = 'Jhon Doe'
  ..email = 'jhon@example.com';

await db.users.put(user);

final saved = await db.users.where().emailEqualTo('jhon@example.com').findFirst();

Model Shape #

Generated collections must follow the rules enforced by the generator:

  • @Collection can only be used on concrete classes, except supported Freezed primary-factory models.
  • A collection must declare at least one persisted field.
  • A collection must declare exactly one persisted field named dbId.
  • A collection needs either an unnamed constructor with no parameters or an unnamed constructor with parameters for every persisted field.
  • Collections with final persisted fields need constructor parameters for every persisted field.
  • Fields annotated with @ignore are excluded from persistence.
  • @Name can override the persisted collection or field name while generated Dart APIs continue to use the Dart identifier.

Supported persisted field shapes are:

  • bool, int, double, and String.
  • DateTime and Duration.
  • Enums.
  • Embedded objects annotated with @Embedded.
  • Nullable variants of supported shapes.
  • Lists of supported non-list shapes, including embedded objects.

Nested lists are not supported.

Example persisted-name override:

@Name('accounts')
@collection
class Account {
  Id dbId = autoIncrement;

  @Name('user_name')
  @Index(unique: true)
  late String username;
}

Freezed Models #

The generator supports Freezed classic classes when they expose concrete final fields:

import 'package:cindel/cindel.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
@Collection(name: 'users')
class User with _$User {
  const User({
    required this.dbId,
    required this.email,
    required this.name,
  });

  @override
  final Id dbId;

  @override
  @Index(unique: true)
  final String email;

  @override
  final String name;
}

It also supports the common Freezed primary factory style by reading persisted properties from the unnamed factory constructor:

import 'package:cindel/cindel.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
@Collection(name: 'users')
abstract class User with _$User {
  const factory User({
    required Id dbId,
    required String email,
    @Index(unique: true) required String username,
    @Enumerated(CindelEnumType.ordinal) required UserStatus status,
    @Default(true) bool active,
    @ignore String? transientNote,
  }) = _User;
}

For primary factory models, Cindel annotations such as @Index, @Enumerated, and @ignore can be placed on factory parameters. Ignored parameters must be optional so generated hydration can rebuild the object.

IMPORTANT: Freezed union/sealed multi-constructor models are not supported.

Generated API #

For a User collection, the generator emits a schema named UserSchema and a typed database accessor:

final users = db.users;

It also emits conversion functions used by the runtime:

  • Dart object to generated Cindel document.
  • Generated Cindel document to Dart object.
  • Dart object to compact binary document.
  • Compact binary document to Dart object.
  • Native typed writer and reader hooks when supported by the field layout.
  • Id getter, and an id setter when the model can assign generated ids.
  • putBy... and putAllBy... helpers for unique replace indexes.

Generated query access starts from where() for indexed fields and collection-level composite indexes:

final user = await db.users.where().emailEqualTo('jhon@example.com').findFirst();

Generated filter() helpers are available for persisted fields:

final activeUsers = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .findAll();

Generated query modifiers include dynamic optional / anyOf / allOf filter composition, field sorting, descending sorting, distinct helpers, and property query accessors:

final names = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .nameProperty()
    .findAll();

List fields generate element and length helpers:

final tagged = await db.users
    .filter()
    .tagsElementEqualTo('flutter')
    .findAll();

final emptyTags = await db.users.filter().tagsIsEmpty().findAll();

final shortTagLists = await db.users
    .filter()
    .tagsLengthLessThan(3, include: true)
    .findAll();

Indexes #

The generator reads @index, @Index(...), and collection-level CompositeIndex(...) annotations.

Value Indexes #

@index
late String name;

Value indexes generate equality helpers and range-style helpers when the field type supports range queries.

Unique Indexes #

@Index(unique: true)
late String email;

Unique indexes generate the same lookup helpers and tell the runtime to enforce unique values.

replace defaults to false. Use @Index(unique: true) for a normal unique index. Add replace: true only when the unique index should generate natural-key upsert helpers and replace conflicting documents during writes:

@Index(unique: true, replace: true)
late String email;

The generated typed collection exposes helpers such as:

await db.users.putByEmail(user);
await db.users.putAllByEmail(users);

Hash Indexes #

@Index(type: CindelIndexType.hash)
late String externalId;

Hash indexes generate equality helpers only.

Word Indexes #

@Index(type: CindelIndexType.words)
late String bio;

Word indexes are supported for string fields.

Multi-Entry Indexes #

@Index(type: CindelIndexType.multiEntry)
late List<String> tags;

Multi-entry indexes are supported for lists of primitive values, DateTime, Duration, or enums.

Composite Indexes #

@Collection(
  indexes: [
    CompositeIndex(['teamId', 'email'], unique: true),
  ],
)
class TeamMember {
  Id dbId = autoIncrement;

  late int teamId;
  late String email;
  late String name;
}

Composite indexes generate equality helpers for the configured field set. When a composite index is both unique and replace: true, the generator also emits putBy... and putAllBy... helpers for the composite key.

Embedded Objects #

Embedded classes are converted as part of their parent document. They are value objects, not root collections, and can be declared with @Embedded() or the lowercase @embedded constant.

@embedded
class Address {
  late String city;
  late String country;
}

@embedded
class Contact {
  String? name;
  String? email;
  Address? address;
}

@collection
class User {
  Id dbId = autoIncrement;
  late String name;
  Contact? primaryContact;
  List<Contact>? contacts;
}

For single embedded object fields, generated filters include nested object helpers. Helpers can continue into nested embedded objects:

final users = await db.users
    .filter()
    .primaryContact((contact) {
      return contact.address((address) {
        return address.cityEqualTo('Santo Domingo');
      });
    })
    .findAll();

final team = await db.users
    .filter()
    .contactsElement((contact) {
      return contact.address((address) {
        return address.countryEqualTo('DO');
      });
    })
    .findAll();

The generator also emits:

  • embedded conversion helpers used by generated document and binary serializers,
  • whole-object equality filters such as primaryContactEqualTo(value),
  • embedded-list equality filters such as contactsEqualTo(values),
  • embedded-list element equality filters such as contactsElementEqualTo(value),
  • embedded-list nested element filters such as contactsElement((contact) => ...),
  • native writer calls for embedded objects and embedded object lists,
  • native reader calls for embedded objects and embedded object lists.

Embedded indexes are not supported by the generator. @Index inside an embedded class is rejected. Put indexes on root collection fields instead.

Enums #

The generator supports enum fields and @Enumerated(...) strategies.

enum UserRole { admin, editor, viewer }

@collection
class User {
  Id dbId = autoIncrement;

  @Enumerated(CindelEnumType.name)
  late UserRole role;
}

For value-based enum persistence:

enum AccountStatus {
  active('A'),
  suspended('S');

  const AccountStatus(this.code);
  final String code;
}

@collection
class Account {
  Id dbId = autoIncrement;

  @Enumerated(CindelEnumType.value, valueField: 'code')
  late AccountStatus status;
}

Builder Details #

The package registers a build_runner builder named cindel_generator.

It uses source_gen as a shared part builder:

  • Input: .dart files.
  • Intermediate output: .cindel.g.part.
  • Final user-facing output: the combined *.g.dart part file.

In normal projects, adding the dependency and running build_runner is enough.

Status #

This generator follows the same release line as the runtime package and emits the native typed readers, writers, query helpers, and hydration hooks used by the optimized Cindel runtime.

1
likes
150
points
245
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Source generator for Cindel schemas, serializers, typed collections, and query builders.

Repository (GitHub)
View/report issues
Contributing

Topics

#database #nosql #codegen #build-runner #source-gen

License

Apache-2.0 (license)

Dependencies

analyzer, build, cindel_annotations, source_gen

More

Packages that depend on cindel_generator