
Keep storage rules with the fields they protect
A customer number should identify one customer. A field used repeatedly to find records should have the storage support that makes those lookups practical.
Wildo lets you describe those requirements alongside the field. Database adapters translate the declaration into their own keys and indexes, keeping the reason for a storage rule close to the value it protects.
You decide what identifies a record and where uniqueness applies. A number can be unique across the application or only within one organization; those are different business rules.
Example — Import the same task without creating a duplicate
An external system gives each task a stable reference. Your application can use that reference to recognize an imported task again, while allowing another organization to use the same reference in its own workspace.
For engineers
Give each annotation a job
isPrimaryKey() identifies the resource’s primary key. isDBIndexed() requests a lookup index. isBusinessKey() identifies a value used by seed and import workflows to find an existing row; an ordinary business-key marker does not itself impose uniqueness. Use isUnique() when storage must reject duplicates.
Wonder Todos makes that distinction explicit in todos.schemas.ts. These selected field declarations omit intervening fields and comments; they show identity, lookup and lifecycle settings around that reference:
_id: z.string().min(1).isPrimaryKey().isSummaryField(),
organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),
createdByUserId: z.string().min(1).isDBIndexed().isForeignKey().excludeFromUpdate().optional(),
todoListId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),
assignedToUserId: z.string().isDBIndexed().isForeignKey().optional(),
title: z.string().min(1).max(200).isSummaryField(),
externalRef: z.string().min(1).max(100).regex(/^[A-Za-z0-9._:-]+$/).isBusinessKey().isUnique({ scope: 'organization', sparse: true }).optional(),
createdAt: z.date().default(() => new Date()).isDBIndexed().isSummaryField().excludeFromCreate().excludeFromUpdate(),
updatedAt: z.date().default(() => new Date()).isDBIndexed().excludeFromCreate().excludeFromUpdate(),
The value is optional for manually created tasks. When present, it accepts a bounded, URL-safe reference. scope: 'organization' makes uniqueness local to the organization, and sparse: true lets tasks without an external reference coexist.
Let the adapter translate the declaration
The MongoDB converter reads index and uniqueness metadata to create the corresponding indexes. The PostgreSQL planner reads the same intent into its schema plan, from which migrations are produced. The shared declaration keeps the rule identifiable; each database still has its own schema-publication lifecycle.
For a key made from several values, use the object-level businessKeySet contract and review the compound uniqueness it describes. Treat the named business identity and the physical primary key as separate decisions when the domain needs both.
Decide what a missing key member means
Consider an organization chart: two sibling units must not share a code, but units without a code are allowed. A root unit has no parent; that absence still describes a real position in the chart.
The engine’s OrganizationUnitSchema declares that distinction at the end of its object schema. This excerpt keeps the actual key members and constraint options; the other fields are omitted:
z.object({
// Other organization-unit fields are omitted.
organizationId: z.string().min(1).isDBIndexed().isForeignKey().isSummaryField().excludeFromUpdate(),
parentUnitId: z.string().optional().isDBIndexed().isForeignKey().excludeFromUpdate(),
code: z.string().min(1).max(20).optional(),
}).businessKeySet(
['organizationId', 'parentUnitId', 'code'],
{
skipRowsMissing: ['code'],
conflictMessageReference: ErrorCustomMessageReference.ORGANIZATION_UNIT_CODE_EXISTS,
},
);
skipRowsMissing excludes a row from this uniqueness constraint when a listed member is absent. It does not mean “list every optional field.” Here, omitting code means there is no business key to enforce; omitting parentUnitId means the unit is a root, whose code must still be unique within its organization.
| New record compared with an existing unit | Result of this constraint | Why |
|---|---|---|
| Same organization, parent and code | Conflict | The complete business identity is already occupied. |
| Same organization and code, both roots | Conflict | An absent parent is the same root position for both records. |
| Same code under a different parent | Allowed | The parent is part of the identity. |
| Same parent and code in another organization | Allowed | The organization is part of the identity. |
| Another unit with no code | Allowed | Missing code excludes that row from this constraint. |
These are uniqueness decisions, not permission grants: the operation’s access rules still apply. conflictMessageReference names the domain-specific conflict instead of making callers interpret a generic duplicate-key message.
The MongoDB adapter emits a partial compound unique index for records with a code. PostgreSQL expresses the same intent with a presence predicate and NULLS NOT DISTINCT, so two roots cannot evade uniqueness through their missing parent. Publishing the declaration through the adapter’s normal schema lifecycle is what installs the constraint.
The compound identity also supports seed/import matching independently of the physical primary key. It does not turn an ordinary CREATE request into an upsert. Choose the appropriate seed/import workflow when repeated input should update an existing record.
Introduce the constraint with existing data in mind
Before adding uniqueness to an established field, examine whether existing rows already satisfy the intended rule. For PostgreSQL, generate and publish the migration with its schema plan. For MongoDB, the owning runtime reconciles and verifies declared unique indexes at startup.
An index helps particular query shapes; it is not a substitute for choosing the filters and sort fields an operation offers. Keep the field’s storage rule and the operation’s public query choices aligned.