feat(migrations): support multi-column unique constraints (ZWEI-2850)#488
Merged
Merged
Conversation
Adds a new `unique` constraint kind to the entity model DSL for declaring
composite (multi-column) unique indices, which previously had to be written
as hand-rolled raw-SQL knex migrations.
It is emitted as a `CREATE UNIQUE INDEX` (not a `UNIQUE` constraint) so it can
be partial via an optional `where` predicate — essential for soft-deletable
entities, where `'"deleted" = false'` keeps a soft-deleted row from blocking
re-use of the natural key. For single columns the field-level `unique: true`
flag remains the right tool.
- model DSL: `{ kind: 'unique'; name; fields: string[]; where? }`
- migration generator reflects existing gm-managed unique *indexes* from
pg_index (excluding constraint-backed ones, so field-level `unique` and
primary keys are untouched) and add/drop/recreates on diff
- validation mirrors check/exclude (unknown column + empty fields rejected),
enforced both at Models construction and migration generation
- docs + 7 unit tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves conflicts with #487 (declarative regular trigger support): the new `trigger` constraint kind and this branch's `unique` kind now coexist in the ConstraintDefinition union, the migration generator's reflection queries + create/alter loops, and the test suite. Resolution tree verified: tsc + lint clean, 102/102 unit tests pass.
|
🎉 This PR is included in version 27.3.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new
uniqueconstraint kind to the entity model DSL for declaring composite (multi-column) unique indices, which previously had to be hand-written as raw-SQL knex migrations.Why
The model DSL already supports single-column uniqueness via the field-level
unique: trueflag, but composite natural keys (join-table FK pairs,(parentId, name)combos, …) had no declarative form — consumers wrote rawCREATE UNIQUE INDEXmigrations by hand. This adds first-class support.Driven by ZWEI-2850 (zwei-wealth-platform wants ~20 composite unique indices declared in-model).
What
New constraint kind:
CREATE UNIQUE INDEX(not aUNIQUEconstraint) so it can be partial viawhere— essential for soft-deletable entities, where a soft-deleted row should no longer block re-using the natural key. (Single-columnunique: trueis unchanged and remains the right tool for one column.)pg_index, deliberately excluding any index backing a constraint (primary keys and field-levelunique: true, which knex emits asADD CONSTRAINT … UNIQUE) — so those stay managed by their own code paths and are never touched here.check/exclude/constraint_triggerdiffing. Diff compares the ordered column list + normalizedwherepredicate againstpg_get_indexdefoutput.check/exclude: unknown column names and emptyfieldsare rejected, enforced both atModelsconstruction and at migration generation.Files
src/models/model-definitions.ts— newuniquekind onConstraintDefinition+UniqueConstraintDefinitionexportsrc/models/utils.ts—validateUniqueConstraintsrc/models/models.ts— validate at constructionsrc/migrations/generate.ts— reflect existing unique indexes, emit/diff on create + alter paths, helper methodsdocs/docs/2-models.md— documents the new kindtests/unit/migration-constraints.spec.ts— 7 new unit testsVerification
tsccleannpm run lintclean🤖 Generated with Claude Code