Framework contracts¶
This page is the normative reference for the engine-neutral contracts in
dorm_framework. It describes the behavior an engine must preserve and the
capabilities it may add. Use Implement a custom engine
for the implementation sequence.
Entity and schema¶
An Entity connects generated values to storage metadata and identity conversion.
It provides:
- an
EntitySchema; - a
PrimaryKeyCodec; - conversion between serialized data and models;
- construction from a resolved creation;
- extraction of an identity from a model.
EntitySchema contains the stored fields, ordered primary keys, foreign-key
metadata, and derived-field metadata. FieldSchema is the metadata value passed
to filters and ordering. The framework, not an engine-specific query class,
owns the logical field name and stored name relationship.
A simple identity has one primary-key field and uses a single-key codec. A
composite identity has multiple ordered fields and uses CompositeKey with a
composite codec. The order and shape must agree between the entity, engine,
serialized data, and any synchronization target.
Repository operations¶
A generated Repository combines read, write, and removal contracts for one
entity. The repository keeps the entity's data, model, identity, query, and
page types visible to the analyzer.
Reads¶
| Operation | Contract |
|---|---|
peek |
Returns one model or null when the identity is absent. |
peekAll |
Returns every matching model, or an empty list when no model matches. |
peekPage |
Returns one Page using the entity's accepted PageRequest type. |
peekAllKeys |
Returns the stored identities. |
pull |
Streams one model or null. |
pullAll |
Streams matching model lists. |
Absence is a normal result. An engine must not convert an absent record or an empty collection into a notFound error.
A stream's later events are determined by the engine. The common contract defines the result shape, not a universal guarantee that every engine observes external database changes.
Writes and removal¶
| Operation | Contract |
|---|---|
put |
Resolves a creation request, persists the result, and returns the final model. |
putAll |
Resolves and persists each creation request and returns the final models. |
push |
Persists an already identified model. |
pushAll |
Persists identified models. |
patch |
Supplies the current model to a callback; a null callback result removes it. |
pop |
Removes one identity. |
popKeys |
Removes the supplied identities. |
popAll |
Removes the identities selected by the primary operation's filter. |
purge |
Removes all records for the entity. |
Creation.auto follows the identity strategy declared by the entity. An engine
may generate the identity, ask the database to generate it, or require an
explicit identity according to that strategy. Creation.explicit always
supplies the final identity.
The framework defines operation results and callback behavior. Atomicity, isolation, and the number of backend statements remain engine capabilities unless an operation or transaction contract states otherwise.
Filters, queries, and pages¶
BaseFilter is a structured value. It resolves a FieldSchema and applies a
FilterExpression to a concrete BaseQuery. The framework does not parse SQL,
Firebase paths, MongoDB selectors, or arbitrary URL strings.
BaseQuery provides the portable operations needed by all engines:
- value equality;
- text-prefix matching;
- date comparison;
- range comparison;
- sorting;
- limit;
- offset.
Optional query interfaces advertise extra filter families:
| Capability | Adds |
|---|---|
ComparisonQuery |
scalar comparisons, set membership, and null checks |
LogicalQuery |
allOf and anyOf composition |
NegationQuery |
not |
CollectionQuery |
contains and containsAny |
A concrete query must implement a capability before its corresponding structured filter factory is available. An engine must reject an unsupported capability rather than silently broadening or moving the filter to an undocumented client-side path.
QueryOptions carries ordering and read-window options. PageRequest and Page
carry pagination input and continuation metadata. Current official engines use
OffsetPageRequest as their typed page request; cursor pagination is not a
portable promise.
Relationships¶
BaseRelationship creates one-to-one, one-to-many, many-to-one, and many-to-many
associations from RelationSource values.
RelationSource provides readable operations and may provide a RelationPlan.
A direct table plan can let an engine optimize a relationship, but readable
operations remain the portable semantic baseline.
Relationship result types distinguish:
- a missing source;
- a missing required related value;
- a nullable related value;
- an empty related collection;
- a populated related collection.
RelationPath evaluates lazily. Declaring a path does not execute a query; reads
occur when a peek or pull operation is called. The framework does not promise a
fixed query count or a native join for every backend.
Transactions¶
TransactionalEngine is an optional capability:
TransactionalDorm is generated for an engine type that satisfies this
capability. The callback uses a temporary Dorm bound to the transaction
context, so repository operations can share the same local transaction.
Streams are unavailable in the transaction context and nested transactions are
not supported by the current generated facade. Engines that do not implement
TransactionalEngine remain valid BaseEngine implementations.
A backend may use an internal transaction for one operation without exposing the public transaction capability. Those are separate guarantees.
Migrations¶
Migrations are an optional capability. MigrationCapableEngine exposes a
MigrationAdapter; BaseEngine does not require it because in-memory and
server-facing engines may not own a persistent schema.
A MigrationAdapter applies the sealed MigrationOperation hierarchy, stores
completed versions, and protects the complete run with a lock. The runner must
record a Migration only after all of its operations finish. Failed operations
may be retried, so adapters and migrations must be idempotent.
A migration adapter may additionally implement TransactionalMigrationAdapter.
Its MigrationTransactionMode declares whether the runner groups a complete
migration, each operation, or no migration work in a transaction. The adapter
must not claim a stronger boundary than its provider guarantees.
Each MigrationOperation exposes a MigrationOperationSafety classification.
The dorm_migrations runner uses this classification to reject destructive
pending operations unless the caller explicitly opts in.
The portable contract separates structural operations such as
AddFieldOperation and RenameFieldOperation from data operations such as
BackfillFieldOperation and CopyFieldOperation. A document backend may treat
a structural field operation as a no-op, but it must execute the corresponding
data operation when existing records need to change.
TransformFieldOperation is the portable contract for typed changes to existing values. Its filter is a FilterExpression, its transformation is declarative, and its batch size is a paging hint for adapters that support pages. Arbitrary Dart callbacks are outside this contract. Before a field is tightened, projects may use MigrationRunner.operationValidator to perform data-specific validation immediately before AlterFieldOperation.
The framework does not infer historical renames from the current
EntitySchema, does not promise distributed atomicity, and does not require a
backend to support every operation. Unsupported operations must fail
explicitly. Foreign keys are explicit operations with named local and referenced
fields; their enforcement and referential actions remain backend capabilities. Check constraints are also explicit; their expressions use provider syntax and are not
translated by the framework.
See Migration protocol for the execution rules and backend semantics.
Synchronization¶
ChangeTrackedEngine is an optional capability used by dorm_sync. Its mutation
result contains the normal operation result and an exact MutationChangeSet.
The change set records the affected identities and final serialized data so a
replica can apply the primary result without re-running a callback or
recalculating a filter. SynchronizedEngine is not a TransactionalEngine and
does not provide distributed atomicity.
See Synchronization protocol for the outbox and delivery rules.
Errors¶
Official external engines may implement ErrorAwareEngine and map provider
failures to DormDatabaseException. The mapper preserves the native cause,
stack trace, operation, engine, and provider code.
Validation errors raised before a provider call remain ordinary Dart errors.
An engine without ErrorAwareEngine may continue to propagate provider-native
errors.
See Portable errors and Handling errors.
Engine boundary¶
A custom engine supplies:
abstract class BaseEngine<Q extends BaseQuery<Q>, P extends PageRequest> {
BaseReference<Q, P> createReference();
BaseRelationship<Q> createRelationship();
}
The reference implements the repository operation surface. The query implements the engine's query representation. The relationship implementation must preserve the framework result shape even when it uses a backend-native plan.
The custom engine guide and the shared conformance tests are the practical extension boundary. Internal concrete classes from official engines are not required application APIs.