@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.
- package/AGENTS.md +29 -4
- package/README.md +20 -1
- package/agents/query-bounds.md +45 -0
- package/agents/schema-paths.md +328 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +28 -4
- package/dist/class.js.map +1 -1
- package/dist/collection.d.ts +101 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +224 -43
- package/dist/collection.js.map +1 -1
- package/dist/database.d.ts +28 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +16 -8
- package/dist/database.js.map +1 -1
- package/dist/db-errors.d.ts +105 -0
- package/dist/db-errors.d.ts.map +1 -0
- package/dist/db-errors.js +382 -0
- package/dist/db-errors.js.map +1 -0
- package/dist/decorators/index.d.ts +17 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +65 -7
- package/dist/decorators/index.js.map +1 -1
- package/dist/dispatch/bus.d.ts.map +1 -1
- package/dist/dispatch/bus.js +4 -3
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/errors.d.ts +30 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +34 -2
- package/dist/errors.js.map +1 -1
- package/dist/generators/changes-route.d.ts.map +1 -1
- package/dist/generators/changes-route.js +6 -3
- package/dist/generators/changes-route.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +8 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +38 -4
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +16 -0
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +41 -3
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts +22 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +34 -3
- package/dist/generators/rest.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +202 -8
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +604 -46
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts +31 -4
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +49 -5
- package/dist/migrations/generator.js.map +1 -1
- package/dist/migrations/index.d.ts +3 -2
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +5 -3
- package/dist/migrations/orchestrate.js +35 -6
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/migrations/sqlite-rebuild.d.ts +142 -0
- package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
- package/dist/migrations/sqlite-rebuild.js +514 -0
- package/dist/migrations/sqlite-rebuild.js.map +1 -0
- package/dist/migrations/tracker.d.ts +114 -1
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +331 -16
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/migrations/types.d.ts +19 -4
- package/dist/migrations/types.d.ts.map +1 -1
- package/dist/migrations.js +5 -3
- package/dist/object.d.ts +45 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +60 -13
- package/dist/object.js.map +1 -1
- package/dist/postgres-timeouts.d.ts +240 -0
- package/dist/postgres-timeouts.d.ts.map +1 -0
- package/dist/postgres-timeouts.js +204 -0
- package/dist/postgres-timeouts.js.map +1 -0
- package/dist/query-bounds.d.ts +97 -0
- package/dist/query-bounds.d.ts.map +1 -0
- package/dist/query-bounds.js +173 -0
- package/dist/query-bounds.js.map +1 -0
- package/dist/registry/types.d.ts +32 -1
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/scanner/types.d.ts +5 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.js +2 -2
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/generator.d.ts +70 -5
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +124 -6
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/live-parity.d.ts +90 -0
- package/dist/schema/live-parity.d.ts.map +1 -0
- package/dist/schema/live-parity.js +600 -0
- package/dist/schema/live-parity.js.map +1 -0
- package/dist/schema/manifest-schema.d.ts +121 -0
- package/dist/schema/manifest-schema.d.ts.map +1 -0
- package/dist/schema/manifest-schema.js +267 -0
- package/dist/schema/manifest-schema.js.map +1 -0
- package/dist/schema/schema-aggregator.d.ts +24 -10
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.js +35 -90
- package/dist/schema/schema-aggregator.js.map +1 -1
- package/dist/schema/system-table-shapes.d.ts +52 -0
- package/dist/schema/system-table-shapes.d.ts.map +1 -0
- package/dist/schema/system-table-shapes.js +174 -0
- package/dist/schema/system-table-shapes.js.map +1 -0
- package/dist/schema/types.d.ts +103 -4
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +1 -0
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +3 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +4 -1
- package/dist/smrt-knowledge.json +18 -6
- package/dist/sync/apply.d.ts.map +1 -1
- package/dist/sync/apply.js +9 -16
- package/dist/sync/apply.js.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +1 -0
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +97 -13
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +6 -4
- package/dist/vite-plugin/web-collections.js.map +1 -1
- 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
|
|
236
|
+
* Generate the CREATE TABLE statement for a schema definition.
|
|
178
237
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
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
|
-
* @
|
|
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":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EAGT,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAMrD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}
|
package/dist/schema/generator.js
CHANGED
|
@@ -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
|
|
27
|
-
version
|
|
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
|
|
966
|
+
* Generate the CREATE TABLE statement for a schema definition.
|
|
855
967
|
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
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
|
-
* @
|
|
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);
|