@happyvertical/smrt-core 0.40.68 → 0.40.70

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.
Files changed (144) hide show
  1. package/AGENTS.md +29 -4
  2. package/README.md +20 -1
  3. package/agents/query-bounds.md +45 -0
  4. package/agents/schema-paths.md +328 -0
  5. package/dist/browser.d.ts +1 -0
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +2 -1
  8. package/dist/class.d.ts.map +1 -1
  9. package/dist/class.js +28 -4
  10. package/dist/class.js.map +1 -1
  11. package/dist/collection.d.ts +101 -2
  12. package/dist/collection.d.ts.map +1 -1
  13. package/dist/collection.js +224 -43
  14. package/dist/collection.js.map +1 -1
  15. package/dist/database.d.ts +28 -0
  16. package/dist/database.d.ts.map +1 -1
  17. package/dist/database.js +16 -8
  18. package/dist/database.js.map +1 -1
  19. package/dist/db-errors.d.ts +105 -0
  20. package/dist/db-errors.d.ts.map +1 -0
  21. package/dist/db-errors.js +382 -0
  22. package/dist/db-errors.js.map +1 -0
  23. package/dist/decorators/index.d.ts +17 -1
  24. package/dist/decorators/index.d.ts.map +1 -1
  25. package/dist/decorators/index.js +65 -7
  26. package/dist/decorators/index.js.map +1 -1
  27. package/dist/dispatch/bus.d.ts.map +1 -1
  28. package/dist/dispatch/bus.js +4 -3
  29. package/dist/dispatch/bus.js.map +1 -1
  30. package/dist/errors.d.ts +30 -2
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +34 -2
  33. package/dist/errors.js.map +1 -1
  34. package/dist/generators/changes-route.d.ts.map +1 -1
  35. package/dist/generators/changes-route.js +6 -3
  36. package/dist/generators/changes-route.js.map +1 -1
  37. package/dist/generators/mcp-runtime-template.d.ts +8 -0
  38. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  39. package/dist/generators/mcp-runtime-template.js +38 -4
  40. package/dist/generators/mcp-runtime-template.js.map +1 -1
  41. package/dist/generators/mcp.d.ts +16 -0
  42. package/dist/generators/mcp.d.ts.map +1 -1
  43. package/dist/generators/mcp.js +41 -3
  44. package/dist/generators/mcp.js.map +1 -1
  45. package/dist/generators/rest.d.ts +22 -0
  46. package/dist/generators/rest.d.ts.map +1 -1
  47. package/dist/generators/rest.js +34 -3
  48. package/dist/generators/rest.js.map +1 -1
  49. package/dist/index.d.ts +6 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +8 -3
  52. package/dist/manifest/static-manifest.js +1 -1
  53. package/dist/manifest/static-manifest.js.map +1 -1
  54. package/dist/manifest/store.js +1 -1
  55. package/dist/manifest.json +1 -1
  56. package/dist/migrations/differ.d.ts +202 -8
  57. package/dist/migrations/differ.d.ts.map +1 -1
  58. package/dist/migrations/differ.js +604 -46
  59. package/dist/migrations/differ.js.map +1 -1
  60. package/dist/migrations/generator.d.ts +31 -4
  61. package/dist/migrations/generator.d.ts.map +1 -1
  62. package/dist/migrations/generator.js +49 -5
  63. package/dist/migrations/generator.js.map +1 -1
  64. package/dist/migrations/index.d.ts +3 -2
  65. package/dist/migrations/index.d.ts.map +1 -1
  66. package/dist/migrations/index.js +5 -3
  67. package/dist/migrations/orchestrate.js +35 -6
  68. package/dist/migrations/orchestrate.js.map +1 -1
  69. package/dist/migrations/sqlite-rebuild.d.ts +142 -0
  70. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
  71. package/dist/migrations/sqlite-rebuild.js +514 -0
  72. package/dist/migrations/sqlite-rebuild.js.map +1 -0
  73. package/dist/migrations/tracker.d.ts +114 -1
  74. package/dist/migrations/tracker.d.ts.map +1 -1
  75. package/dist/migrations/tracker.js +331 -16
  76. package/dist/migrations/tracker.js.map +1 -1
  77. package/dist/migrations/types.d.ts +19 -4
  78. package/dist/migrations/types.d.ts.map +1 -1
  79. package/dist/migrations.js +5 -3
  80. package/dist/object.d.ts +45 -6
  81. package/dist/object.d.ts.map +1 -1
  82. package/dist/object.js +60 -13
  83. package/dist/object.js.map +1 -1
  84. package/dist/postgres-timeouts.d.ts +240 -0
  85. package/dist/postgres-timeouts.d.ts.map +1 -0
  86. package/dist/postgres-timeouts.js +204 -0
  87. package/dist/postgres-timeouts.js.map +1 -0
  88. package/dist/query-bounds.d.ts +97 -0
  89. package/dist/query-bounds.d.ts.map +1 -0
  90. package/dist/query-bounds.js +173 -0
  91. package/dist/query-bounds.js.map +1 -0
  92. package/dist/registry/types.d.ts +32 -1
  93. package/dist/registry/types.d.ts.map +1 -1
  94. package/dist/registry.js +1 -1
  95. package/dist/scanner/types.d.ts +5 -0
  96. package/dist/scanner/types.d.ts.map +1 -1
  97. package/dist/scanner/types.js.map +1 -1
  98. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  99. package/dist/schema/ddl/base-strategy.js +2 -2
  100. package/dist/schema/ddl/base-strategy.js.map +1 -1
  101. package/dist/schema/generator.d.ts +70 -5
  102. package/dist/schema/generator.d.ts.map +1 -1
  103. package/dist/schema/generator.js +124 -6
  104. package/dist/schema/generator.js.map +1 -1
  105. package/dist/schema/index.d.ts +3 -0
  106. package/dist/schema/index.d.ts.map +1 -1
  107. package/dist/schema/index.js +4 -1
  108. package/dist/schema/live-parity.d.ts +90 -0
  109. package/dist/schema/live-parity.d.ts.map +1 -0
  110. package/dist/schema/live-parity.js +600 -0
  111. package/dist/schema/live-parity.js.map +1 -0
  112. package/dist/schema/manifest-schema.d.ts +121 -0
  113. package/dist/schema/manifest-schema.d.ts.map +1 -0
  114. package/dist/schema/manifest-schema.js +267 -0
  115. package/dist/schema/manifest-schema.js.map +1 -0
  116. package/dist/schema/schema-aggregator.d.ts +24 -10
  117. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  118. package/dist/schema/schema-aggregator.js +35 -90
  119. package/dist/schema/schema-aggregator.js.map +1 -1
  120. package/dist/schema/system-table-shapes.d.ts +52 -0
  121. package/dist/schema/system-table-shapes.d.ts.map +1 -0
  122. package/dist/schema/system-table-shapes.js +174 -0
  123. package/dist/schema/system-table-shapes.js.map +1 -0
  124. package/dist/schema/types.d.ts +103 -4
  125. package/dist/schema/types.d.ts.map +1 -1
  126. package/dist/schema/utils.d.ts +1 -0
  127. package/dist/schema/utils.d.ts.map +1 -1
  128. package/dist/schema/utils.js +3 -1
  129. package/dist/schema/utils.js.map +1 -1
  130. package/dist/schema.js +4 -1
  131. package/dist/smrt-knowledge.json +18 -6
  132. package/dist/sync/apply.d.ts.map +1 -1
  133. package/dist/sync/apply.js +9 -16
  134. package/dist/sync/apply.js.map +1 -1
  135. package/dist/testing/database.d.ts.map +1 -1
  136. package/dist/testing/database.js +1 -0
  137. package/dist/testing/database.js.map +1 -1
  138. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  139. package/dist/vite-plugin/sveltekit-generator.js +97 -13
  140. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  141. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  142. package/dist/vite-plugin/web-collections.js +6 -4
  143. package/dist/vite-plugin/web-collections.js.map +1 -1
  144. package/package.json +11 -11
@@ -1,6 +1,6 @@
1
1
  import { FieldDefinition, FieldMeta, ManifestSchema, SmartObjectDefinition, SmartObjectManifest } from '../scanner/types.js';
2
2
  import { DatabaseEngine } from './ddl/types.js';
3
- import { SchemaDefinition } from './types.js';
3
+ import { DeclaredIndexDefinition, SchemaDefinition } from './types.js';
4
4
  /**
5
5
  * Structural shape of a field as read from either the build-time manifest
6
6
  * (`FieldDefinition`) or the runtime ObjectRegistry (`getAllFields()` returns
@@ -27,6 +27,15 @@ interface RegistryField {
27
27
  type SchemaGeneratorConfig = {
28
28
  conflictColumns?: string[];
29
29
  idType?: 'uuid' | 'text';
30
+ /**
31
+ * `@smrt({ indexes: [...] })` declarations (#2357).
32
+ *
33
+ * The manifest paths receive the whole `decoratorConfig`, so this arrives for
34
+ * free there. The runtime paths rebuild a narrow config bag by hand
35
+ * (`schema/utils.ts`, `testing/database.ts`) and must thread it explicitly —
36
+ * omitting it is what made the option unreachable at runtime.
37
+ */
38
+ indexes?: DeclaredIndexDefinition[];
30
39
  registry?: {
31
40
  getConfig?(className: string): {
32
41
  idType?: 'uuid' | 'text';
@@ -55,6 +64,56 @@ export declare class SchemaGenerator {
55
64
  * Generate column definitions
56
65
  */
57
66
  private generateColumns;
67
+ /**
68
+ * Append `@smrt({ indexes: [...] })` declarations to a generated index set
69
+ * (#2357).
70
+ *
71
+ * Shared by every schema path — build-time AST, runtime registry CTI/STI, and
72
+ * manifest CTI/STI — so one declaration behaves identically however the schema
73
+ * was derived. Call it *before* `ensureTenantIdIndex`: a declared composite
74
+ * leading with `tenant_id` is exactly what that helper's "already leading"
75
+ * check is meant to defer to, and appending afterwards would leave the table
76
+ * with a redundant standalone tenant index (#2384).
77
+ *
78
+ * Nothing is dropped quietly. A column that resolves to no column on the
79
+ * table, a malformed entry, and a name collision with a different index are
80
+ * all hard errors — a silently missing index is a production performance bug
81
+ * that surfaces only under load.
82
+ *
83
+ * @param indexes - Generated index set, appended in place
84
+ * @param declared - The object's `indexes` config, from any config source
85
+ * @param columns - Emitted columns, keyed by column name
86
+ * @param tableName - Table the indexes belong to (for error messages)
87
+ */
88
+ private appendDeclaredIndexes;
89
+ /** Narrow one raw `indexes[]` entry, rejecting anything unusable. */
90
+ private validateDeclaredIndex;
91
+ /**
92
+ * Resolve a declared index column to an emitted column name.
93
+ *
94
+ * Declarations name SMRT fields, but only the runtime/manifest paths
95
+ * snake_case them into columns — the build-time AST path keys columns by the
96
+ * field name itself. Prefer an exact column hit, then the snake_case form, and
97
+ * otherwise return the input unchanged so the caller reports it as unknown.
98
+ */
99
+ private resolveDeclaredIndexColumn;
100
+ /** Whether two index definitions describe the same database object. */
101
+ private sameIndexTarget;
102
+ /**
103
+ * Ensure a tenancy-injected `tenant_id` column has an index leading with it.
104
+ *
105
+ * Tenancy injects the column but nothing indexed it: the generated set covers
106
+ * foreign keys, unique columns, `updated_at` and the STI discriminator, and
107
+ * `tenant_id` is in none of those. Every tenant-scoped read filters on it, so
108
+ * without an index each one scans the whole multi-tenant table — measured in
109
+ * one production database, 164 of 212 tenant-scoped tables had no such index
110
+ * (#2356).
111
+ *
112
+ * "Leading with it" rather than "always add": a table that already has a
113
+ * composite index starting on `tenant_id` (commonly from `conflictColumns`)
114
+ * is already served, and a standalone duplicate would only cost writes.
115
+ */
116
+ private ensureTenantIdIndex;
58
117
  /**
59
118
  * Generate index definitions
60
119
  */
@@ -174,13 +233,19 @@ export declare class SchemaGenerator {
174
233
  */
175
234
  private toSnakeCase;
176
235
  /**
177
- * Generate SQL CREATE TABLE statement from schema definition
236
+ * Generate the CREATE TABLE statement for a schema definition.
178
237
  *
179
- * This is the single source of truth for SQL generation, consolidating
180
- * logic that was previously duplicated across multiple code paths.
238
+ * With an `engine` this delegates to that engine's DDL strategy. Without
239
+ * one it renders the engine-neutral preview stored in `schema.ddl` and
240
+ * `manifest.json`: abstract SQL types, no indexes, no triggers. That
241
+ * preview is NOT an executable representation of the table (#2358) —
242
+ * executable paths (`db:migrate`, `MigrationGenerator`, `SchemaAggregator`,
243
+ * `createIsolatedTestDbFromManifest`) render `schema.columns` and
244
+ * `schema.indexes` through `getDDLStrategy(engine)` instead.
181
245
  *
182
246
  * @param schema - Schema definition object
183
- * @returns SQL CREATE TABLE statement with indexes
247
+ * @param engine - Optional target engine; omit for the neutral preview
248
+ * @returns SQL CREATE TABLE statement (no indexes)
184
249
  */
185
250
  generateSQL(schema: SchemaDefinition, engine?: DatabaseEngine): string;
186
251
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}
@@ -17,14 +17,17 @@ var SchemaGenerator = class {
17
17
  const indexes = this.generateIndexes(objectDef, columns);
18
18
  const triggers = this.generateTriggers(objectDef, tableName);
19
19
  const foreignKeys = this.extractForeignKeys(columns);
20
+ const dependencies = this.extractDependencies(objectDef, foreignKeys);
21
+ const version = this.generateVersion(objectDef);
22
+ this.ensureTenantIdIndex(indexes, columns, tableName);
20
23
  return {
21
24
  tableName,
22
25
  columns,
23
26
  indexes,
24
27
  triggers,
25
28
  foreignKeys,
26
- dependencies: this.extractDependencies(objectDef, foreignKeys),
27
- version: this.generateVersion(objectDef),
29
+ dependencies,
30
+ version,
28
31
  packageName: this.extractPackageName(objectDef.filePath),
29
32
  baseClass: objectDef.extends
30
33
  };
@@ -138,6 +141,106 @@ var SchemaGenerator = class {
138
141
  return columns;
139
142
  }
140
143
  /**
144
+ * Append `@smrt({ indexes: [...] })` declarations to a generated index set
145
+ * (#2357).
146
+ *
147
+ * Shared by every schema path — build-time AST, runtime registry CTI/STI, and
148
+ * manifest CTI/STI — so one declaration behaves identically however the schema
149
+ * was derived. Call it *before* `ensureTenantIdIndex`: a declared composite
150
+ * leading with `tenant_id` is exactly what that helper's "already leading"
151
+ * check is meant to defer to, and appending afterwards would leave the table
152
+ * with a redundant standalone tenant index (#2384).
153
+ *
154
+ * Nothing is dropped quietly. A column that resolves to no column on the
155
+ * table, a malformed entry, and a name collision with a different index are
156
+ * all hard errors — a silently missing index is a production performance bug
157
+ * that surfaces only under load.
158
+ *
159
+ * @param indexes - Generated index set, appended in place
160
+ * @param declared - The object's `indexes` config, from any config source
161
+ * @param columns - Emitted columns, keyed by column name
162
+ * @param tableName - Table the indexes belong to (for error messages)
163
+ */
164
+ appendDeclaredIndexes(indexes, declared, columns, tableName) {
165
+ if (declared === void 0 || declared === null) return;
166
+ if (!Array.isArray(declared)) throw new Error(`@smrt({ indexes }) on "${tableName}" must be an array of index declarations.`);
167
+ for (const entry of declared) {
168
+ const spec = this.validateDeclaredIndex(entry, tableName);
169
+ const mappedColumns = spec.columns.map((column) => this.resolveDeclaredIndexColumn(column, columns));
170
+ const unknownColumns = mappedColumns.filter((column) => !Object.hasOwn(columns, column));
171
+ if (unknownColumns.length > 0) throw new Error(`Declared index "${spec.name}" on "${tableName}" references unknown column(s): ${unknownColumns.join(", ")}. Declared index columns must name a field or column on the object.`);
172
+ const index = {
173
+ name: spec.name,
174
+ columns: mappedColumns,
175
+ ...spec.unique === true ? { unique: true } : {},
176
+ ...spec.where !== void 0 ? { where: spec.where } : {}
177
+ };
178
+ const existing = indexes.find((other) => other.name === index.name);
179
+ if (existing) {
180
+ if (this.sameIndexTarget(existing, index)) continue;
181
+ throw new Error(`Declared index "${index.name}" on "${tableName}" collides with a different index of the same name. Rename the declared index.`);
182
+ }
183
+ indexes.push(index);
184
+ }
185
+ }
186
+ /** Narrow one raw `indexes[]` entry, rejecting anything unusable. */
187
+ validateDeclaredIndex(entry, tableName) {
188
+ const spec = entry;
189
+ const describe = () => {
190
+ try {
191
+ return JSON.stringify(entry) ?? String(entry);
192
+ } catch {
193
+ return String(entry);
194
+ }
195
+ };
196
+ if (typeof spec !== "object" || spec === null || Array.isArray(spec)) throw new Error(`Declared index on "${tableName}" must be an object: got ${describe()}.`);
197
+ if (typeof spec.name !== "string" || spec.name.length === 0) throw new Error(`Declared index on "${tableName}" needs a non-empty "name": got ${describe()}.`);
198
+ if (!Array.isArray(spec.columns) || spec.columns.length === 0 || !spec.columns.every((column) => typeof column === "string" && column.length > 0)) throw new Error(`Declared index "${spec.name}" on "${tableName}" needs a non-empty "columns" array of field or column names.`);
199
+ if (spec.unique !== void 0 && typeof spec.unique !== "boolean") throw new Error(`Declared index "${spec.name}" on "${tableName}" has a non-boolean "unique": got ${describe()}.`);
200
+ if (spec.where !== void 0 && (typeof spec.where !== "string" || spec.where.trim().length === 0)) throw new Error(`Declared index "${spec.name}" on "${tableName}" has an empty or non-string "where" predicate.`);
201
+ return spec;
202
+ }
203
+ /**
204
+ * Resolve a declared index column to an emitted column name.
205
+ *
206
+ * Declarations name SMRT fields, but only the runtime/manifest paths
207
+ * snake_case them into columns — the build-time AST path keys columns by the
208
+ * field name itself. Prefer an exact column hit, then the snake_case form, and
209
+ * otherwise return the input unchanged so the caller reports it as unknown.
210
+ */
211
+ resolveDeclaredIndexColumn(name, columns) {
212
+ if (Object.hasOwn(columns, name)) return name;
213
+ const snakeCased = this.toSnakeCase(name);
214
+ return Object.hasOwn(columns, snakeCased) ? snakeCased : name;
215
+ }
216
+ /** Whether two index definitions describe the same database object. */
217
+ sameIndexTarget(a, b) {
218
+ return a.columns.length === b.columns.length && a.columns.every((column, i) => column === b.columns[i]) && (a.unique ?? false) === (b.unique ?? false) && (a.where ?? null) === (b.where ?? null);
219
+ }
220
+ /**
221
+ * Ensure a tenancy-injected `tenant_id` column has an index leading with it.
222
+ *
223
+ * Tenancy injects the column but nothing indexed it: the generated set covers
224
+ * foreign keys, unique columns, `updated_at` and the STI discriminator, and
225
+ * `tenant_id` is in none of those. Every tenant-scoped read filters on it, so
226
+ * without an index each one scans the whole multi-tenant table — measured in
227
+ * one production database, 164 of 212 tenant-scoped tables had no such index
228
+ * (#2356).
229
+ *
230
+ * "Leading with it" rather than "always add": a table that already has a
231
+ * composite index starting on `tenant_id` (commonly from `conflictColumns`)
232
+ * is already served, and a standalone duplicate would only cost writes.
233
+ */
234
+ ensureTenantIdIndex(indexes, columns, tableName) {
235
+ const tenantColumn = Object.entries(columns).find(([, columnDef]) => columnDef?.referenceKind === "tenantId")?.[0];
236
+ if (!tenantColumn) return;
237
+ if (indexes.some((index) => index.columns?.[0] === tenantColumn)) return;
238
+ indexes.push({
239
+ name: `${tableName}_${tenantColumn}_idx`,
240
+ columns: [tenantColumn]
241
+ });
242
+ }
243
+ /**
141
244
  * Generate index definitions
142
245
  */
143
246
  generateIndexes(objectDef, columns) {
@@ -159,6 +262,7 @@ var SchemaGenerator = class {
159
262
  unique: true,
160
263
  description: `Unique index for ${columnName}`
161
264
  });
265
+ this.appendDeclaredIndexes(indexes, objectDef.decoratorConfig?.indexes, columns, tableName);
162
266
  return indexes;
163
267
  }
164
268
  /**
@@ -362,6 +466,8 @@ var SchemaGenerator = class {
362
466
  columns: [colName],
363
467
  description: `Index for ${colName}`
364
468
  });
469
+ this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);
470
+ this.ensureTenantIdIndex(indexes, columns, tableName);
365
471
  return {
366
472
  tableName,
367
473
  columns,
@@ -560,6 +666,8 @@ var SchemaGenerator = class {
560
666
  columns: [colName],
561
667
  description: `Index for ${colName}`
562
668
  });
669
+ this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);
670
+ this.ensureTenantIdIndex(indexes, columns, tableName);
563
671
  return {
564
672
  tableName,
565
673
  columns,
@@ -683,6 +791,8 @@ var SchemaGenerator = class {
683
791
  name: `${tableName}_${colName}_idx`,
684
792
  columns: [colName]
685
793
  });
794
+ this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);
795
+ this.ensureTenantIdIndex(indexes, columns, tableName);
686
796
  const schemaDefinition = {
687
797
  tableName,
688
798
  columns: this.convertManifestColumnsToSchemaColumns(columns),
@@ -779,6 +889,8 @@ var SchemaGenerator = class {
779
889
  name: `${tableName}_${colName}_idx`,
780
890
  columns: [colName]
781
891
  });
892
+ this.appendDeclaredIndexes(indexes, config?.indexes, columns, tableName);
893
+ this.ensureTenantIdIndex(indexes, columns, tableName);
782
894
  const schemaDefinition = {
783
895
  tableName,
784
896
  columns: this.convertManifestColumnsToSchemaColumns(columns),
@@ -851,13 +963,19 @@ var SchemaGenerator = class {
851
963
  return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
852
964
  }
853
965
  /**
854
- * Generate SQL CREATE TABLE statement from schema definition
966
+ * Generate the CREATE TABLE statement for a schema definition.
855
967
  *
856
- * This is the single source of truth for SQL generation, consolidating
857
- * logic that was previously duplicated across multiple code paths.
968
+ * With an `engine` this delegates to that engine's DDL strategy. Without
969
+ * one it renders the engine-neutral preview stored in `schema.ddl` and
970
+ * `manifest.json`: abstract SQL types, no indexes, no triggers. That
971
+ * preview is NOT an executable representation of the table (#2358) —
972
+ * executable paths (`db:migrate`, `MigrationGenerator`, `SchemaAggregator`,
973
+ * `createIsolatedTestDbFromManifest`) render `schema.columns` and
974
+ * `schema.indexes` through `getDDLStrategy(engine)` instead.
858
975
  *
859
976
  * @param schema - Schema definition object
860
- * @returns SQL CREATE TABLE statement with indexes
977
+ * @param engine - Optional target engine; omit for the neutral preview
978
+ * @returns SQL CREATE TABLE statement (no indexes)
861
979
  */
862
980
  generateSQL(schema, engine) {
863
981
  if (engine) return getDDLStrategy(engine).generateCreateTable(schema);