@happyvertical/smrt-core 0.42.7 → 0.43.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/AGENTS.md +35 -2
- package/agents/schema-paths.md +37 -15
- package/dist/cascade.d.ts +7 -6
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +19 -21
- package/dist/cascade.js.map +1 -1
- package/dist/class.d.ts +10 -0
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +21 -0
- package/dist/class.js.map +1 -1
- package/dist/decorators/index.d.ts +50 -15
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +23 -11
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +17 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +20 -1
- package/dist/migrations/differ.d.ts +2 -0
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +103 -0
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +26 -2
- package/dist/migrations/generator.js.map +1 -1
- package/dist/migrations/orchestrate.d.ts.map +1 -1
- package/dist/migrations/orchestrate.js +4 -1
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/object.d.ts +4 -4
- package/dist/object.js +4 -4
- package/dist/object.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +6 -0
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +80 -8
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry.d.ts +5 -7
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +6 -8
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +64 -18
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +7 -1
- 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 -0
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts +1 -0
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +23 -1
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/postgres-strategy.js +1 -1
- package/dist/schema/foreign-key-ddl.d.ts +16 -0
- package/dist/schema/foreign-key-ddl.d.ts.map +1 -0
- package/dist/schema/foreign-key-ddl.js +85 -0
- package/dist/schema/foreign-key-ddl.js.map +1 -0
- package/dist/schema/foreign-key-planner.d.ts +18 -0
- package/dist/schema/foreign-key-planner.d.ts.map +1 -0
- package/dist/schema/foreign-key-planner.js +109 -0
- package/dist/schema/foreign-key-planner.js.map +1 -0
- package/dist/schema/foreign-key-policy.d.ts +21 -0
- package/dist/schema/foreign-key-policy.d.ts.map +1 -0
- package/dist/schema/foreign-key-policy.js +41 -0
- package/dist/schema/foreign-key-policy.js.map +1 -0
- package/dist/schema/generator.d.ts +3 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +65 -13
- 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/manifest-schema.d.ts.map +1 -1
- package/dist/schema/manifest-schema.js +21 -3
- package/dist/schema/manifest-schema.js.map +1 -1
- package/dist/schema/schema-aggregator.d.ts +7 -1
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.js +54 -9
- package/dist/schema/schema-aggregator.js.map +1 -1
- package/dist/schema/schema-manager.d.ts +1 -6
- package/dist/schema/schema-manager.d.ts.map +1 -1
- package/dist/schema/schema-manager.js +26 -30
- package/dist/schema/schema-manager.js.map +1 -1
- package/dist/schema/types.d.ts +12 -5
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +1 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +15 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +4 -1
- package/dist/smrt-knowledge.json +18 -8
- package/dist/testing/database.d.ts +11 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +80 -9
- package/dist/testing/database.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { quoteIdentifier } from "./sql-identifiers.js";
|
|
2
|
+
import { foreignKeyConstraintName, renderForeignKeyConstraint, schemaDependenciesForEngine, schemaForeignKeys, schemaForeignKeysForEngine } from "./foreign-key-ddl.js";
|
|
3
|
+
//#region src/schema/foreign-key-planner.ts
|
|
4
|
+
/** Render the inverse of a PostgreSQL deferred ADD CONSTRAINT statement. */
|
|
5
|
+
function renderDeferredForeignKeyDrop(table, foreignKey) {
|
|
6
|
+
return `ALTER TABLE ${quoteIdentifier(table)} DROP CONSTRAINT IF EXISTS ${quoteIdentifier(foreignKeyConstraintName(table, foreignKey))};`;
|
|
7
|
+
}
|
|
8
|
+
function stronglyConnectedComponents(names, dependencies) {
|
|
9
|
+
let nextIndex = 0;
|
|
10
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
11
|
+
const lowLinks = /* @__PURE__ */ new Map();
|
|
12
|
+
const stack = [];
|
|
13
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
14
|
+
const componentByName = /* @__PURE__ */ new Map();
|
|
15
|
+
let component = 0;
|
|
16
|
+
const visit = (name) => {
|
|
17
|
+
indexes.set(name, nextIndex);
|
|
18
|
+
lowLinks.set(name, nextIndex);
|
|
19
|
+
nextIndex++;
|
|
20
|
+
stack.push(name);
|
|
21
|
+
onStack.add(name);
|
|
22
|
+
for (const dependency of dependencies.get(name) || []) if (!indexes.has(dependency)) {
|
|
23
|
+
visit(dependency);
|
|
24
|
+
lowLinks.set(name, Math.min(lowLinks.get(name) ?? 0, lowLinks.get(dependency) ?? 0));
|
|
25
|
+
} else if (onStack.has(dependency)) lowLinks.set(name, Math.min(lowLinks.get(name) ?? 0, indexes.get(dependency) ?? 0));
|
|
26
|
+
if (lowLinks.get(name) !== indexes.get(name)) return;
|
|
27
|
+
while (stack.length > 0) {
|
|
28
|
+
const member = stack.pop();
|
|
29
|
+
onStack.delete(member);
|
|
30
|
+
componentByName.set(member, component);
|
|
31
|
+
if (member === name) break;
|
|
32
|
+
}
|
|
33
|
+
component++;
|
|
34
|
+
};
|
|
35
|
+
for (const name of names) if (!indexes.has(name)) visit(name);
|
|
36
|
+
return componentByName;
|
|
37
|
+
}
|
|
38
|
+
function omitForeignKeys(schema, omitted) {
|
|
39
|
+
if (omitted.size === 0) return schema;
|
|
40
|
+
const columns = Object.fromEntries(Object.entries(schema.columns).map(([name, definition]) => [name, omitted.has(name) ? {
|
|
41
|
+
...definition,
|
|
42
|
+
foreignKey: void 0
|
|
43
|
+
} : definition]));
|
|
44
|
+
return {
|
|
45
|
+
...schema,
|
|
46
|
+
columns,
|
|
47
|
+
foreignKeys: schemaForeignKeys(schema).filter((foreignKey) => !omitted.has(foreignKey.column))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Deterministically order new tables and define safe behavior for FK cycles. */
|
|
51
|
+
function planForeignKeyCreation(input, engine) {
|
|
52
|
+
const byName = new Map([...input].sort((a, b) => a.tableName.localeCompare(b.tableName)).map((schema) => [schema.tableName, schema]));
|
|
53
|
+
const names = [...byName.keys()];
|
|
54
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const [name, schema] of byName) {
|
|
56
|
+
for (const foreignKey of schemaForeignKeysForEngine(schema, engine)) renderForeignKeyConstraint(name, foreignKey);
|
|
57
|
+
dependencies.set(name, Array.from(new Set(schemaDependenciesForEngine(schema, engine).filter((target) => target !== name && byName.has(target)))).sort());
|
|
58
|
+
}
|
|
59
|
+
const components = stronglyConnectedComponents(names, dependencies);
|
|
60
|
+
const componentSizes = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const id of components.values()) componentSizes.set(id, (componentSizes.get(id) || 0) + 1);
|
|
62
|
+
const isMutualCycle = (source, target) => {
|
|
63
|
+
const component = components.get(source);
|
|
64
|
+
return source !== target && component !== void 0 && component === components.get(target) && (componentSizes.get(component) || 0) > 1;
|
|
65
|
+
};
|
|
66
|
+
if (engine === "duckdb" || engine === "json") for (const [source, schema] of byName) for (const foreignKey of schemaForeignKeysForEngine(schema, engine)) {
|
|
67
|
+
if (source === foreignKey.referencesTable) throw new Error(`[DDL:${engine}] Foreign key ${source}.${foreignKey.column} is self-referential; DuckDB cannot insert self-referencing foreign keys safely.`);
|
|
68
|
+
if (foreignKey.onDelete === "CASCADE" || foreignKey.onDelete === "SET NULL") throw new Error(`[DDL:${engine}] Foreign key ${source}.${foreignKey.column} uses ON DELETE ${foreignKey.onDelete}, which DuckDB does not support. Use PostgreSQL/SQLite or keep this relationship app-side only with @crossPackageRef.`);
|
|
69
|
+
if (foreignKey.onUpdate !== void 0) throw new Error(`[DDL:${engine}] Foreign key ${source}.${foreignKey.column} uses ON UPDATE ${foreignKey.onUpdate}, which DuckDB does not support. DuckDB cannot preserve SMRT's ON UPDATE CASCADE contract; use PostgreSQL/SQLite or keep this relationship app-side only with @crossPackageRef.`);
|
|
70
|
+
if (isMutualCycle(source, foreignKey.referencesTable)) throw new Error(`[DDL:${engine}] Foreign-key cycle ${source}.${foreignKey.column} -> ${foreignKey.referencesTable}.${foreignKey.referencesColumn} cannot be created safely because DuckDB does not support ALTER TABLE ADD CONSTRAINT.`);
|
|
71
|
+
}
|
|
72
|
+
const visited = /* @__PURE__ */ new Set();
|
|
73
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
74
|
+
const orderedNames = [];
|
|
75
|
+
const visit = (name) => {
|
|
76
|
+
if (visited.has(name) || visiting.has(name)) return;
|
|
77
|
+
visiting.add(name);
|
|
78
|
+
for (const dependency of dependencies.get(name) || []) visit(dependency);
|
|
79
|
+
visiting.delete(name);
|
|
80
|
+
visited.add(name);
|
|
81
|
+
orderedNames.push(name);
|
|
82
|
+
};
|
|
83
|
+
for (const name of names) visit(name);
|
|
84
|
+
const deferred = [];
|
|
85
|
+
const schemas = orderedNames.map((name) => {
|
|
86
|
+
const schema = byName.get(name);
|
|
87
|
+
if (engine !== "postgres") return schema;
|
|
88
|
+
const omitted = /* @__PURE__ */ new Set();
|
|
89
|
+
for (const foreignKey of schemaForeignKeysForEngine(schema, engine)) if (isMutualCycle(name, foreignKey.referencesTable)) {
|
|
90
|
+
omitted.add(foreignKey.column);
|
|
91
|
+
deferred.push({
|
|
92
|
+
table: name,
|
|
93
|
+
foreignKey
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return omitForeignKeys(schema, omitted);
|
|
97
|
+
});
|
|
98
|
+
deferred.sort((a, b) => `${a.table}.${a.foreignKey.column}`.localeCompare(`${b.table}.${b.foreignKey.column}`));
|
|
99
|
+
return {
|
|
100
|
+
schemas,
|
|
101
|
+
cyclicTables: names.filter((name) => (componentSizes.get(components.get(name) ?? -1) || 0) > 1),
|
|
102
|
+
deferredConstraints: deferred,
|
|
103
|
+
deferredStatements: deferred.map(({ table, foreignKey }) => `ALTER TABLE ${quoteIdentifier(table)} ADD ${renderForeignKeyConstraint(table, foreignKey)};`)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { planForeignKeyCreation, renderDeferredForeignKeyDrop };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=foreign-key-planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-key-planner.js","names":[],"sources":["../../src/schema/foreign-key-planner.ts"],"sourcesContent":["import type { DatabaseEngine } from './ddl/types.js';\nimport {\n foreignKeyConstraintName,\n renderForeignKeyConstraint,\n schemaDependenciesForEngine,\n schemaForeignKeys,\n schemaForeignKeysForEngine,\n} from './foreign-key-ddl.js';\nimport { quoteIdentifier } from './sql-identifiers.js';\nimport type { ForeignKeyDefinition, SchemaDefinition } from './types.js';\n\nexport interface ForeignKeyCreationPlan {\n schemas: SchemaDefinition[];\n deferredStatements: string[];\n /** Tables participating in a multi-table strongly connected component. */\n cyclicTables: string[];\n /** PostgreSQL constraints omitted from CREATE TABLE and added afterward. */\n deferredConstraints: Array<{\n table: string;\n foreignKey: ForeignKeyDefinition;\n }>;\n}\n\n/** Render the inverse of a PostgreSQL deferred ADD CONSTRAINT statement. */\nexport function renderDeferredForeignKeyDrop(\n table: string,\n foreignKey: ForeignKeyDefinition,\n): string {\n return `ALTER TABLE ${quoteIdentifier(table)} DROP CONSTRAINT IF EXISTS ${quoteIdentifier(\n foreignKeyConstraintName(table, foreignKey),\n )};`;\n}\n\nfunction stronglyConnectedComponents(\n names: string[],\n dependencies: Map<string, string[]>,\n): Map<string, number> {\n let nextIndex = 0;\n const indexes = new Map<string, number>();\n const lowLinks = new Map<string, number>();\n const stack: string[] = [];\n const onStack = new Set<string>();\n const componentByName = new Map<string, number>();\n let component = 0;\n\n const visit = (name: string): void => {\n indexes.set(name, nextIndex);\n lowLinks.set(name, nextIndex);\n nextIndex++;\n stack.push(name);\n onStack.add(name);\n\n for (const dependency of dependencies.get(name) || []) {\n if (!indexes.has(dependency)) {\n visit(dependency);\n lowLinks.set(\n name,\n Math.min(lowLinks.get(name) ?? 0, lowLinks.get(dependency) ?? 0),\n );\n } else if (onStack.has(dependency)) {\n lowLinks.set(\n name,\n Math.min(lowLinks.get(name) ?? 0, indexes.get(dependency) ?? 0),\n );\n }\n }\n\n if (lowLinks.get(name) !== indexes.get(name)) return;\n while (stack.length > 0) {\n const member = stack.pop() as string;\n onStack.delete(member);\n componentByName.set(member, component);\n if (member === name) break;\n }\n component++;\n };\n\n for (const name of names) {\n if (!indexes.has(name)) visit(name);\n }\n return componentByName;\n}\n\nfunction omitForeignKeys(\n schema: SchemaDefinition,\n omitted: Set<string>,\n): SchemaDefinition {\n if (omitted.size === 0) return schema;\n const columns = Object.fromEntries(\n Object.entries(schema.columns).map(([name, definition]) => [\n name,\n omitted.has(name) ? { ...definition, foreignKey: undefined } : definition,\n ]),\n );\n return {\n ...schema,\n columns,\n foreignKeys: schemaForeignKeys(schema).filter(\n (foreignKey) => !omitted.has(foreignKey.column),\n ),\n };\n}\n\n/** Deterministically order new tables and define safe behavior for FK cycles. */\nexport function planForeignKeyCreation(\n input: readonly SchemaDefinition[],\n engine: DatabaseEngine,\n): ForeignKeyCreationPlan {\n const byName = new Map(\n [...input]\n .sort((a, b) => a.tableName.localeCompare(b.tableName))\n .map((schema) => [schema.tableName, schema]),\n );\n const names = [...byName.keys()];\n const dependencies = new Map<string, string[]>();\n for (const [name, schema] of byName) {\n for (const foreignKey of schemaForeignKeysForEngine(schema, engine)) {\n // Validate externally supplied manifest actions before a dry-run can\n // report an executable plan.\n renderForeignKeyConstraint(name, foreignKey);\n }\n dependencies.set(\n name,\n Array.from(\n new Set(\n schemaDependenciesForEngine(schema, engine).filter(\n (target) => target !== name && byName.has(target),\n ),\n ),\n ).sort(),\n );\n }\n\n const components = stronglyConnectedComponents(names, dependencies);\n const componentSizes = new Map<number, number>();\n for (const id of components.values()) {\n componentSizes.set(id, (componentSizes.get(id) || 0) + 1);\n }\n const isMutualCycle = (source: string, target: string): boolean => {\n const component = components.get(source);\n return (\n source !== target &&\n component !== undefined &&\n component === components.get(target) &&\n (componentSizes.get(component) || 0) > 1\n );\n };\n\n if (engine === 'duckdb' || engine === 'json') {\n for (const [source, schema] of byName) {\n for (const foreignKey of schemaForeignKeysForEngine(schema, engine)) {\n if (source === foreignKey.referencesTable) {\n throw new Error(\n `[DDL:${engine}] Foreign key ${source}.${foreignKey.column} is self-referential; DuckDB cannot insert self-referencing foreign keys safely.`,\n );\n }\n if (\n foreignKey.onDelete === 'CASCADE' ||\n foreignKey.onDelete === 'SET NULL'\n ) {\n throw new Error(\n `[DDL:${engine}] Foreign key ${source}.${foreignKey.column} uses ON DELETE ${foreignKey.onDelete}, which DuckDB does not support. Use PostgreSQL/SQLite or keep this relationship app-side only with @crossPackageRef.`,\n );\n }\n if (foreignKey.onUpdate !== undefined) {\n throw new Error(\n `[DDL:${engine}] Foreign key ${source}.${foreignKey.column} uses ON UPDATE ${foreignKey.onUpdate}, which DuckDB does not support. DuckDB cannot preserve SMRT's ON UPDATE CASCADE contract; use PostgreSQL/SQLite or keep this relationship app-side only with @crossPackageRef.`,\n );\n }\n if (isMutualCycle(source, foreignKey.referencesTable)) {\n throw new Error(\n `[DDL:${engine}] Foreign-key cycle ${source}.${foreignKey.column} -> ${foreignKey.referencesTable}.${foreignKey.referencesColumn} cannot be created safely because DuckDB does not support ALTER TABLE ADD CONSTRAINT.`,\n );\n }\n }\n }\n }\n\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const orderedNames: string[] = [];\n const visit = (name: string): void => {\n if (visited.has(name) || visiting.has(name)) return;\n visiting.add(name);\n for (const dependency of dependencies.get(name) || []) visit(dependency);\n visiting.delete(name);\n visited.add(name);\n orderedNames.push(name);\n };\n for (const name of names) visit(name);\n\n const deferred: Array<{ table: string; foreignKey: ForeignKeyDefinition }> =\n [];\n const schemas = orderedNames.map((name) => {\n const schema = byName.get(name) as SchemaDefinition;\n if (engine !== 'postgres') return schema;\n const omitted = new Set<string>();\n for (const foreignKey of schemaForeignKeysForEngine(schema, engine)) {\n if (isMutualCycle(name, foreignKey.referencesTable)) {\n omitted.add(foreignKey.column);\n deferred.push({ table: name, foreignKey });\n }\n }\n return omitForeignKeys(schema, omitted);\n });\n\n deferred.sort((a, b) =>\n `${a.table}.${a.foreignKey.column}`.localeCompare(\n `${b.table}.${b.foreignKey.column}`,\n ),\n );\n return {\n schemas,\n cyclicTables: names.filter(\n (name) => (componentSizes.get(components.get(name) ?? -1) || 0) > 1,\n ),\n deferredConstraints: deferred,\n deferredStatements: deferred.map(\n ({ table, foreignKey }) =>\n `ALTER TABLE ${quoteIdentifier(table)} ADD ${renderForeignKeyConstraint(table, foreignKey)};`,\n ),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ForeignKeyAction } from './types.js';
|
|
2
|
+
/** Normalize decorator action spelling into the SQL vocabulary. */
|
|
3
|
+
export declare function normalizeForeignKeyAction(value: unknown): ForeignKeyAction | undefined;
|
|
4
|
+
/** Validate an externally supplied action rather than interpolating raw SQL. */
|
|
5
|
+
export declare function requireForeignKeyAction(value: unknown, context: string): ForeignKeyAction;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the one delete policy shared by app-side cascade and database DDL.
|
|
8
|
+
*
|
|
9
|
+
* A same-package reference that identifies the referencing row defaults to
|
|
10
|
+
* CASCADE. Tenant ids are scope markers rather than ownership edges, and all
|
|
11
|
+
* other references default to SQL NO ACTION.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveForeignKeyDeleteAction(options: {
|
|
14
|
+
declared?: unknown;
|
|
15
|
+
isConflictColumn: boolean;
|
|
16
|
+
isTenantIdField: boolean;
|
|
17
|
+
}): {
|
|
18
|
+
action: ForeignKeyAction;
|
|
19
|
+
declared: boolean;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=foreign-key-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-key-policy.d.ts","sourceRoot":"","sources":["../../src/schema/foreign-key-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AASnD,mEAAmE;AACnE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,gBAAgB,GAAG,SAAS,CAM9B;AAED,gFAAgF;AAChF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,gBAAgB,CAQlB;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAelD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/schema/foreign-key-policy.ts
|
|
2
|
+
var FOREIGN_KEY_ACTIONS = /* @__PURE__ */ new Set([
|
|
3
|
+
"CASCADE",
|
|
4
|
+
"SET NULL",
|
|
5
|
+
"RESTRICT",
|
|
6
|
+
"NO ACTION"
|
|
7
|
+
]);
|
|
8
|
+
/** Normalize decorator action spelling into the SQL vocabulary. */
|
|
9
|
+
function normalizeForeignKeyAction(value) {
|
|
10
|
+
if (typeof value !== "string") return void 0;
|
|
11
|
+
const normalized = value.trim().toUpperCase().replace(/_/g, " ");
|
|
12
|
+
return FOREIGN_KEY_ACTIONS.has(normalized) ? normalized : void 0;
|
|
13
|
+
}
|
|
14
|
+
/** Validate an externally supplied action rather than interpolating raw SQL. */
|
|
15
|
+
function requireForeignKeyAction(value, context) {
|
|
16
|
+
const normalized = normalizeForeignKeyAction(value);
|
|
17
|
+
if (!normalized) throw new Error(`Invalid foreign-key action ${JSON.stringify(value)} for ${context}. Expected CASCADE, SET NULL, RESTRICT, or NO ACTION.`);
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the one delete policy shared by app-side cascade and database DDL.
|
|
22
|
+
*
|
|
23
|
+
* A same-package reference that identifies the referencing row defaults to
|
|
24
|
+
* CASCADE. Tenant ids are scope markers rather than ownership edges, and all
|
|
25
|
+
* other references default to SQL NO ACTION.
|
|
26
|
+
*/
|
|
27
|
+
function resolveForeignKeyDeleteAction(options) {
|
|
28
|
+
const declaredAction = options.declared === void 0 ? void 0 : requireForeignKeyAction(options.declared, "ON DELETE");
|
|
29
|
+
if (declaredAction) return {
|
|
30
|
+
action: declaredAction,
|
|
31
|
+
declared: true
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
action: !options.isTenantIdField && options.isConflictColumn ? "CASCADE" : "NO ACTION",
|
|
35
|
+
declared: false
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { normalizeForeignKeyAction, requireForeignKeyAction, resolveForeignKeyDeleteAction };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=foreign-key-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-key-policy.js","names":[],"sources":["../../src/schema/foreign-key-policy.ts"],"sourcesContent":["import type { ForeignKeyAction } from './types.js';\n\nconst FOREIGN_KEY_ACTIONS = new Set<ForeignKeyAction>([\n 'CASCADE',\n 'SET NULL',\n 'RESTRICT',\n 'NO ACTION',\n]);\n\n/** Normalize decorator action spelling into the SQL vocabulary. */\nexport function normalizeForeignKeyAction(\n value: unknown,\n): ForeignKeyAction | undefined {\n if (typeof value !== 'string') return undefined;\n const normalized = value.trim().toUpperCase().replace(/_/g, ' ');\n return FOREIGN_KEY_ACTIONS.has(normalized as ForeignKeyAction)\n ? (normalized as ForeignKeyAction)\n : undefined;\n}\n\n/** Validate an externally supplied action rather than interpolating raw SQL. */\nexport function requireForeignKeyAction(\n value: unknown,\n context: string,\n): ForeignKeyAction {\n const normalized = normalizeForeignKeyAction(value);\n if (!normalized) {\n throw new Error(\n `Invalid foreign-key action ${JSON.stringify(value)} for ${context}. Expected CASCADE, SET NULL, RESTRICT, or NO ACTION.`,\n );\n }\n return normalized;\n}\n\n/**\n * Resolve the one delete policy shared by app-side cascade and database DDL.\n *\n * A same-package reference that identifies the referencing row defaults to\n * CASCADE. Tenant ids are scope markers rather than ownership edges, and all\n * other references default to SQL NO ACTION.\n */\nexport function resolveForeignKeyDeleteAction(options: {\n declared?: unknown;\n isConflictColumn: boolean;\n isTenantIdField: boolean;\n}): { action: ForeignKeyAction; declared: boolean } {\n const declaredAction =\n options.declared === undefined\n ? undefined\n : requireForeignKeyAction(options.declared, 'ON DELETE');\n if (declaredAction) {\n return { action: declaredAction, declared: true };\n }\n return {\n action:\n !options.isTenantIdField && options.isConflictColumn\n ? 'CASCADE'\n : 'NO ACTION',\n declared: false,\n };\n}\n"],"mappings":";AAEA,IAAM,sCAAsB,IAAI,IAAsB;CACpD;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,0BACd,OAC8B;CAC9B,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,MAAM,GAAG;CAC/D,OAAO,oBAAoB,IAAI,UAA8B,IACxD,aACD,KAAA;AACN;;AAGA,SAAgB,wBACd,OACA,SACkB;CAClB,MAAM,aAAa,0BAA0B,KAAK;CAClD,IAAI,CAAC,YACH,MAAM,IAAI,MACR,8BAA8B,KAAK,UAAU,KAAK,EAAE,OAAO,QAAQ,sDACrE;CAEF,OAAO;AACT;;;;;;;;AASA,SAAgB,8BAA8B,SAIM;CAClD,MAAM,iBACJ,QAAQ,aAAa,KAAA,IACjB,KAAA,IACA,wBAAwB,QAAQ,UAAU,WAAW;CAC3D,IAAI,gBACF,OAAO;EAAE,QAAQ;EAAgB,UAAU;CAAK;CAElD,OAAO;EACL,QACE,CAAC,QAAQ,mBAAmB,QAAQ,mBAChC,YACA;EACN,UAAU;CACZ;AACF"}
|
|
@@ -57,7 +57,9 @@ type SchemaGeneratorConfig = {
|
|
|
57
57
|
};
|
|
58
58
|
getDescendants(baseClassName: string): string[];
|
|
59
59
|
getAllFields(className: string): Promise<Map<string, RegistryField>>;
|
|
60
|
+
getFields?(className: string): Map<string, RegistryField>;
|
|
60
61
|
getSTIBase?(className: string): string | null;
|
|
62
|
+
getTableName?(className: string): string | undefined;
|
|
61
63
|
};
|
|
62
64
|
};
|
|
63
65
|
export declare class SchemaGenerator {
|
|
@@ -71,6 +73,7 @@ export declare class SchemaGenerator {
|
|
|
71
73
|
private shouldEmitDefault;
|
|
72
74
|
private getRegistryTargetIdColumnType;
|
|
73
75
|
private reconcileRegistryForeignKeyColumnTypes;
|
|
76
|
+
private applyRegistryForeignKeys;
|
|
74
77
|
/**
|
|
75
78
|
* Whether an existing index already leads with `leadingColumns`, in order.
|
|
76
79
|
*
|
|
@@ -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,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveForeignKeyDeleteAction } from "./foreign-key-policy.js";
|
|
1
2
|
import { classnameToTablename } from "../utils/naming.js";
|
|
2
3
|
import { formatDefaultValue, quoteIdentifier, quoteStringLiteral } from "./sql-identifiers.js";
|
|
3
4
|
import { assertIdentifierFits, enforceIdentifierLimits } from "./index-utils.js";
|
|
@@ -9,6 +10,10 @@ import { createHash } from "node:crypto";
|
|
|
9
10
|
* Schema generator for SMRT objects
|
|
10
11
|
* Converts AST field definitions to database schema definitions
|
|
11
12
|
*/
|
|
13
|
+
function getForeignKeyConstraintEngines(field) {
|
|
14
|
+
const constraint = field._meta?.constraint;
|
|
15
|
+
return typeof constraint === "object" ? [...constraint.engines] : void 0;
|
|
16
|
+
}
|
|
12
17
|
var SchemaGenerator = class {
|
|
13
18
|
/**
|
|
14
19
|
* Convert field type to SQL data type
|
|
@@ -67,6 +72,45 @@ var SchemaGenerator = class {
|
|
|
67
72
|
};
|
|
68
73
|
}
|
|
69
74
|
}
|
|
75
|
+
applyRegistryForeignKeys(columns, fields, conflictColumns, registry) {
|
|
76
|
+
const conflictSet = new Set(conflictColumns);
|
|
77
|
+
for (const [fieldName, field] of fields.entries()) {
|
|
78
|
+
if (field.type !== "foreignKey" || !field.related) continue;
|
|
79
|
+
const columnName = this.toSnakeCase(fieldName);
|
|
80
|
+
const column = columns[columnName];
|
|
81
|
+
if (!column) continue;
|
|
82
|
+
if (field._meta?.constraint === false) {
|
|
83
|
+
column.foreignKey = void 0;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const isTenantIdField = field.__tenancy?.isTenantIdField === true || field._meta?.__tenancy?.isTenantIdField === true;
|
|
87
|
+
if (isTenantIdField) {
|
|
88
|
+
column.foreignKey = void 0;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const [targetName, declaredTargetColumn] = field.related.split(".");
|
|
92
|
+
const targetBase = registry?.getSTIBase?.(targetName) || targetName;
|
|
93
|
+
const registeredTargetTable = registry?.getTableName?.(targetBase);
|
|
94
|
+
if (registry && !registeredTargetTable) {
|
|
95
|
+
column.foreignKey = void 0;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const targetColumn = declaredTargetColumn || Array.from(registry?.getFields?.(targetBase)?.entries() || []).find(([, targetField]) => targetField._meta?.primaryKey === true)?.[0] || "id";
|
|
99
|
+
const targetTable = registeredTargetTable || this.classNameToTableName(targetBase);
|
|
100
|
+
const { action } = resolveForeignKeyDeleteAction({
|
|
101
|
+
declared: field._meta?.onDelete,
|
|
102
|
+
isConflictColumn: conflictSet.has(columnName),
|
|
103
|
+
isTenantIdField
|
|
104
|
+
});
|
|
105
|
+
column.foreignKey = {
|
|
106
|
+
table: targetTable,
|
|
107
|
+
column: this.toSnakeCase(targetColumn),
|
|
108
|
+
onDelete: action,
|
|
109
|
+
onUpdate: "CASCADE",
|
|
110
|
+
...getForeignKeyConstraintEngines(field) ? { engines: getForeignKeyConstraintEngines(field) } : {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
70
114
|
/**
|
|
71
115
|
* Whether an existing index already leads with `leadingColumns`, in order.
|
|
72
116
|
*
|
|
@@ -483,7 +527,8 @@ var SchemaGenerator = class {
|
|
|
483
527
|
referencesTable: columnDef.foreignKey.table,
|
|
484
528
|
referencesColumn: columnDef.foreignKey.column,
|
|
485
529
|
onDelete: columnDef.foreignKey.onDelete,
|
|
486
|
-
onUpdate: columnDef.foreignKey.onUpdate
|
|
530
|
+
onUpdate: columnDef.foreignKey.onUpdate,
|
|
531
|
+
...columnDef.foreignKey.engines ? { engines: columnDef.foreignKey.engines } : {}
|
|
487
532
|
});
|
|
488
533
|
return foreignKeys;
|
|
489
534
|
}
|
|
@@ -570,14 +615,15 @@ var SchemaGenerator = class {
|
|
|
570
615
|
description: field._meta?.description
|
|
571
616
|
};
|
|
572
617
|
if (field._meta?.default !== void 0 && this.shouldEmitDefault(field, field._meta.default)) columnDef.defaultValue = field._meta.default;
|
|
573
|
-
if (field.type === "foreignKey") {
|
|
618
|
+
if (field.type === "foreignKey" && field._meta?.constraint !== false && field._meta?.__tenancy?.isTenantIdField !== true) {
|
|
574
619
|
const relatedName = field.related;
|
|
575
620
|
const onDeleteAction = field._meta?.onDelete;
|
|
576
621
|
if (relatedName) columnDef.foreignKey = {
|
|
577
622
|
table: this.classNameToTableName(relatedName),
|
|
578
623
|
column: "id",
|
|
579
624
|
onDelete: onDeleteAction || "CASCADE",
|
|
580
|
-
onUpdate: "CASCADE"
|
|
625
|
+
onUpdate: "CASCADE",
|
|
626
|
+
...getForeignKeyConstraintEngines(field) ? { engines: getForeignKeyConstraintEngines(field) } : {}
|
|
581
627
|
};
|
|
582
628
|
}
|
|
583
629
|
if ((field._meta?.indexed === true || field.indexed === true) && !columnDef.foreignKey && !columnDef.unique && !columnDef.primaryKey) indexedColumns.add(this.toSnakeCase(fieldName));
|
|
@@ -596,9 +642,11 @@ var SchemaGenerator = class {
|
|
|
596
642
|
description: "Last update timestamp"
|
|
597
643
|
};
|
|
598
644
|
this.reconcileRegistryForeignKeyColumnTypes(columns, fields, config?.registry);
|
|
645
|
+
const resolvedConflict = this.resolveConflictTarget("cti", columns, config);
|
|
646
|
+
this.applyRegistryForeignKeys(columns, fields, resolvedConflict.conflictColumns, config?.registry);
|
|
599
647
|
const indexes = [];
|
|
600
648
|
if (!hasCustomPK) {
|
|
601
|
-
const { conflictColumns, tenantColumn } =
|
|
649
|
+
const { conflictColumns, tenantColumn } = resolvedConflict;
|
|
602
650
|
if (!this.conflictColumnsArePrimaryKey(conflictColumns, columns)) indexes.push({
|
|
603
651
|
name: conflictIndexName(tableName, conflictColumns, tenantColumn),
|
|
604
652
|
columns: conflictColumns,
|
|
@@ -623,7 +671,7 @@ var SchemaGenerator = class {
|
|
|
623
671
|
indexes,
|
|
624
672
|
triggers: [],
|
|
625
673
|
foreignKeys: this.extractForeignKeys(columns),
|
|
626
|
-
dependencies:
|
|
674
|
+
dependencies: this.extractForeignKeys(columns).map((foreignKey) => foreignKey.referencesTable).filter((dependency) => dependency !== tableName),
|
|
627
675
|
version: createHash("sha256").update(JSON.stringify(columns)).digest("hex").substring(0, 8),
|
|
628
676
|
packageName: "runtime",
|
|
629
677
|
baseClass: "SmrtObject"
|
|
@@ -755,14 +803,15 @@ var SchemaGenerator = class {
|
|
|
755
803
|
description: field._meta?.description
|
|
756
804
|
};
|
|
757
805
|
if (field._meta?.default !== void 0 && this.shouldEmitDefault(field, field._meta.default)) columnDef.defaultValue = field._meta.default;
|
|
758
|
-
if (field.type === "foreignKey") {
|
|
806
|
+
if (field.type === "foreignKey" && field._meta?.constraint !== false && field._meta?.__tenancy?.isTenantIdField !== true) {
|
|
759
807
|
const relatedName = field.related;
|
|
760
808
|
const onDeleteAction = field._meta?.onDelete;
|
|
761
809
|
if (relatedName) columnDef.foreignKey = {
|
|
762
810
|
table: this.classNameToTableName(relatedName),
|
|
763
811
|
column: "id",
|
|
764
812
|
onDelete: onDeleteAction || "CASCADE",
|
|
765
|
-
onUpdate: "CASCADE"
|
|
813
|
+
onUpdate: "CASCADE",
|
|
814
|
+
...getForeignKeyConstraintEngines(field) ? { engines: getForeignKeyConstraintEngines(field) } : {}
|
|
766
815
|
};
|
|
767
816
|
}
|
|
768
817
|
if ((field._meta?.indexed === true || field.indexed === true) && !columnDef.foreignKey) indexedStiColumns.add(columnName);
|
|
@@ -781,9 +830,11 @@ var SchemaGenerator = class {
|
|
|
781
830
|
defaultValue: "current_timestamp",
|
|
782
831
|
description: "Last update timestamp"
|
|
783
832
|
};
|
|
833
|
+
const resolvedConflict = this.resolveConflictTarget("sti", columns, config);
|
|
784
834
|
for (const className of allClassNames) {
|
|
785
835
|
const classFields = await ObjectRegistry.getAllFields(className);
|
|
786
836
|
this.reconcileRegistryForeignKeyColumnTypes(columns, classFields, ObjectRegistry);
|
|
837
|
+
this.applyRegistryForeignKeys(columns, classFields, resolvedConflict.conflictColumns, ObjectRegistry);
|
|
787
838
|
}
|
|
788
839
|
const indexes = [];
|
|
789
840
|
this.emitStiConflictIndex(indexes, columns, tableName, config);
|
|
@@ -819,7 +870,7 @@ var SchemaGenerator = class {
|
|
|
819
870
|
indexes,
|
|
820
871
|
triggers: [],
|
|
821
872
|
foreignKeys: this.extractForeignKeys(columns),
|
|
822
|
-
dependencies:
|
|
873
|
+
dependencies: this.extractForeignKeys(columns).map((foreignKey) => foreignKey.referencesTable).filter((dependency) => dependency !== tableName),
|
|
823
874
|
version: createHash("sha256").update(JSON.stringify({
|
|
824
875
|
columns,
|
|
825
876
|
baseClassName,
|
|
@@ -949,8 +1000,8 @@ var SchemaGenerator = class {
|
|
|
949
1000
|
jsonPath: idx.jsonPath
|
|
950
1001
|
})),
|
|
951
1002
|
triggers: [],
|
|
952
|
-
foreignKeys:
|
|
953
|
-
dependencies:
|
|
1003
|
+
foreignKeys: this.extractForeignKeys(this.convertManifestColumnsToSchemaColumns(columns)),
|
|
1004
|
+
dependencies: this.extractForeignKeys(this.convertManifestColumnsToSchemaColumns(columns)).map((foreignKey) => foreignKey.referencesTable).filter((dependency) => dependency !== tableName),
|
|
954
1005
|
version: "",
|
|
955
1006
|
packageName: ""
|
|
956
1007
|
};
|
|
@@ -1046,8 +1097,8 @@ var SchemaGenerator = class {
|
|
|
1046
1097
|
jsonPath: idx.jsonPath
|
|
1047
1098
|
})),
|
|
1048
1099
|
triggers: [],
|
|
1049
|
-
foreignKeys:
|
|
1050
|
-
dependencies:
|
|
1100
|
+
foreignKeys: this.extractForeignKeys(this.convertManifestColumnsToSchemaColumns(columns)),
|
|
1101
|
+
dependencies: this.extractForeignKeys(this.convertManifestColumnsToSchemaColumns(columns)).map((foreignKey) => foreignKey.referencesTable).filter((dependency) => dependency !== tableName),
|
|
1051
1102
|
version: "",
|
|
1052
1103
|
packageName: ""
|
|
1053
1104
|
};
|
|
@@ -1108,7 +1159,8 @@ var SchemaGenerator = class {
|
|
|
1108
1159
|
referenceKind: col.referenceKind,
|
|
1109
1160
|
notNull: col.notNull,
|
|
1110
1161
|
unique: col.unique,
|
|
1111
|
-
defaultValue: col.default
|
|
1162
|
+
defaultValue: col.default,
|
|
1163
|
+
foreignKey: col.foreignKey ? { ...col.foreignKey } : void 0
|
|
1112
1164
|
};
|
|
1113
1165
|
return columns;
|
|
1114
1166
|
}
|