@metaobjectsdev/codegen-ts 0.24.5 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -12
- package/dist/api-surface.d.ts +29 -0
- package/dist/api-surface.d.ts.map +1 -1
- package/dist/api-surface.js +47 -0
- package/dist/api-surface.js.map +1 -1
- package/dist/column-mapper.d.ts +12 -0
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +1 -0
- package/dist/column-mapper.js.map +1 -1
- package/dist/constants.d.ts +24 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +54 -19
- package/dist/constants.js.map +1 -1
- package/dist/db-emitting.d.ts +6 -0
- package/dist/db-emitting.d.ts.map +1 -0
- package/dist/db-emitting.js +26 -0
- package/dist/db-emitting.js.map +1 -0
- package/dist/generator-registry.d.ts.map +1 -1
- package/dist/generator-registry.js +14 -1
- package/dist/generator-registry.js.map +1 -1
- package/dist/generator.d.ts +5 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/agent-docs-file.d.ts +19 -0
- package/dist/generators/agent-docs-file.d.ts.map +1 -0
- package/dist/generators/agent-docs-file.js +257 -0
- package/dist/generators/agent-docs-file.js.map +1 -0
- package/dist/generators/agent-requirements-page.d.ts +22 -0
- package/dist/generators/agent-requirements-page.d.ts.map +1 -0
- package/dist/generators/agent-requirements-page.js +175 -0
- package/dist/generators/agent-requirements-page.js.map +1 -0
- package/dist/generators/agent-schema-input.d.ts +69 -0
- package/dist/generators/agent-schema-input.d.ts.map +1 -0
- package/dist/generators/agent-schema-input.js +23 -0
- package/dist/generators/agent-schema-input.js.map +1 -0
- package/dist/generators/agent-schema-page.d.ts +21 -0
- package/dist/generators/agent-schema-page.d.ts.map +1 -0
- package/dist/generators/agent-schema-page.js +241 -0
- package/dist/generators/agent-schema-page.js.map +1 -0
- package/dist/generators/agent-ui-page.d.ts +22 -0
- package/dist/generators/agent-ui-page.d.ts.map +1 -0
- package/dist/generators/agent-ui-page.js +253 -0
- package/dist/generators/agent-ui-page.js.map +1 -0
- package/dist/generators/api-docs-file.d.ts.map +1 -1
- package/dist/generators/api-docs-file.js +13 -6
- package/dist/generators/api-docs-file.js.map +1 -1
- package/dist/generators/api-model.d.ts +6 -0
- package/dist/generators/api-model.d.ts.map +1 -1
- package/dist/generators/api-model.js +54 -35
- package/dist/generators/api-model.js.map +1 -1
- package/dist/generators/callable-file.js +1 -1
- package/dist/generators/callable-file.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +47 -2
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-data.d.ts +6 -2
- package/dist/generators/docs-data.d.ts.map +1 -1
- package/dist/generators/index.d.ts +3 -8
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +19 -14
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/names-file.d.ts +17 -0
- package/dist/generators/names-file.d.ts.map +1 -0
- package/dist/generators/names-file.js +95 -0
- package/dist/generators/names-file.js.map +1 -0
- package/dist/generators/requirements-markdown.d.ts +18 -1
- package/dist/generators/requirements-markdown.d.ts.map +1 -1
- package/dist/generators/requirements-markdown.js +11 -9
- package/dist/generators/requirements-markdown.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts +17 -2
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +9 -11
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts +17 -2
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +10 -8
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-generator.d.ts.map +1 -1
- package/dist/generators/template-generator.js +20 -6
- package/dist/generators/template-generator.js.map +1 -1
- package/dist/index.d.ts +20 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -4
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +38 -1
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +9 -1
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/names.d.ts +275 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +433 -0
- package/dist/names.js.map +1 -0
- package/dist/overwrite-policy.d.ts.map +1 -1
- package/dist/overwrite-policy.js +15 -3
- package/dist/overwrite-policy.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts +25 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +79 -6
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/reference-templates.d.ts +1 -1
- package/dist/reference-templates.d.ts.map +1 -1
- package/dist/reference-templates.js +1 -1
- package/dist/reference-templates.js.map +1 -1
- package/dist/render-context.d.ts +12 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +1 -0
- package/dist/render-context.js.map +1 -1
- package/dist/retired-codegen-attrs.d.ts +32 -0
- package/dist/retired-codegen-attrs.d.ts.map +1 -0
- package/dist/retired-codegen-attrs.js +47 -0
- package/dist/retired-codegen-attrs.js.map +1 -0
- package/dist/routes-expose.d.ts +63 -0
- package/dist/routes-expose.d.ts.map +1 -0
- package/dist/routes-expose.js +57 -0
- package/dist/routes-expose.js.map +1 -0
- package/dist/runner.d.ts +18 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +109 -7
- package/dist/runner.js.map +1 -1
- package/dist/templates/callable-file.d.ts +7 -2
- package/dist/templates/callable-file.d.ts.map +1 -1
- package/dist/templates/callable-file.js +54 -9
- package/dist/templates/callable-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +236 -65
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-constants.d.ts +15 -6
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +85 -161
- package/dist/templates/entity-constants.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +38 -4
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/entity-meta-file.d.ts +15 -1
- package/dist/templates/entity-meta-file.d.ts.map +1 -1
- package/dist/templates/entity-meta-file.js +12 -2
- package/dist/templates/entity-meta-file.js.map +1 -1
- package/dist/templates/entity-ui-descriptor.d.ts +112 -0
- package/dist/templates/entity-ui-descriptor.d.ts.map +1 -0
- package/dist/templates/entity-ui-descriptor.js +238 -0
- package/dist/templates/entity-ui-descriptor.js.map +1 -0
- package/dist/templates/field-meta.d.ts +46 -9
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +95 -21
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/names-decl.d.ts +38 -0
- package/dist/templates/names-decl.d.ts.map +1 -0
- package/dist/templates/names-decl.js +104 -0
- package/dist/templates/names-decl.js.map +1 -0
- package/dist/templates/projection-decl.d.ts +21 -0
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +53 -29
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +17 -2
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/requirement-test.d.ts.map +1 -1
- package/dist/templates/requirement-test.js +13 -2
- package/dist/templates/requirement-test.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts +2 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +11 -2
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/routes-file.d.ts +2 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +54 -21
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/value-object-file.d.ts.map +1 -1
- package/dist/templates/value-object-file.js +15 -1
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/view-decl.d.ts +37 -1
- package/dist/templates/view-decl.d.ts.map +1 -1
- package/dist/templates/view-decl.js +27 -3
- package/dist/templates/view-decl.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +21 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +40 -5
- package/dist/templates/zod-validators.js.map +1 -1
- package/dist/view-context.d.ts +24 -0
- package/dist/view-context.d.ts.map +1 -0
- package/dist/view-context.js +81 -0
- package/dist/view-context.js.map +1 -0
- package/package.json +6 -6
- package/src/api-surface.ts +53 -0
- package/src/column-mapper.ts +13 -0
- package/src/constants.ts +91 -16
- package/src/db-emitting.ts +31 -0
- package/src/generator-registry.ts +14 -4
- package/src/generator.ts +5 -0
- package/src/generators/agent-docs-file.ts +297 -0
- package/src/generators/agent-requirements-page.ts +199 -0
- package/src/generators/agent-schema-input.ts +88 -0
- package/src/generators/agent-schema-page.ts +297 -0
- package/src/generators/agent-ui-page.ts +290 -0
- package/src/generators/api-docs-file.ts +13 -6
- package/src/generators/api-model.ts +63 -34
- package/src/generators/callable-file.ts +1 -1
- package/src/generators/docs-data-builder.ts +46 -2
- package/src/generators/docs-data.ts +6 -2
- package/src/generators/index.ts +25 -14
- package/src/generators/names-file.ts +98 -0
- package/src/generators/requirements-markdown.ts +36 -2
- package/src/generators/routes-file-hono.ts +19 -10
- package/src/generators/routes-file.ts +20 -7
- package/src/generators/template-generator.ts +20 -6
- package/src/index.ts +64 -4
- package/src/metaobjects-config.ts +48 -2
- package/src/names.ts +629 -0
- package/src/overwrite-policy.ts +15 -3
- package/src/projection/extract-view-spec.ts +85 -6
- package/src/reference/entity.ts +21 -2
- package/src/reference/names.ts +125 -0
- package/src/reference/queries.ts +19 -3
- package/src/reference/routes-hono.ts +19 -9
- package/src/reference/routes.ts +25 -9
- package/src/reference-templates.ts +1 -1
- package/src/render-context.ts +13 -1
- package/src/retired-codegen-attrs.ts +52 -0
- package/src/routes-expose.ts +86 -0
- package/src/runner.ts +122 -11
- package/src/templates/callable-file.ts +60 -8
- package/src/templates/drizzle-schema.ts +248 -60
- package/src/templates/entity-constants.ts +95 -184
- package/src/templates/entity-file.ts +46 -4
- package/src/templates/entity-meta-file.ts +16 -3
- package/src/templates/entity-ui-descriptor.ts +305 -0
- package/src/templates/field-meta.ts +100 -19
- package/src/templates/names-decl.ts +165 -0
- package/src/templates/projection-decl.ts +72 -31
- package/src/templates/queries-file.ts +17 -2
- package/src/templates/requirement-test.ts +13 -2
- package/src/templates/routes-file-hono.ts +14 -2
- package/src/templates/routes-file.ts +69 -24
- package/src/templates/value-object-file.ts +15 -1
- package/src/templates/view-decl.ts +53 -3
- package/src/templates/zod-validators.ts +41 -5
- package/src/view-context.ts +88 -0
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// plus the relations() block auto-emitted at the end.
|
|
4
4
|
|
|
5
5
|
import { code, imp, joinCode, type Code } from "ts-poet";
|
|
6
|
-
import { MetaObject, MetaField, MetaIndex, stripPackage } from "@metaobjectsdev/metadata";
|
|
6
|
+
import { MetaObject, MetaField, MetaIndex, isMetaObject, stripPackage } from "@metaobjectsdev/metadata";
|
|
7
7
|
import {
|
|
8
8
|
FIELD_SUBTYPE_LONG,
|
|
9
9
|
IDENTITY_ATTR_FIELDS, IDENTITY_ATTR_GENERATION,
|
|
10
|
+
IDENTITY_ATTR_EXPR, IDENTITY_ATTR_USING, IDENTITY_ATTR_ORDERS, IDENTITY_ATTR_WHERE,
|
|
10
11
|
GENERATION_INCREMENT, GENERATION_UUID,
|
|
11
12
|
FIELD_ATTR_AUTO_SET,
|
|
12
13
|
FIELD_ATTR_OBJECT_REF,
|
|
@@ -14,7 +15,11 @@ import {
|
|
|
14
15
|
import { fieldDeclaringPackage, type RenderContext } from "../render-context.js";
|
|
15
16
|
import { crossEntitySpecifier, valueObjectModuleSpecifier } from "../import-path.js";
|
|
16
17
|
import { mapColumnType, type ColumnSpec, type EnumIntCustomType } from "../column-mapper.js";
|
|
17
|
-
import { tableNameFromEntity
|
|
18
|
+
import { tableNameFromEntity } from "../naming.js";
|
|
19
|
+
import {
|
|
20
|
+
namesRef, physicalNameExpr, sourceSchemaExpr, indexNameExpr, columnExpr,
|
|
21
|
+
} from "../names.js";
|
|
22
|
+
import { resolveTableSchema } from "@metaobjectsdev/metadata";
|
|
18
23
|
import { renderRelationsBlock } from "./relations-block.js";
|
|
19
24
|
import { renderDocsFor } from "./jsdoc.js";
|
|
20
25
|
import { collectTphSubtypeFields } from "./tph-discriminator.js";
|
|
@@ -36,6 +41,81 @@ export function renderDrizzleSchema(obj: MetaObject, ctx: RenderContext): Code {
|
|
|
36
41
|
const tableName = obj.dbTable ?? tableNameFromEntity(obj.name, ctx.columnNamingStrategy);
|
|
37
42
|
const varName = ctx.collectionName(obj.name);
|
|
38
43
|
|
|
44
|
+
// §A6/§B2 — reference `<Entity>Names` instead of embedding the physical names a SECOND
|
|
45
|
+
// time. `namesRef` is undefined whenever the names generator is not active in this run
|
|
46
|
+
// (opt-in under ADR-0034 scaffold-and-own — an unconditional import would break every
|
|
47
|
+
// project that has not enabled it) or the object has no primary source — both PRESENCE
|
|
48
|
+
// guards. No per-site equality check: whenever the constant exists, it IS the name;
|
|
49
|
+
// `primaryRdbSource` (@metaobjectsdev/metadata) already refuses (throws) any object
|
|
50
|
+
// whose `@role: primary` sources disagree on a name, and BOTH branches reach it — the
|
|
51
|
+
// constant through `resolveObjectNames`, and `obj.dbTable` above on its own — so this
|
|
52
|
+
// file never has to re-check, and the literal arm is refused too rather than silently
|
|
53
|
+
// binding the inherited parent's table when the names generator is out of the run.
|
|
54
|
+
const names = namesRef(obj, ctx);
|
|
55
|
+
const tableNameExpr: Code = physicalNameExpr(names, tableName, obj);
|
|
56
|
+
|
|
57
|
+
// @schema — the table binding must land in the SAME schema the migration creates the table
|
|
58
|
+
// in. migrate-ts qualifies every table, view, index and FK it emits (`CREATE TABLE
|
|
59
|
+
// "sales"."widgets"`), and under ADR-0015 it owns where a table lives; codegen dropping the
|
|
60
|
+
// schema meant generated queries hit `public.widgets` while the migration built
|
|
61
|
+
// `sales.widgets`. Whether that surfaced depended entirely on the adopter's `search_path`,
|
|
62
|
+
// which is the worst kind of bug — it works on the machine that has it configured.
|
|
63
|
+
//
|
|
64
|
+
// Drizzle expresses this with a DIFFERENT call shape rather than a third argument:
|
|
65
|
+
// `pgSchema("sales").table(name, …)`. It is built inline rather than hoisted to a `const`
|
|
66
|
+
// so two tables in one file sharing a schema cannot collide on a generated identifier; a
|
|
67
|
+
// schema object is just a descriptor, so a second call costs nothing.
|
|
68
|
+
//
|
|
69
|
+
// sqlite/d1 are excluded because SQLite has no schema concept at all — migrate already
|
|
70
|
+
// REFUSES a non-default @schema there (expected-schema.ts), so there is nothing to honour
|
|
71
|
+
// and no divergence to close.
|
|
72
|
+
const tableSchema = dialect === "sqlite" ? undefined : resolveTableSchema(obj);
|
|
73
|
+
const schemaExpr: Code | undefined =
|
|
74
|
+
tableSchema === undefined ? undefined
|
|
75
|
+
: sourceSchemaExpr(names) ?? code`${JSON.stringify(tableSchema)}`;
|
|
76
|
+
const tableCall: Code = schemaExpr === undefined
|
|
77
|
+
? code`${tableFnSym}`
|
|
78
|
+
: code`${imp(`pgSchema@${importModule}`)}(${schemaExpr}).table`;
|
|
79
|
+
// A column's constant, on the same terms — but resolved against the entity that DECLARES
|
|
80
|
+
// the field, which is not always `obj`. Under TPH the fold below emits a subtype's own
|
|
81
|
+
// columns into this base's table, and those columns live in the SUBTYPE's names artifact;
|
|
82
|
+
// looking them up in the base's finds nothing.
|
|
83
|
+
//
|
|
84
|
+
// This used to pass `names` for every column and call the resulting miss "normal". It was
|
|
85
|
+
// not: the base's artifact genuinely does not carry a subtype's column, so every TPH
|
|
86
|
+
// subtype column fell through to the literal while `<Sub>Names.fields.<f>.column` sat
|
|
87
|
+
// right there, already imported by the subtype's own artifact. C# never had the bug
|
|
88
|
+
// because its emitter passes the subtype entity to `ColumnRef`; this is that shape.
|
|
89
|
+
//
|
|
90
|
+
// `fromPackage` stays `obj.package` so the import path is computed relative to the file
|
|
91
|
+
// being emitted (this base's), not to the subtype's own package.
|
|
92
|
+
// THIS entity's artifact first. It already carries every INHERITED field — a concrete
|
|
93
|
+
// entity's artifact spreads its abstract parent's `fields` — so an `extends` chain needs
|
|
94
|
+
// no redirect, and taking one would be a regression: an abstract base has no source, so
|
|
95
|
+
// it has no artifact of its own to redirect TO, and the column would fall back to a
|
|
96
|
+
// literal that `obj`'s artifact was carrying all along.
|
|
97
|
+
//
|
|
98
|
+
// A miss here means the field belongs to a different entity ENTIRELY, which under TPH is
|
|
99
|
+
// exactly the subtype whose columns this base's table absorbs. Only then resolve against
|
|
100
|
+
// the declaring entity, found through the field's own parent link.
|
|
101
|
+
//
|
|
102
|
+
// `isMetaObject`, never `instanceof`: two physical copies of @metaobjectsdev/metadata in
|
|
103
|
+
// one process give the class object and the instance different identities, so the check
|
|
104
|
+
// would return false for a real node — silently.
|
|
105
|
+
const columnNameExpr = (field: MetaField, dbName: string): Code => {
|
|
106
|
+
if (names === undefined || names.resolved.fields[field.name] !== undefined) {
|
|
107
|
+
return columnExpr(names, field.name, dbName);
|
|
108
|
+
}
|
|
109
|
+
const owner = field.parent;
|
|
110
|
+
// `fromPackage` is obj's: the import path is relative to the file being emitted (this
|
|
111
|
+
// base's), not to the subtype's own package.
|
|
112
|
+
const ownerNames =
|
|
113
|
+
owner !== undefined && isMetaObject(owner) && owner !== obj
|
|
114
|
+
? namesRef(owner, ctx, obj.package)
|
|
115
|
+
: undefined;
|
|
116
|
+
return columnExpr(ownerNames, field.name, dbName);
|
|
117
|
+
};
|
|
118
|
+
|
|
39
119
|
const primary = obj.primaryIdentity();
|
|
40
120
|
const rawPkFields = primary?.attr(IDENTITY_ATTR_FIELDS);
|
|
41
121
|
const pkFieldsList: string[] = Array.isArray(rawPkFields)
|
|
@@ -52,20 +132,58 @@ export function renderDrizzleSchema(obj: MetaObject, ctx: RenderContext): Code {
|
|
|
52
132
|
|
|
53
133
|
// Collect secondary identities and the field names that need .unique() on
|
|
54
134
|
// their column. identity.secondary is ALWAYS unique (no @unique attr) — only
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
135
|
+
// An `identity.secondary` reaches the table ONLY as the `uniqueIndex(...)` callback
|
|
136
|
+
// emitted further down — never additionally as `.unique()` on its column.
|
|
137
|
+
//
|
|
138
|
+
// It used to do both for a single-column identity, under a comment describing an
|
|
139
|
+
// either/or ("single-column identities propagate .unique() to the column; multi-column
|
|
140
|
+
// ones emit a callback uniqueIndex() entry instead") that the code did not implement:
|
|
141
|
+
// the callback loop has no column-count condition, so a one-column secondary got a
|
|
142
|
+
// column-level unique AND a table-level unique index. `migrate-ts` emits only the index
|
|
143
|
+
// for that shape — it mirrors a `<table>_<col>_unique` constraint for `@unique` fields
|
|
144
|
+
// and nothing extra for a secondary — so the generated schema declared a constraint the
|
|
145
|
+
// database does not have, and `drizzle-kit push` run against it proposes creating one.
|
|
146
|
+
//
|
|
147
|
+
// `.unique()` still comes from the FIELD's own `@unique`, via the column mapper, which
|
|
148
|
+
// is exactly the arm migrate mirrors.
|
|
58
149
|
const secondaryIdentities = obj.secondaryIdentities();
|
|
59
|
-
const uniqueFieldNames = new Set<string>();
|
|
60
|
-
for (const sec of secondaryIdentities) {
|
|
61
|
-
const fields = sec.attr(IDENTITY_ATTR_FIELDS) as string[] | undefined;
|
|
62
|
-
if (!Array.isArray(fields) || fields.length !== 1) continue; // multi-col uniques use a callback uniqueIndex, not a column flag
|
|
63
|
-
uniqueFieldNames.add(fields[0]!);
|
|
64
|
-
}
|
|
65
150
|
|
|
66
151
|
const columnLines: Code[] = [];
|
|
67
152
|
// Collect CHECK constraints for enum columns; emitted as table-level check() callbacks.
|
|
68
|
-
const checkConstraints: Array<{ name:
|
|
153
|
+
const checkConstraints: Array<{ name: Code; expr: Code }> = [];
|
|
154
|
+
/**
|
|
155
|
+
* One CHECK entry, composed against `<Entity>Names` when the artifact is in the run.
|
|
156
|
+
*
|
|
157
|
+
* #293 — migrate's `<table>_<col>_chk` is the shared convention and it is the AUTHORITY:
|
|
158
|
+
* its suffix form is systematic across five constraint kinds and those names are already
|
|
159
|
+
* in live databases, so flipping migrate instead would emit DROP/ADD CONSTRAINT churn
|
|
160
|
+
* against production for a cosmetic difference. Referencing the constants therefore
|
|
161
|
+
* composes the IDENTICAL string at runtime rather than changing the convention — the
|
|
162
|
+
* template literal below evaluates to exactly what the literal arm spells.
|
|
163
|
+
*
|
|
164
|
+
* The expression's column goes through `sql.raw`, not `sql.identifier`: the latter QUOTES
|
|
165
|
+
* the name, which changes the constraint text migrate compares against.
|
|
166
|
+
*/
|
|
167
|
+
const checkEntry = (field: MetaField, spec: ColumnSpec): { name: Code; expr: Code } => {
|
|
168
|
+
const colExpr = columnNameExpr(field, spec.dbName);
|
|
169
|
+
const values = spec.checkConstraintValues;
|
|
170
|
+
const sqlSym = imp("sql@drizzle-orm");
|
|
171
|
+
// No artifact in this run (the documented ADR-0034 opt-out) — spell both halves, which
|
|
172
|
+
// is what this file did unconditionally before.
|
|
173
|
+
if (names === undefined || values === undefined) {
|
|
174
|
+
return {
|
|
175
|
+
name: code`${JSON.stringify(`${tableName}_${spec.dbName}_chk`)}`,
|
|
176
|
+
expr: code`${spec.checkConstraint ?? ""}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// `\${` emits a literal `${` into the generated source: the name becomes a template
|
|
180
|
+
// literal and the expression an interpolation, both evaluated at run time in the
|
|
181
|
+
// consumer's code, where they produce the identical strings the literal arm spells.
|
|
182
|
+
return {
|
|
183
|
+
name: code`\`\${${tableNameExpr}}_\${${colExpr}}_chk\``,
|
|
184
|
+
expr: code`\${${sqlSym}.raw(${colExpr})} IN (${values})`,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
69
187
|
// Int-backed field.enum customType helpers, emitted ahead of the table. Keyed by
|
|
70
188
|
// const name so a shared enum used by two fields of the SAME entity emits once.
|
|
71
189
|
const enumIntTypes = new Map<string, EnumIntCustomType>();
|
|
@@ -76,7 +194,6 @@ export function renderDrizzleSchema(obj: MetaObject, ctx: RenderContext): Code {
|
|
|
76
194
|
// no longer creates.
|
|
77
195
|
if (child.isDerived()) continue;
|
|
78
196
|
const isPk = pkFieldNames.has(child.name);
|
|
79
|
-
const isUnique = uniqueFieldNames.has(child.name) && !isPk;
|
|
80
197
|
const fkInfo = fkMap.get(child.name);
|
|
81
198
|
// Compute the column spec once per field and reuse it for both the column
|
|
82
199
|
// line and the CHECK collection.
|
|
@@ -85,18 +202,9 @@ export function renderDrizzleSchema(obj: MetaObject, ctx: RenderContext): Code {
|
|
|
85
202
|
enumIntTypes.set(spec.enumIntCustomType.fnConstName, spec.enumIntCustomType);
|
|
86
203
|
}
|
|
87
204
|
const fieldDocs = renderDocsFor(child);
|
|
88
|
-
const columnLine = renderColumn(spec, child, ctx, isPk, pkGeneration, fkInfo, isComposite,
|
|
205
|
+
const columnLine = renderColumn(spec, columnNameExpr(child, spec.dbName), child, ctx, isPk, pkGeneration, fkInfo, isComposite, obj.package, obj.name);
|
|
89
206
|
columnLines.push(fieldDocs ? code` ${fieldDocs}\n${columnLine}` : columnLine);
|
|
90
|
-
if (spec.checkConstraint !== undefined)
|
|
91
|
-
checkConstraints.push({
|
|
92
|
-
// #293 — migrate's `<table>_<col>_chk` is the shared convention, and it is the
|
|
93
|
-
// authority: its suffix form is systematic across five constraint kinds and those
|
|
94
|
-
// names are already in live databases, so flipping migrate instead would emit
|
|
95
|
-
// DROP/ADD CONSTRAINT churn against production for a cosmetic difference.
|
|
96
|
-
name: `${tableName}_${spec.dbName}_chk`,
|
|
97
|
-
expr: spec.checkConstraint,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
207
|
+
if (spec.checkConstraint !== undefined) checkConstraints.push(checkEntry(child, spec));
|
|
100
208
|
}
|
|
101
209
|
|
|
102
210
|
// FR-017 Tier 2 — TPH single-table inheritance. When this entity is a
|
|
@@ -114,21 +222,12 @@ export function renderDrizzleSchema(obj: MetaObject, ctx: RenderContext): Code {
|
|
|
114
222
|
}
|
|
115
223
|
const fieldDocs = renderDocsFor(child);
|
|
116
224
|
const columnLine = renderColumn(
|
|
117
|
-
spec, child, ctx, false, undefined, fkMap.get(child.name), isComposite,
|
|
225
|
+
spec, columnNameExpr(child, spec.dbName), child, ctx, false, undefined, fkMap.get(child.name), isComposite, obj.package, obj.name, true,
|
|
118
226
|
);
|
|
119
227
|
columnLines.push(fieldDocs ? code` ${fieldDocs}\n${columnLine}` : columnLine);
|
|
120
228
|
// Enum CHECK constraints stay valid under TPH: `NULL IN (...)` is NULL
|
|
121
229
|
// (not false), so other-subtype rows with NULL pass the check.
|
|
122
|
-
if (spec.checkConstraint !== undefined)
|
|
123
|
-
checkConstraints.push({
|
|
124
|
-
// #293 — migrate's `<table>_<col>_chk` is the shared convention, and it is the
|
|
125
|
-
// authority: its suffix form is systematic across five constraint kinds and those
|
|
126
|
-
// names are already in live databases, so flipping migrate instead would emit
|
|
127
|
-
// DROP/ADD CONSTRAINT churn against production for a cosmetic difference.
|
|
128
|
-
name: `${tableName}_${spec.dbName}_chk`,
|
|
129
|
-
expr: spec.checkConstraint,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
230
|
+
if (spec.checkConstraint !== undefined) checkConstraints.push(checkEntry(child, spec));
|
|
132
231
|
}
|
|
133
232
|
|
|
134
233
|
// Build all table callback entries
|
|
@@ -138,38 +237,122 @@ export function renderDrizzleSchema(obj: MetaObject, ctx: RenderContext): Code {
|
|
|
138
237
|
callbackEntries.push(buildCompositeKeyCallback(pkFieldNames, importModule));
|
|
139
238
|
}
|
|
140
239
|
|
|
240
|
+
/**
|
|
241
|
+
* One index callback entry, for BOTH `identity.secondary` (unique) and `index.lookup`
|
|
242
|
+
* (non-unique). The two shapes differ only in that boolean, and migrate treats their
|
|
243
|
+
* physical attributes identically — `@expr`/`@using`/`@orders`/`@where` are registered on
|
|
244
|
+
* both by the same provider — so they are emitted by ONE function here. They used to be
|
|
245
|
+
* two near-identical loops, which is how the physical attributes came to be honoured by
|
|
246
|
+
* neither: a rule written twice is a rule that can be forgotten twice.
|
|
247
|
+
*
|
|
248
|
+
* What was missing is a SHAPE divergence, not a naming one (the names were reconciled
|
|
249
|
+
* separately). An `@expr`-only index emitted NOTHING at all here, because both loops
|
|
250
|
+
* required a non-empty `@fields` — while migrate emits it, keying off `@expr`. And
|
|
251
|
+
* `@where` never reached this file, so a PARTIAL unique index read as a FULLY unique one
|
|
252
|
+
* in the generated schema: `drizzle-kit push` would propose replacing the database's
|
|
253
|
+
* partial index with a total one, and any diff written from generated source disagreed
|
|
254
|
+
* with the database over a difference that was not real. Same class as the sqlite
|
|
255
|
+
* emitter dropping these in 0.15.21.
|
|
256
|
+
*/
|
|
257
|
+
const indexEntry = (
|
|
258
|
+
// `type`/`subType` as well as `name`: the index-name resolver and the artifact's
|
|
259
|
+
// `identities` / `indexes` collections both key off which KIND of node this is, and
|
|
260
|
+
// ADR-0040 put that distinction in the type rather than in an attribute.
|
|
261
|
+
node: { name: string; type: string; subType: string; attr: (n: string) => unknown },
|
|
262
|
+
fieldNames: string[],
|
|
263
|
+
unique: boolean,
|
|
264
|
+
): Code | undefined => {
|
|
265
|
+
const str = (n: string): string | undefined => {
|
|
266
|
+
const raw = node.attr(n);
|
|
267
|
+
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : undefined;
|
|
268
|
+
};
|
|
269
|
+
const expr = str(IDENTITY_ATTR_EXPR);
|
|
270
|
+
// An expression index keys off @expr; a plain one needs @fields. Mirrors migrate's
|
|
271
|
+
// `if (fieldNames.length === 0 && expr === undefined) continue`.
|
|
272
|
+
if (fieldNames.length === 0 && expr === undefined) return undefined;
|
|
273
|
+
|
|
274
|
+
const sqlSym = imp("sql@drizzle-orm");
|
|
275
|
+
const indexSym = imp(`${unique ? "uniqueIndex" : "index"}@${importModule}`);
|
|
276
|
+
|
|
277
|
+
// @orders is positional against @fields; migrate only serializes it when at least one
|
|
278
|
+
// entry is `desc`, because an all-ascending array must be indistinguishable from no
|
|
279
|
+
// orders at all for diff stability. Match that exactly or the two disagree on a
|
|
280
|
+
// no-op.
|
|
281
|
+
const ordersRaw = node.attr(IDENTITY_ATTR_ORDERS);
|
|
282
|
+
const orders = Array.isArray(ordersRaw)
|
|
283
|
+
? fieldNames.map((_, i) => (ordersRaw[i] === "desc" ? "desc" : "asc"))
|
|
284
|
+
: [];
|
|
285
|
+
const anyDesc = orders.some((o) => o === "desc");
|
|
286
|
+
const colRefs = fieldNames.map((f, i) =>
|
|
287
|
+
anyDesc ? `table.${f}.${orders[i] === "desc" ? "desc" : "asc"}()` : `table.${f}`);
|
|
288
|
+
|
|
289
|
+
// sqlite's `.on()` accepts only columns — no SQL — so an expression index is not
|
|
290
|
+
// expressible there. It is skipped rather than emitted wrong; migrate still creates it,
|
|
291
|
+
// and a Drizzle declaration that silently indexed a COLUMN instead of the expression
|
|
292
|
+
// would be a worse answer than an absent one.
|
|
293
|
+
if (expr !== undefined && dialect === "sqlite") return undefined;
|
|
294
|
+
|
|
295
|
+
// @using selects the access method and takes the columns itself, so it REPLACES .on().
|
|
296
|
+
// Postgres-only: SQLite has no USING clause. migrate omits a `btree` value as the
|
|
297
|
+
// default, and so does this — emitting it would be a spurious difference.
|
|
298
|
+
const using = dialect === "sqlite" ? undefined : str(IDENTITY_ATTR_USING);
|
|
299
|
+
const target: Code =
|
|
300
|
+
expr !== undefined ? code`${sqlSym}\`${expr}\``
|
|
301
|
+
: code`${colRefs.join(", ")}`;
|
|
302
|
+
// The CONSTANT when the artifact is in the run, the literal otherwise — and the
|
|
303
|
+
// literal arm goes through `resolveIndexName` too, so the two paths answer with one
|
|
304
|
+
// function. Both halves matter: migrate's expected-schema resolves the same name and
|
|
305
|
+
// the two spelled it independently until now, which is how codegen came to declare
|
|
306
|
+
// `idx_<table>_<col>` against an index the database actually held under
|
|
307
|
+
// `identity.name` (fdb4118f1); and an empty `index.lookup` name — which the loader
|
|
308
|
+
// accepts, unlike an identity — is refused here rather than emitting `index("")`.
|
|
309
|
+
const indexName = indexNameExpr(names, node);
|
|
310
|
+
const head: Code =
|
|
311
|
+
using !== undefined && using !== "btree"
|
|
312
|
+
? code`${indexSym}(${indexName}).using(${JSON.stringify(using)}, ${target})`
|
|
313
|
+
: code`${indexSym}(${indexName}).on(${target})`;
|
|
314
|
+
|
|
315
|
+
// @where — the partial-index predicate, available on both dialects.
|
|
316
|
+
const where = str(IDENTITY_ATTR_WHERE);
|
|
317
|
+
return where === undefined ? head : code`${head}.where(${sqlSym}\`${where}\`)`;
|
|
318
|
+
};
|
|
319
|
+
|
|
141
320
|
// identity.secondary — always unique (uniqueness is in the type, no boolean).
|
|
142
321
|
for (const sec of secondaryIdentities) {
|
|
143
322
|
const fields = sec.attr(IDENTITY_ATTR_FIELDS) as string[] | undefined;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
//
|
|
323
|
+
// The identity's OWN name, which is what migrate puts in the database
|
|
324
|
+
// (`expected-schema.ts`, secondary-identity pass) and what `index.lookup` three
|
|
325
|
+
// blocks below already emits. This used to compose `idx_<table>_<col>...` by running
|
|
326
|
+
// the naming strategy over the FIELD names — two defects in one expression: the name
|
|
327
|
+
// disagreed with migrate's (so a DROP INDEX written from generated source failed, and
|
|
328
|
+
// any schema diff reported a difference that was not real), and running the strategy
|
|
329
|
+
// over a field NAME cannot see `@column`, so a declared physical column was invisible
|
|
330
|
+
// to it. Gated by `secondary-index-name-parity.test.ts`, whose fixture is de-blinded
|
|
331
|
+
// against exactly that second defect.
|
|
332
|
+
// Column refs use the callback param `table` (not the outer varName) so TS does not see
|
|
148
333
|
// the table referencing itself inside its own initializer (TS7022/TS7024).
|
|
149
|
-
const
|
|
150
|
-
callbackEntries.push(
|
|
334
|
+
const entry = indexEntry(sec, Array.isArray(fields) ? fields : [], /* unique */ true);
|
|
335
|
+
if (entry !== undefined) callbackEntries.push(entry);
|
|
151
336
|
}
|
|
152
337
|
|
|
153
338
|
// index.lookup — always non-unique. Use MetaIndex.fields() per ADR-0039.
|
|
154
339
|
for (const lookup of obj.lookupIndexes()) {
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
const indexSym = imp(`index@${importModule}`);
|
|
158
|
-
const cols = fields.map((f) => `table.${f}`).join(", ");
|
|
159
|
-
callbackEntries.push(code`${indexSym}(${JSON.stringify(lookup.name)}).on(${cols})`);
|
|
340
|
+
const entry = indexEntry(lookup, lookup.fields(), /* unique */ false);
|
|
341
|
+
if (entry !== undefined) callbackEntries.push(entry);
|
|
160
342
|
}
|
|
161
343
|
|
|
162
|
-
// Emit table-level CHECK constraints for enum fields.
|
|
344
|
+
// Emit table-level CHECK constraints for enum fields. Both halves are composed by
|
|
345
|
+
// `checkEntry` above, which owns the constant-vs-literal decision for the pair.
|
|
163
346
|
for (const { name, expr } of checkConstraints) {
|
|
164
347
|
const checkSym = imp(`check@${importModule}`);
|
|
165
348
|
const sqlSym = imp("sql@drizzle-orm");
|
|
166
|
-
callbackEntries.push(code`${checkSym}(${
|
|
349
|
+
callbackEntries.push(code`${checkSym}(${name}, ${sqlSym}\`${expr}\`)`);
|
|
167
350
|
}
|
|
168
351
|
|
|
169
352
|
let tableBlock: Code;
|
|
170
353
|
if (callbackEntries.length > 0) {
|
|
171
354
|
tableBlock = code`
|
|
172
|
-
export const ${varName} = ${
|
|
355
|
+
export const ${varName} = ${tableCall}(${tableNameExpr}, {
|
|
173
356
|
${joinCode(columnLines, { on: ",\n", trim: false })}
|
|
174
357
|
}, (table) => [
|
|
175
358
|
${joinCode(callbackEntries, { on: ",\n ", trim: false })}
|
|
@@ -177,7 +360,7 @@ ${joinCode(columnLines, { on: ",\n", trim: false })}
|
|
|
177
360
|
`;
|
|
178
361
|
} else {
|
|
179
362
|
tableBlock = code`
|
|
180
|
-
export const ${varName} = ${
|
|
363
|
+
export const ${varName} = ${tableCall}(${tableNameExpr}, {
|
|
181
364
|
${joinCode(columnLines, { on: ",\n", trim: false })}
|
|
182
365
|
});
|
|
183
366
|
`;
|
|
@@ -309,13 +492,16 @@ function inlineObjectLiteral(obj: Record<string, unknown>): string {
|
|
|
309
492
|
/** Render one column line (field name + Drizzle column expression). */
|
|
310
493
|
function renderColumn(
|
|
311
494
|
spec: ColumnSpec,
|
|
495
|
+
// §A6 — the column's physical name as an EXPRESSION: either the `<Entity>Names` constant
|
|
496
|
+
// or the same literal as before. Passed in rather than derived here so one call site owns
|
|
497
|
+
// the decision (and the equality check that makes it byte-safe).
|
|
498
|
+
dbNameExpr: Code,
|
|
312
499
|
field: MetaField,
|
|
313
500
|
ctx: RenderContext,
|
|
314
501
|
isPk: boolean,
|
|
315
502
|
pkGeneration: string | undefined,
|
|
316
503
|
fkInfo: FkInfo | undefined,
|
|
317
504
|
isComposite: boolean,
|
|
318
|
-
isUnique: boolean = false,
|
|
319
505
|
entityPackage: string | undefined = undefined,
|
|
320
506
|
// Name of the entity this column belongs to — used to detect a self-referential
|
|
321
507
|
// FK (target entity === this entity), which Drizzle emits without a self-import.
|
|
@@ -332,12 +518,11 @@ function renderColumn(
|
|
|
332
518
|
? spec.enumIntCustomType.fnConstName
|
|
333
519
|
: imp(`${spec.fnName}@${spec.importModule}`);
|
|
334
520
|
|
|
335
|
-
const dbNameLit = JSON.stringify(spec.dbName);
|
|
336
521
|
let baseCall: Code;
|
|
337
522
|
if (spec.fnOptions !== undefined && Object.keys(spec.fnOptions).length > 0) {
|
|
338
|
-
baseCall = code`${fnSym}(${
|
|
523
|
+
baseCall = code`${fnSym}(${dbNameExpr}, ${inlineObjectLiteral(spec.fnOptions)})`;
|
|
339
524
|
} else {
|
|
340
|
-
baseCall = code`${fnSym}(${
|
|
525
|
+
baseCall = code`${fnSym}(${dbNameExpr})`;
|
|
341
526
|
}
|
|
342
527
|
|
|
343
528
|
let pkSuffix = "";
|
|
@@ -350,10 +535,10 @@ function renderColumn(
|
|
|
350
535
|
// Postgres: bigserial for long (8-byte), serial for int (4-byte).
|
|
351
536
|
if (field.subType === FIELD_SUBTYPE_LONG) {
|
|
352
537
|
const bigserialSym = imp(`bigserial@${spec.importModule}`);
|
|
353
|
-
baseCall = code`${bigserialSym}(${
|
|
538
|
+
baseCall = code`${bigserialSym}(${dbNameExpr}, { mode: "number" })`;
|
|
354
539
|
} else {
|
|
355
540
|
const serialSym = imp(`serial@${spec.importModule}`);
|
|
356
|
-
baseCall = code`${serialSym}(${
|
|
541
|
+
baseCall = code`${serialSym}(${dbNameExpr})`;
|
|
357
542
|
}
|
|
358
543
|
pkSuffix = isComposite ? "" : ".primaryKey()";
|
|
359
544
|
}
|
|
@@ -370,14 +555,17 @@ function renderColumn(
|
|
|
370
555
|
}
|
|
371
556
|
|
|
372
557
|
let modifiersStr = pkSuffix;
|
|
373
|
-
//
|
|
374
|
-
|
|
558
|
+
// No `isUnique` parameter here any more, and its absence is the fix rather than a tidy-up.
|
|
559
|
+
// A single-column `identity.secondary` used to append `.unique()` on top of the constraint
|
|
560
|
+
// migrate already emits under the identity's own name, so codegen declared one constraint
|
|
561
|
+
// the database does not have. When that producer was removed both call sites started
|
|
562
|
+
// passing a literal `false`, leaving a parameter whose every branch was unreachable — a
|
|
563
|
+
// dead switch that reads like a live one. `spec.modifiers` carries whatever uniqueness the
|
|
564
|
+
// column genuinely has, and the loop below is the only thing that applies it.
|
|
375
565
|
for (const m of spec.modifiers) {
|
|
376
566
|
// Single-column PKs imply notNull/unique; avoid emitting them twice.
|
|
377
567
|
// Composite-PK columns are NOT declared with .primaryKey(), so they DO need .notNull().
|
|
378
568
|
if (isPk && !isComposite && (m === ".notNull()" || m === ".unique()")) continue;
|
|
379
|
-
// Avoid double-emitting .unique() if it was already appended above.
|
|
380
|
-
if (isUnique && m === ".unique()") continue;
|
|
381
569
|
// TPH subtype-only column: never .notNull() / .unique() — rows of other
|
|
382
570
|
// subtypes store NULL, so neither constraint can hold across the table.
|
|
383
571
|
if (forceNullable && (m === ".notNull()" || m === ".unique()")) continue;
|