@metaobjectsdev/migrate-ts 0.6.0 → 0.7.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.
@@ -0,0 +1,147 @@
1
+ // expected-views.ts — derive ViewDescriptor[] from projection metadata.
2
+ //
3
+ // Ports the shape of csharp/MetaObjects.Codegen/Schema/PostgresSchema.cs's
4
+ // CreateView. v1 supports:
5
+ //
6
+ // * passthrough origin (no @via) → plain column from the single base entity
7
+ // * aggregate origin (@agg/@of/@via) → correlated subquery over a to-many
8
+ //
9
+ // Deferred to v2 (returns a "-- TODO" view that the runner skips applying):
10
+ //
11
+ // * passthrough origin WITH @via → to-one correlated subquery
12
+ // * collection origin → json_agg over a to-many
13
+ // * multi-base projections → blocked
14
+ //
15
+ // Identifiers are quoted throughout so mixed-case column names (e.g.
16
+ // "programId") survive PG's case-folding pass.
17
+ import { MetaPassthroughOrigin, MetaAggregateOrigin, MetaCollectionOrigin, MetaSource, SOURCE_ROLE_PRIMARY, TYPE_OBJECT, TYPE_ORIGIN, resolveColumnName, resolveTableName, stripPackage, } from "@metaobjectsdev/metadata";
18
+ export function buildExpectedViews(root, strategy) {
19
+ const out = [];
20
+ for (const child of root.ownChildren()) {
21
+ if (child.type !== TYPE_OBJECT)
22
+ continue;
23
+ const proj = child;
24
+ if (proj.isAbstract)
25
+ continue;
26
+ if (!isReadOnlyProjection(proj))
27
+ continue;
28
+ const view = buildView(proj, root, strategy);
29
+ if (view !== null)
30
+ out.push(view);
31
+ }
32
+ return out;
33
+ }
34
+ function isReadOnlyProjection(entity) {
35
+ const sources = entity.ownChildren().filter((c) => c instanceof MetaSource);
36
+ if (sources.length === 0)
37
+ return false;
38
+ const hasReadOnly = sources.some((s) => s.isReadOnly());
39
+ const hasWritable = sources.some((s) => !s.isReadOnly());
40
+ return hasReadOnly && !hasWritable;
41
+ }
42
+ function buildView(projection, root, strategy) {
43
+ const primarySource = projection.ownChildren().find((c) => c instanceof MetaSource && c.role === SOURCE_ROLE_PRIMARY);
44
+ if (primarySource?.tableName === undefined)
45
+ return null;
46
+ const viewName = primarySource.tableName;
47
+ const cols = [];
48
+ let baseEntity;
49
+ let blocked;
50
+ const T = "t"; // target alias in correlated subqueries
51
+ const baseTable = () => {
52
+ if (baseEntity === undefined)
53
+ return undefined;
54
+ const found = root.findObject(baseEntity);
55
+ return found ? resolveTableName(found) : baseEntity;
56
+ };
57
+ for (const f of projection.fields()) {
58
+ const origin = f.ownChildren().find((c) => c.type === TYPE_ORIGIN);
59
+ const fieldCol = resolveColumnName(f, strategy);
60
+ if (origin instanceof MetaPassthroughOrigin && origin.via === undefined &&
61
+ origin.from !== undefined && origin.from.includes(".")) {
62
+ const [ent, field] = splitDot(origin.from);
63
+ const bare = stripPackage(ent);
64
+ baseEntity ??= bare;
65
+ if (bare !== baseEntity) {
66
+ blocked = "passthrough from multiple base entities";
67
+ break;
68
+ }
69
+ const srcEntity = root.findObject(baseEntity);
70
+ const srcCol = resolveColumnByName(srcEntity, field, strategy);
71
+ cols.push(` "${srcCol}" AS "${fieldCol}"`);
72
+ }
73
+ else if (origin instanceof MetaAggregateOrigin && origin.agg !== undefined &&
74
+ origin.of !== undefined && origin.via !== undefined &&
75
+ origin.of.includes(".") && origin.via.includes(".")) {
76
+ const [baseEnt, relName] = splitDot(origin.via);
77
+ const bareBase = stripPackage(baseEnt);
78
+ baseEntity ??= bareBase;
79
+ if (bareBase !== baseEntity) {
80
+ blocked = "aggregate over a different base entity";
81
+ break;
82
+ }
83
+ const fk = resolveToManyFk(root, baseEntity, relName, strategy);
84
+ if (fk === null) {
85
+ blocked = `unresolved to-many FK for @via "${origin.via}" (target needs an identity.reference back to ${baseEntity})`;
86
+ break;
87
+ }
88
+ const ofCol = resolveColumnByName(fk.target, splitDot(origin.of)[1], strategy);
89
+ cols.push(` (SELECT ${origin.agg}(${T}."${ofCol}") ` +
90
+ `FROM "${fk.targetTable}" ${T} ` +
91
+ `WHERE ${T}."${fk.fkCol}" = "${baseTable()}"."${fk.parentCol}") AS "${fieldCol}"`);
92
+ }
93
+ else if (origin instanceof MetaPassthroughOrigin || origin instanceof MetaCollectionOrigin) {
94
+ blocked = `field "${f.name}" uses an origin shape not yet supported by TS migrate-ts (passthrough-via / collection — deferred)`;
95
+ break;
96
+ }
97
+ else {
98
+ blocked = `field "${f.name}" has no resolvable origin`;
99
+ break;
100
+ }
101
+ }
102
+ if (blocked !== undefined || baseEntity === undefined || cols.length === 0) {
103
+ // Caller decides what to do with a null result. For v1 the runner just
104
+ // omits it from expected.views — diff sees no expected view, no
105
+ // create-view change, and an actual leftover view (if any) gets dropped.
106
+ return null;
107
+ }
108
+ const sql = `SELECT\n${cols.join(",\n")}\nFROM "${baseTable()}"`;
109
+ const view = { name: viewName, sql };
110
+ return view;
111
+ }
112
+ function splitDot(s) {
113
+ const i = s.indexOf(".");
114
+ return [s.slice(0, i), s.slice(i + 1)];
115
+ }
116
+ function resolveColumnByName(owner, fieldName, strategy) {
117
+ if (owner === undefined)
118
+ return fieldName;
119
+ const field = owner.fields().find((f) => f.name === fieldName);
120
+ return field ? resolveColumnName(field, strategy) : fieldName;
121
+ }
122
+ function resolveToManyFk(root, baseEntityName, relName, strategy) {
123
+ const baseObj = root.findObject(baseEntityName);
124
+ if (baseObj === undefined)
125
+ return null;
126
+ const rel = baseObj.relationships().find((r) => r.name === relName);
127
+ if (rel === undefined || rel.objectRef === undefined)
128
+ return null;
129
+ const target = root.findObject(stripPackage(rel.objectRef));
130
+ if (target === undefined)
131
+ return null;
132
+ const fkRef = target.referenceIdentities().find((r) => r.targetEntity !== undefined && stripPackage(r.targetEntity) === baseEntityName);
133
+ if (fkRef === undefined)
134
+ return null;
135
+ const fkFields = fkRef.fields;
136
+ if (fkFields.length === 0)
137
+ return null;
138
+ const fkCol = resolveColumnByName(target, fkFields[0], strategy);
139
+ const parentFieldName = fkRef.targetFields.length > 0
140
+ ? fkRef.targetFields[0]
141
+ : baseObj.primaryIdentity()?.fields[0];
142
+ if (parentFieldName === undefined)
143
+ return null;
144
+ const parentCol = resolveColumnByName(baseObj, parentFieldName, strategy);
145
+ return { target, targetTable: resolveTableName(target), fkCol, parentCol };
146
+ }
147
+ //# sourceMappingURL=expected-views.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expected-views.js","sourceRoot":"","sources":["../src/expected-views.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -68,6 +68,14 @@ export interface ViewDescriptor {
68
68
  name: string;
69
69
  /** Same semantics as TableDescriptor.schema. */
70
70
  schema?: string;
71
+ /**
72
+ * View body: everything between `CREATE VIEW <name> AS` and the trailing `;`
73
+ * (the SELECT clause through the FROM/WHERE/GROUP-BY tail). Populated by
74
+ * `buildExpectedSchema` from projection metadata; omitted by introspect
75
+ * (body-level comparison isn't implemented yet — diff matches by name only,
76
+ * so a body change does NOT trigger replace-view today).
77
+ */
78
+ sql?: string;
71
79
  }
72
80
  /**
73
81
  * Every variant with a `table: string` (or `table: TableDescriptor`) field also
@@ -166,14 +174,17 @@ export type Change = {
166
174
  } | {
167
175
  kind: "create-view";
168
176
  view: ViewDescriptor;
177
+ schema?: string;
169
178
  status: ChangeStatus;
170
179
  } | {
171
180
  kind: "drop-view";
172
181
  view: string;
182
+ schema?: string;
173
183
  status: ChangeStatus;
174
184
  } | {
175
185
  kind: "replace-view";
176
186
  view: ViewDescriptor;
187
+ schema?: string;
177
188
  status: ChangeStatus;
178
189
  };
179
190
  export type ChangeKind = Change["kind"];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/migrate-ts",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-rc.1",
4
4
  "description": "Schema migration tooling for MetaObjects: diff metadata vs DB and emit SQL for Postgres and SQLite.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@iarna/toml": "^2.2.5",
37
- "@metaobjectsdev/metadata": "0.6.0"
37
+ "@metaobjectsdev/metadata": "0.7.0-rc.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "kysely": ">=0.27.0"
package/src/diff/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  SchemaSnapshot, TableDescriptor, ColumnDescriptor, IndexDescriptor, FkDescriptor,
3
+ ViewDescriptor,
3
4
  Change, ChangeStatus, DiffResult, AllowOptions, AmbiguousCallback,
4
5
  } from "../types.js";
5
6
  import type { SqlType } from "../sql-type.js";
@@ -150,6 +151,13 @@ export async function diff(
150
151
  diffTableForeignKeys(expectedTable, actualTable, changes);
151
152
  }
152
153
 
154
+ // Pass 2b: views. Identity is (schema, name). Body comparison isn't
155
+ // implemented — introspect doesn't read view bodies today, so a name match
156
+ // is no-change regardless of how the user's projection metadata has evolved.
157
+ // Users who change a view body without renaming need to manually drop+recreate
158
+ // or do a full bootstrap until replace-view-from-body-diff lands.
159
+ diffViews(args.expected.views, args.actual.views, changes);
160
+
153
161
  // Pass 3: detect table renames BEFORE column renames — so a renamed table's
154
162
  // columns are not scanned as orphaned drop/add pairs.
155
163
  await detectTableRenames(changes, args.onAmbiguous);
@@ -273,6 +281,27 @@ function diffTableForeignKeys(
273
281
  }
274
282
  }
275
283
 
284
+ function viewIdentity(v: { name: string; schema?: string }): string {
285
+ return (v.schema ?? DEFAULT_DB_SCHEMA_POSTGRES) + "." + v.name;
286
+ }
287
+
288
+ function diffViews(
289
+ expected: ViewDescriptor[], actual: ViewDescriptor[], changes: Change[],
290
+ ): void {
291
+ const exp = new Map(expected.map((v) => [viewIdentity(v), v] as const));
292
+ const act = new Map(actual.map((v) => [viewIdentity(v), v] as const));
293
+ for (const [id, v] of exp) {
294
+ if (!act.has(id)) {
295
+ changes.push({ kind: "create-view", view: v, ...schemaSpread(v.schema), status: ALLOWED });
296
+ }
297
+ }
298
+ for (const [id, v] of act) {
299
+ if (!exp.has(id)) {
300
+ changes.push({ kind: "drop-view", view: v.name, ...schemaSpread(v.schema), status: ALLOWED });
301
+ }
302
+ }
303
+ }
304
+
276
305
  function columnDefaultsEqual(a: ColumnDescriptor["default"], b: ColumnDescriptor["default"]): boolean {
277
306
  if (a === undefined && b === undefined) return true;
278
307
  if (a === undefined || b === undefined) return false;
package/src/emit/index.ts CHANGED
@@ -25,11 +25,15 @@ export function emit(changes: Change[], opts: EmitOptions): EmitResult {
25
25
  const blocked = changes.filter((c) => c.status.state === "blocked");
26
26
  if (blocked.length > 0) throw new BlockedChangesError(blocked);
27
27
 
28
- const viewChanges = changes.filter((c) => VIEW_KINDS.has(c.kind));
29
- if (viewChanges.length > 0) {
30
- throw new Error(
31
- `view migration not implemented in v0.1 (${viewChanges.length} view-targeting change(s); deferred to v0.3)`,
32
- );
28
+ // Views are postgres-only for now (sqlite/d1 DDL renderers don't handle them).
29
+ if (opts.dialect !== "postgres") {
30
+ const viewChanges = changes.filter((c) => VIEW_KINDS.has(c.kind));
31
+ if (viewChanges.length > 0) {
32
+ throw new Error(
33
+ `view migration not implemented for dialect "${opts.dialect}" ` +
34
+ `(${viewChanges.length} view-targeting change(s); postgres-only today)`,
35
+ );
36
+ }
33
37
  }
34
38
 
35
39
  switch (opts.dialect) {
@@ -1,11 +1,15 @@
1
1
  import type {
2
2
  Change, EmitResult, ColumnDescriptor, IndexDescriptor, FkDescriptor,
3
- TableDescriptor, ColumnDefault, FkAction,
3
+ TableDescriptor, ViewDescriptor, ColumnDefault, FkAction,
4
4
  } from "../types.js";
5
5
  import type { SqlType } from "../sql-type.js";
6
6
  import { DEFAULT_DB_SCHEMA_POSTGRES } from "@metaobjectsdev/metadata";
7
7
 
8
+ // Stages run low → high. drop-view + drop-fk run BEFORE drop-table so a view
9
+ // that depends on a soon-to-be-dropped table is removed first. create-view
10
+ // runs AFTER add-fk so the view can reference the new schema in full.
8
11
  const STAGE_ORDER: Record<Change["kind"], number> = {
12
+ "drop-view": 0,
9
13
  "create-table": 1,
10
14
  "add-column": 2, "drop-column": 2,
11
15
  "change-column-type": 2, "change-column-nullable": 2, "change-column-default": 2,
@@ -13,8 +17,7 @@ const STAGE_ORDER: Record<Change["kind"], number> = {
13
17
  "add-index": 4, "drop-index": 4,
14
18
  "add-fk": 5, "drop-fk": 5,
15
19
  "drop-table": 6,
16
- // view kinds never reach here (filtered in emit())
17
- "create-view": 99, "drop-view": 99, "replace-view": 99,
20
+ "create-view": 7, "replace-view": 7,
18
21
  };
19
22
 
20
23
  export function renderPostgres(changes: Change[]): EmitResult {
@@ -58,11 +61,9 @@ function renderUp(c: Change): string {
58
61
  case "drop-index": return `DROP INDEX ${quoteIndexQualified(c.index, c.schema)};`;
59
62
  case "add-fk": return renderAddFk(c.table, c.schema, c.fk);
60
63
  case "drop-fk": return `ALTER TABLE ${quoteQualified(c.table, c.schema)} DROP CONSTRAINT ${quote(c.fk)};`;
61
- case "create-view":
62
- case "drop-view":
63
- case "replace-view":
64
- // emit() filters these; defensive throw if reached.
65
- throw new Error(`unexpected view-kind in renderPostgres: ${c.kind}`);
64
+ case "create-view": return renderCreateView(c.view, c.schema, /* orReplace */ false);
65
+ case "drop-view": return `DROP VIEW ${quoteQualifiedView(c.view, c.schema)};`;
66
+ case "replace-view": return renderCreateView(c.view, c.schema, /* orReplace */ true);
66
67
  }
67
68
  }
68
69
 
@@ -87,10 +88,9 @@ function renderDown(c: Change): string {
87
88
  case "drop-index": return `-- WARNING: down migration cannot restore the original index definition`;
88
89
  case "add-fk": return `ALTER TABLE ${quoteQualified(c.table, c.schema)} DROP CONSTRAINT ${quote(c.fk.name)};`;
89
90
  case "drop-fk": return `-- WARNING: down migration cannot restore the original FK definition`;
90
- case "create-view":
91
- case "drop-view":
92
- case "replace-view":
93
- throw new Error(`unexpected view-kind in renderPostgres: ${c.kind}`);
91
+ case "create-view": return `DROP VIEW ${quoteQualifiedView(c.view.name, c.schema)};`;
92
+ case "drop-view": return `-- WARNING: down migration cannot restore the original view definition`;
93
+ case "replace-view": return `-- WARNING: down migration cannot restore the original view definition`;
94
94
  }
95
95
  }
96
96
 
@@ -219,3 +219,16 @@ function quoteIndexQualified(index: string, schema: string | undefined): string
219
219
  if (!schema || schema === DEFAULT_DB_SCHEMA_POSTGRES) return quote(index);
220
220
  return quote(schema) + "." + quote(index);
221
221
  }
222
+
223
+ /** Same shape as quoteQualified, just for view identifiers (kept separate for readability). */
224
+ function quoteQualifiedView(view: string, schema: string | undefined): string {
225
+ return quoteQualified(view, schema);
226
+ }
227
+
228
+ function renderCreateView(v: ViewDescriptor, schema: string | undefined, orReplace: boolean): string {
229
+ if (v.sql === undefined || v.sql.trim().length === 0) {
230
+ throw new Error(`view "${v.name}" has no sql body — buildExpectedSchema must populate it before emit`);
231
+ }
232
+ const prefix = orReplace ? "CREATE OR REPLACE VIEW" : "CREATE VIEW";
233
+ return `${prefix} ${quoteQualifiedView(v.name, schema)} AS\n${v.sql};`;
234
+ }
@@ -1,10 +1,11 @@
1
- import type { MetaData, MetaObject, MetaRoot } from "@metaobjectsdev/metadata";
1
+ import type { ColumnNamingStrategy, MetaData, MetaObject, MetaRoot } from "@metaobjectsdev/metadata";
2
2
  import {
3
3
  TYPE_OBJECT,
4
4
  MetaSource,
5
5
  IDENTITY_ATTR_GENERATION,
6
6
  IDENTITY_ATTR_UNIQUE,
7
7
  FIELD_ATTR_DEFAULT,
8
+ FIELD_ATTR_MAX_LENGTH,
8
9
  FIELD_ATTR_UNIQUE,
9
10
  FIELD_SUBTYPE_STRING,
10
11
  FIELD_SUBTYPE_INT,
@@ -25,12 +26,14 @@ import {
25
26
  FIELD_ATTR_STORAGE,
26
27
  STORAGE_FLATTENED,
27
28
  DOC_ATTR_DESCRIPTION,
29
+ applyColumnNamingStrategy, DEFAULT_COLUMN_NAMING_STRATEGY,
28
30
  resolveTableName, resolveColumnName, resolveTableSchema,
29
31
  } from "@metaobjectsdev/metadata";
30
32
  import type { SqlType } from "./sql-type.js";
31
33
  import type {
32
34
  Dialect, SchemaSnapshot, TableDescriptor, ColumnDescriptor, IndexDescriptor, FkDescriptor,
33
35
  } from "./types.js";
36
+ import { buildExpectedViews } from "./expected-views.js";
34
37
  import {
35
38
  resolveReferentialActions,
36
39
  validateSetNullNullability,
@@ -49,6 +52,12 @@ export interface BuildExpectedSchemaOptions {
49
52
  * patterns produce INTEGER / TEXT in the actual DB.
50
53
  */
51
54
  dialect?: Dialect;
55
+ /**
56
+ * Column-naming strategy for fields with no `@column` override. Defaults to
57
+ * `"snake_case"`. Must match the runtime's `ObjectManager` strategy — a
58
+ * mismatch yields a schema whose columns the runtime can't address.
59
+ */
60
+ columnNamingStrategy?: ColumnNamingStrategy;
52
61
  }
53
62
 
54
63
  export function buildExpectedSchema(
@@ -58,6 +67,7 @@ export function buildExpectedSchema(
58
67
  // D1 is SQLite at the SQL level; normalize it so downstream dialect checks
59
68
  // don't need to handle "d1" separately.
60
69
  const dialect = opts?.dialect === "d1" ? "sqlite" : opts?.dialect;
70
+ const strategy: ColumnNamingStrategy = opts?.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY;
61
71
 
62
72
  // Pass 1: collect entities + their resolved table names.
63
73
  // Skip:
@@ -87,7 +97,7 @@ export function buildExpectedSchema(
87
97
  // Schema is resolved here (not stored in Pass 1) to avoid exactOptionalPropertyTypes
88
98
  // issues with `string | undefined` vs `schema?: string`.
89
99
  const tables: TableDescriptor[] = entities.map(({ entity, tableName }) => {
90
- const t = buildTable(entity, tableName, resolveTargetTable, root as MetaRoot);
100
+ const t = buildTable(entity, tableName, resolveTargetTable, root as MetaRoot, strategy);
91
101
  const schema = resolveTableSchema(entity);
92
102
  if (schema !== undefined) t.schema = schema;
93
103
  return t;
@@ -113,7 +123,12 @@ export function buildExpectedSchema(
113
123
  }
114
124
  }
115
125
 
116
- return { tables, views: [] };
126
+ // Pass 4: views from read-only projections. Built regardless of dialect so
127
+ // the diff produces correct create-view changes; emit() refuses them for
128
+ // sqlite/d1 with a clear error ("view migration not implemented for ...").
129
+ const views = buildExpectedViews(root as MetaRoot, strategy);
130
+
131
+ return { tables, views };
117
132
  }
118
133
 
119
134
  /**
@@ -143,6 +158,7 @@ function buildTable(
143
158
  tableName: string,
144
159
  resolveTargetTable: (entityName: string) => string | undefined,
145
160
  root: MetaRoot,
161
+ strategy: ColumnNamingStrategy,
146
162
  ): TableDescriptor {
147
163
  // Use effective accessors so inherited fields/identities (from `extends:` /
148
164
  // abstract bases like BaseEntity) are included.
@@ -155,7 +171,7 @@ function buildTable(
155
171
 
156
172
  const primaryKey = pkJsNames.map((jsName) => {
157
173
  const field = findField(entity, jsName);
158
- return field ? resolveColumnName(field) : toSnake(jsName);
174
+ return field ? resolveColumnName(field, strategy) : applyColumnNamingStrategy(jsName, strategy);
159
175
  });
160
176
 
161
177
  const columns: ColumnDescriptor[] = [];
@@ -167,17 +183,17 @@ function buildTable(
167
183
  ) {
168
184
  // Flattened storage: expand nested value-object fields as prefixed columns.
169
185
  // The parent field.object itself does NOT produce its own column.
170
- columns.push(...flattenObjectField(field, root));
186
+ columns.push(...flattenObjectField(field, root, strategy));
171
187
  } else {
172
- columns.push(buildColumn(field, isPk, isPk ? pkGeneration : undefined));
188
+ columns.push(buildColumn(field, isPk, isPk ? pkGeneration : undefined, strategy));
173
189
  }
174
190
  }
175
191
 
176
192
  const descriptor: TableDescriptor = {
177
193
  name: tableName,
178
194
  columns,
179
- indexes: buildSecondaryIndexes(entity, tableName),
180
- foreignKeys: buildForeignKeys(entity, tableName, resolveTargetTable, root),
195
+ indexes: buildSecondaryIndexes(entity, tableName, strategy),
196
+ foreignKeys: buildForeignKeys(entity, tableName, resolveTargetTable, root, strategy),
181
197
  primaryKey,
182
198
  };
183
199
  const entityDesc = readDescription(entity);
@@ -196,7 +212,9 @@ function readDescription(node: { attr: (n: string) => unknown }): string | undef
196
212
  return typeof v === "string" && v.length > 0 ? v : undefined;
197
213
  }
198
214
 
199
- function buildSecondaryIndexes(entity: MetaObject, tableName: string): IndexDescriptor[] {
215
+ function buildSecondaryIndexes(
216
+ entity: MetaObject, tableName: string, strategy: ColumnNamingStrategy,
217
+ ): IndexDescriptor[] {
200
218
  const indexes: IndexDescriptor[] = [];
201
219
 
202
220
  // (a) Implicit unique indexes from @unique fields. Drizzle auto-creates these
@@ -205,7 +223,7 @@ function buildSecondaryIndexes(entity: MetaObject, tableName: string): IndexDesc
205
223
  // doesn't see them as drop-only on the actual side.
206
224
  for (const field of entity.fields()) {
207
225
  if (field.ownAttr(FIELD_ATTR_UNIQUE) !== true) continue;
208
- const colName = resolveColumnName(field);
226
+ const colName = resolveColumnName(field, strategy);
209
227
  indexes.push({
210
228
  name: `${tableName}_${colName}_unique`,
211
229
  columns: [colName],
@@ -221,7 +239,7 @@ function buildSecondaryIndexes(entity: MetaObject, tableName: string): IndexDesc
221
239
  if (fieldNames.length === 0) continue;
222
240
  const cols = fieldNames.map((jsName) => {
223
241
  const field = findField(entity, jsName);
224
- return field ? resolveColumnName(field) : toSnake(jsName);
242
+ return field ? resolveColumnName(field, strategy) : applyColumnNamingStrategy(jsName, strategy);
225
243
  });
226
244
  const uniqueAttr = identity.ownAttr(IDENTITY_ATTR_UNIQUE);
227
245
  indexes.push({
@@ -238,6 +256,7 @@ function buildForeignKeys(
238
256
  tableName: string,
239
257
  resolveTargetTable: (entityName: string) => string | undefined,
240
258
  root: MetaRoot,
259
+ strategy: ColumnNamingStrategy,
241
260
  ): FkDescriptor[] {
242
261
  const fks: FkDescriptor[] = [];
243
262
  for (const refChild of entity.referenceIdentities()) {
@@ -253,7 +272,7 @@ function buildForeignKeys(
253
272
 
254
273
  const fkCols = fkFieldJsNames.map((jsName) => {
255
274
  const fkField = findField(entity, jsName);
256
- return fkField ? resolveColumnName(fkField) : toSnake(jsName);
275
+ return fkField ? resolveColumnName(fkField, strategy) : applyColumnNamingStrategy(jsName, strategy);
257
276
  });
258
277
 
259
278
  // Target columns: prefer explicit multi-field dotted form, else delegate
@@ -261,8 +280,8 @@ function buildForeignKeys(
261
280
  // primary identity → "id" fallback).
262
281
  const explicitTargetFields = refChild.targetFields;
263
282
  const refColumns = explicitTargetFields.length > 1
264
- ? explicitTargetFields.map(toSnake)
265
- : [toSnake(refChild.resolvedTargetPkField(root) ?? "id")];
283
+ ? explicitTargetFields.map((n) => applyColumnNamingStrategy(n, strategy))
284
+ : [applyColumnNamingStrategy(refChild.resolvedTargetPkField(root) ?? "id", strategy)];
266
285
 
267
286
  const { onDelete, onUpdate } = resolveReferentialActions(entity, refChild);
268
287
  const constraintName = `${tableName}_${fkCols[0]}_fk`;
@@ -291,15 +310,17 @@ function buildForeignKeys(
291
310
  * EF OwnsOne pattern: no JSON column for the parent itself; each nested field
292
311
  * becomes `<parent_col>_<nested_col>` in the owning entity's table.
293
312
  */
294
- function flattenObjectField(field: MetaData, root: MetaRoot): ColumnDescriptor[] {
313
+ function flattenObjectField(
314
+ field: MetaData, root: MetaRoot, strategy: ColumnNamingStrategy,
315
+ ): ColumnDescriptor[] {
295
316
  const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
296
317
  if (typeof ref !== "string" || ref.length === 0) return [];
297
318
  const targetObject = root.findObject(ref);
298
319
  if (targetObject === undefined) return [];
299
- const prefix = resolveColumnName(field) + "_";
320
+ const prefix = resolveColumnName(field, strategy) + "_";
300
321
  const cols: ColumnDescriptor[] = [];
301
322
  for (const nested of targetObject.fields()) {
302
- const inner = buildColumn(nested, /* isPk */ false, /* pkGeneration */ undefined);
323
+ const inner = buildColumn(nested, /* isPk */ false, /* pkGeneration */ undefined, strategy);
303
324
  cols.push({ ...inner, name: prefix + inner.name });
304
325
  }
305
326
  return cols;
@@ -317,14 +338,15 @@ function buildColumn(
317
338
  field: MetaData,
318
339
  isPk: boolean,
319
340
  pkGeneration: string | undefined,
341
+ strategy: ColumnNamingStrategy,
320
342
  ): ColumnDescriptor {
321
343
  // Both the @required attr and the validator.required child signal NOT NULL.
322
344
  const fieldIsRequired = isRequired(field);
323
345
  const defaultRaw = field.ownAttr(FIELD_ATTR_DEFAULT);
324
346
 
325
347
  const col: ColumnDescriptor = {
326
- name: resolveColumnName(field),
327
- sqlType: subtypeToSqlType(field.subType),
348
+ name: resolveColumnName(field, strategy),
349
+ sqlType: subtypeToSqlType(field),
328
350
  nullable: !isPk && !fieldIsRequired,
329
351
  };
330
352
 
@@ -345,9 +367,14 @@ function buildColumn(
345
367
  return col;
346
368
  }
347
369
 
348
- function subtypeToSqlType(subType: string): SqlType {
370
+ function subtypeToSqlType(field: MetaData): SqlType {
371
+ const subType = field.subType;
349
372
  switch (subType) {
350
- case FIELD_SUBTYPE_STRING: return { kind: "text" };
373
+ case FIELD_SUBTYPE_STRING: {
374
+ // @maxLength is declared as ATTR_SUBTYPE_INT so the loader coerces it to a number.
375
+ const m = field.ownAttr(FIELD_ATTR_MAX_LENGTH);
376
+ return typeof m === "number" ? { kind: "text", maxLength: m } : { kind: "text" };
377
+ }
351
378
  case FIELD_SUBTYPE_INT:
352
379
  case FIELD_SUBTYPE_SHORT:
353
380
  case FIELD_SUBTYPE_BYTE: return { kind: "integer", bits: 32 };
@@ -366,9 +393,3 @@ function subtypeToSqlType(subType: string): SqlType {
366
393
  }
367
394
  }
368
395
 
369
- function toSnake(s: string): string {
370
- return s
371
- .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
372
- .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
373
- .toLowerCase();
374
- }