@happyvertical/smrt-core 0.40.15 → 0.40.16
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/dist/browser.js +2 -2
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +21 -4
- package/dist/change-feed.js.map +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +5 -3
- package/dist/class.js.map +1 -1
- package/dist/dispatch/bus.d.ts.map +1 -1
- package/dist/dispatch/bus.js +11 -6
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/manifest/static-manifest.js +2 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
- package/dist/manifest/test-manifest-stub.js +132 -2
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/migrations/backfill-tracker.d.ts +1 -0
- package/dist/migrations/backfill-tracker.d.ts.map +1 -1
- package/dist/migrations/backfill-tracker.js +9 -3
- package/dist/migrations/backfill-tracker.js.map +1 -1
- package/dist/migrations/differ.d.ts +18 -0
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +28 -5
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts +8 -0
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +5 -1
- package/dist/migrations/generator.js.map +1 -1
- package/dist/migrations/orchestrate.d.ts +10 -0
- package/dist/migrations/orchestrate.d.ts.map +1 -1
- package/dist/migrations/orchestrate.js +8 -2
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +4 -2
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +13 -6
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +54 -15
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry.d.ts +3 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -4
- package/dist/registry.js.map +1 -1
- package/dist/schema/code-generator.js +123 -0
- package/dist/schema/code-generator.js.map +1 -0
- 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/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +2 -3
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/index.js +26 -2
- package/dist/schema/ddl/index.js.map +1 -1
- package/dist/schema/ddl/materialize-manifest.d.ts +36 -0
- package/dist/schema/ddl/materialize-manifest.d.ts.map +1 -0
- package/dist/schema/ddl/materialize-manifest.js +361 -0
- package/dist/schema/ddl/materialize-manifest.js.map +1 -0
- package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/postgres-strategy.js +1 -1
- package/dist/schema/ddl/postgres-strategy.js.map +1 -1
- package/dist/schema/index.js +12 -0
- package/dist/schema/override-system.js +205 -0
- package/dist/schema/override-system.js.map +1 -0
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.js +261 -0
- package/dist/schema/schema-aggregator.js.map +1 -0
- package/dist/schema/schema-manager.js +11 -1
- package/dist/schema/schema-manager.js.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 +2 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +13 -0
- package/dist/smrt-knowledge.json +5 -4
- package/dist/system/compatibility.d.ts +25 -0
- package/dist/system/compatibility.d.ts.map +1 -1
- package/dist/system/compatibility.js +61 -1
- package/dist/system/compatibility.js.map +1 -1
- package/dist/system/index.js +2 -2
- package/dist/system/schema.d.ts +17 -9
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +25 -11
- package/dist/system/schema.js.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +5 -2
- package/dist/testing/database.js.map +1 -1
- package/package.json +10 -5
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//#region src/schema/code-generator.ts
|
|
2
|
+
var SchemaCodeGenerator = class {
|
|
3
|
+
/**
|
|
4
|
+
* Generate getSchema() method source code
|
|
5
|
+
*/
|
|
6
|
+
generateSchemaMethod(_objectDef, schema) {
|
|
7
|
+
return `${this.generateImports()}\n\n${this.generateMethod(schema)}`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generate complete schema file with all methods
|
|
11
|
+
*/
|
|
12
|
+
generateSchemaFile(schemas) {
|
|
13
|
+
return `${this.generateImports()}\n\n${Object.entries(schemas).map(([className, schema]) => this.generateClassMethod(className, schema)).join("\n\n")}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate import statements
|
|
17
|
+
*/
|
|
18
|
+
generateImports() {
|
|
19
|
+
return `import type { SchemaDefinition } from '@happyvertical/smrt-core';`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate getSchema method for a specific class
|
|
23
|
+
*/
|
|
24
|
+
generateClassMethod(className, schema) {
|
|
25
|
+
return `export class ${className}Schema {
|
|
26
|
+
static getSchema(): SchemaDefinition {
|
|
27
|
+
return ${this.generateSchemaObject(schema)};
|
|
28
|
+
}
|
|
29
|
+
}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate standalone getSchema method
|
|
33
|
+
*/
|
|
34
|
+
generateMethod(schema) {
|
|
35
|
+
return `static getSchema(): SchemaDefinition {
|
|
36
|
+
return ${this.generateSchemaObject(schema)};
|
|
37
|
+
}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate schema object definition
|
|
41
|
+
*/
|
|
42
|
+
generateSchemaObject(schema) {
|
|
43
|
+
return `{
|
|
44
|
+
tableName: ${JSON.stringify(schema.tableName)},
|
|
45
|
+
columns: ${this.generateColumns(schema.columns)},
|
|
46
|
+
indexes: ${this.generateIndexes(schema.indexes)},
|
|
47
|
+
triggers: ${this.generateTriggers(schema.triggers)},
|
|
48
|
+
foreignKeys: ${this.generateForeignKeys(schema.foreignKeys)},
|
|
49
|
+
dependencies: ${JSON.stringify(schema.dependencies)},
|
|
50
|
+
version: ${JSON.stringify(schema.version)},
|
|
51
|
+
packageName: ${JSON.stringify(schema.packageName)},
|
|
52
|
+
baseClass: ${JSON.stringify(schema.baseClass)},
|
|
53
|
+
}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate columns object
|
|
57
|
+
*/
|
|
58
|
+
generateColumns(columns) {
|
|
59
|
+
return `{
|
|
60
|
+
${Object.entries(columns).map(([name, def]) => {
|
|
61
|
+
const defStr = JSON.stringify(def, null, 6).replace(/^/gm, " ");
|
|
62
|
+
return ` ${JSON.stringify(name)}: ${defStr}`;
|
|
63
|
+
}).join(",\n")}
|
|
64
|
+
}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate indexes array
|
|
68
|
+
*/
|
|
69
|
+
generateIndexes(indexes) {
|
|
70
|
+
if (indexes.length === 0) return "[]";
|
|
71
|
+
return `[
|
|
72
|
+
${indexes.map((idx) => JSON.stringify(idx, null, 4).replace(/^/gm, " ")).join(",\n")}
|
|
73
|
+
]`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate triggers array
|
|
77
|
+
*/
|
|
78
|
+
generateTriggers(triggers) {
|
|
79
|
+
if (triggers.length === 0) return "[]";
|
|
80
|
+
return `[
|
|
81
|
+
${triggers.map((trigger) => JSON.stringify(trigger, null, 4).replace(/^/gm, " ")).join(",\n")}
|
|
82
|
+
]`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Generate foreign keys array
|
|
86
|
+
*/
|
|
87
|
+
generateForeignKeys(foreignKeys) {
|
|
88
|
+
if (foreignKeys.length === 0) return "[]";
|
|
89
|
+
return `[
|
|
90
|
+
${foreignKeys.map((fk) => JSON.stringify(fk, null, 4).replace(/^/gm, " ")).join(",\n")}
|
|
91
|
+
]`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Generate TypeScript type definition file
|
|
95
|
+
*/
|
|
96
|
+
generateTypeDefinitions(schemas) {
|
|
97
|
+
return `import type { SchemaDefinition } from '@happyvertical/smrt-core/schema';\n\n${Object.keys(schemas).map((className) => `export interface ${className}Schema {
|
|
98
|
+
getSchema(): SchemaDefinition;
|
|
99
|
+
}`).join("\n\n")}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Generate schema manifest file
|
|
103
|
+
*/
|
|
104
|
+
generateManifest(packageName, schemas) {
|
|
105
|
+
const manifest = {
|
|
106
|
+
version: "1.0.0",
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
packageName,
|
|
109
|
+
schemas: Object.fromEntries(Object.entries(schemas).map(([className, schema]) => [className, {
|
|
110
|
+
tableName: schema.tableName,
|
|
111
|
+
version: schema.version,
|
|
112
|
+
dependencies: schema.dependencies,
|
|
113
|
+
packageName: schema.packageName
|
|
114
|
+
}])),
|
|
115
|
+
dependencies: Array.from(new Set(Object.values(schemas).flatMap((s) => s.dependencies)))
|
|
116
|
+
};
|
|
117
|
+
return `export default ${JSON.stringify(manifest, null, 2)};`;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
//#endregion
|
|
121
|
+
export { SchemaCodeGenerator };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=code-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-generator.js","names":[],"sources":["../../src/schema/code-generator.ts"],"sourcesContent":["/**\n * Code generator for SMRT schema methods\n * Generates getSchema() static methods for SMRT objects\n */\n\nimport type { SmartObjectDefinition } from '../scanner/types.js';\nimport type {\n ColumnDefinition,\n ForeignKeyDefinition,\n IndexDefinition,\n SchemaDefinition,\n TriggerDefinition,\n} from './types.js';\n\nexport class SchemaCodeGenerator {\n /**\n * Generate getSchema() method source code\n */\n generateSchemaMethod(\n _objectDef: SmartObjectDefinition,\n schema: SchemaDefinition,\n ): string {\n const imports = this.generateImports();\n const method = this.generateMethod(schema);\n\n return `${imports}\\n\\n${method}`;\n }\n\n /**\n * Generate complete schema file with all methods\n */\n generateSchemaFile(schemas: Record<string, SchemaDefinition>): string {\n const imports = this.generateImports();\n const methods = Object.entries(schemas)\n .map(([className, schema]) => this.generateClassMethod(className, schema))\n .join('\\n\\n');\n\n return `${imports}\\n\\n${methods}`;\n }\n\n /**\n * Generate import statements\n */\n private generateImports(): string {\n return `import type { SchemaDefinition } from '@happyvertical/smrt-core';`;\n }\n\n /**\n * Generate getSchema method for a specific class\n */\n private generateClassMethod(\n className: string,\n schema: SchemaDefinition,\n ): string {\n const methodBody = this.generateSchemaObject(schema);\n\n return `export class ${className}Schema {\n static getSchema(): SchemaDefinition {\n return ${methodBody};\n }\n}`;\n }\n\n /**\n * Generate standalone getSchema method\n */\n private generateMethod(schema: SchemaDefinition): string {\n const methodBody = this.generateSchemaObject(schema);\n\n return `static getSchema(): SchemaDefinition {\n return ${methodBody};\n}`;\n }\n\n /**\n * Generate schema object definition\n */\n private generateSchemaObject(schema: SchemaDefinition): string {\n return `{\n tableName: ${JSON.stringify(schema.tableName)},\n columns: ${this.generateColumns(schema.columns)},\n indexes: ${this.generateIndexes(schema.indexes)},\n triggers: ${this.generateTriggers(schema.triggers)},\n foreignKeys: ${this.generateForeignKeys(schema.foreignKeys)},\n dependencies: ${JSON.stringify(schema.dependencies)},\n version: ${JSON.stringify(schema.version)},\n packageName: ${JSON.stringify(schema.packageName)},\n baseClass: ${JSON.stringify(schema.baseClass)},\n }`;\n }\n\n /**\n * Generate columns object\n */\n private generateColumns(columns: Record<string, ColumnDefinition>): string {\n const entries = Object.entries(columns).map(([name, def]) => {\n const defStr = JSON.stringify(def, null, 6).replace(/^/gm, ' ');\n return ` ${JSON.stringify(name)}: ${defStr}`;\n });\n\n return `{\n${entries.join(',\\n')}\n }`;\n }\n\n /**\n * Generate indexes array\n */\n private generateIndexes(indexes: IndexDefinition[]): string {\n if (indexes.length === 0) return '[]';\n\n const indexStrings = indexes.map((idx) =>\n JSON.stringify(idx, null, 4).replace(/^/gm, ' '),\n );\n\n return `[\n${indexStrings.join(',\\n')}\n ]`;\n }\n\n /**\n * Generate triggers array\n */\n private generateTriggers(triggers: TriggerDefinition[]): string {\n if (triggers.length === 0) return '[]';\n\n const triggerStrings = triggers.map((trigger) =>\n JSON.stringify(trigger, null, 4).replace(/^/gm, ' '),\n );\n\n return `[\n${triggerStrings.join(',\\n')}\n ]`;\n }\n\n /**\n * Generate foreign keys array\n */\n private generateForeignKeys(foreignKeys: ForeignKeyDefinition[]): string {\n if (foreignKeys.length === 0) return '[]';\n\n const fkStrings = foreignKeys.map((fk) =>\n JSON.stringify(fk, null, 4).replace(/^/gm, ' '),\n );\n\n return `[\n${fkStrings.join(',\\n')}\n ]`;\n }\n\n /**\n * Generate TypeScript type definition file\n */\n generateTypeDefinitions(schemas: Record<string, SchemaDefinition>): string {\n const imports = `import type { SchemaDefinition } from '@happyvertical/smrt-core/schema';`;\n\n const interfaces = Object.keys(schemas)\n .map(\n (className) =>\n `export interface ${className}Schema {\n getSchema(): SchemaDefinition;\n}`,\n )\n .join('\\n\\n');\n\n return `${imports}\\n\\n${interfaces}`;\n }\n\n /**\n * Generate schema manifest file\n */\n generateManifest(\n packageName: string,\n schemas: Record<string, SchemaDefinition>,\n ): string {\n const manifest = {\n version: '1.0.0',\n timestamp: Date.now(),\n packageName,\n schemas: Object.fromEntries(\n Object.entries(schemas).map(([className, schema]) => [\n className,\n {\n tableName: schema.tableName,\n version: schema.version,\n dependencies: schema.dependencies,\n packageName: schema.packageName,\n },\n ]),\n ),\n dependencies: Array.from(\n new Set(Object.values(schemas).flatMap((s) => s.dependencies)),\n ),\n };\n\n return `export default ${JSON.stringify(manifest, null, 2)};`;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-strategy.d.ts","sourceRoot":"","sources":["../../../src/schema/ddl/base-strategy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA4B9D;;;;;GAKG;AACH,8BAAsB,eAAgB,YAAW,WAAW;IAC1D,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEzC;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM;IAuBrD;;;;OAIG;IACH,wBAAwB,CACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,GAC1B,MAAM;
|
|
1
|
+
{"version":3,"file":"base-strategy.d.ts","sourceRoot":"","sources":["../../../src/schema/ddl/base-strategy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA4B9D;;;;;GAKG;AACH,8BAAsB,eAAgB,YAAW,WAAW;IAC1D,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEzC;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM;IAuBrD;;;;OAIG;IACH,wBAAwB,CACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,gBAAgB,GAC1B,MAAM;IAyCT;;;OAGG;IACH,SAAS,CAAC,+BAA+B,CACvC,OAAO,EAAE,eAAe,EAAE,GACzB,MAAM,EAAE;IA8BX;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE;IAwDnD;;;;;OAKG;IACH,SAAS,CAAC,6BAA6B,CACrC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,MAAM;IAKT;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE;IAsBpD;;;OAGG;IACH,SAAS,CAAC,wBAAwB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,iBAAiB,GACzB,MAAM;IAcT;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IA2BlC;;;;;;;;;OASG;IACH,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM;IAS7D;;;OAGG;IACH,SAAS,CAAC,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAItD;;;OAGG;IACH,gBAAgB,IAAI,OAAO;IAI3B;;;OAGG;IACH,oBAAoB,IAAI,OAAO;CAGhC"}
|
|
@@ -55,7 +55,7 @@ var BaseDDLStrategy = class {
|
|
|
55
55
|
const parts = [quoteIdentifier(columnName), this.mapType(columnDef.type)];
|
|
56
56
|
if (columnDef.primaryKey) parts.push("PRIMARY KEY");
|
|
57
57
|
if (columnDef.notNull && !columnDef.primaryKey) parts.push("NOT NULL");
|
|
58
|
-
if (columnDef.unique && !
|
|
58
|
+
if (columnDef.unique && !columnDef.primaryKey) parts.push("UNIQUE");
|
|
59
59
|
if (columnDef.defaultValue !== void 0) {
|
|
60
60
|
const formatted = this.formatDefaultValue(columnDef.defaultValue, columnDef.type);
|
|
61
61
|
parts.push(`DEFAULT ${formatted}`);
|
|
@@ -72,7 +72,7 @@ var BaseDDLStrategy = class {
|
|
|
72
72
|
if (!indexes || !Array.isArray(indexes)) return constraints;
|
|
73
73
|
for (const index of indexes) {
|
|
74
74
|
if (!index?.columns || !Array.isArray(index.columns)) continue;
|
|
75
|
-
if (index.unique && index.columns.length > 0) {
|
|
75
|
+
if (index.unique && !index.where && !index.jsonPath && index.columns.length > 0) {
|
|
76
76
|
const columns = index.columns.map((c) => quoteIdentifier(c)).join(", ");
|
|
77
77
|
constraints.push(`UNIQUE(${columns})`);
|
|
78
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-strategy.js","names":[],"sources":["../../../src/schema/ddl/base-strategy.ts"],"sourcesContent":["/**\n * Base DDL Strategy - Shared logic for all database engines\n *\n * Provides common DDL generation logic that engine-specific strategies\n * can override for their particular requirements.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport {\n formatDefaultValue as formatDefaultValueShared,\n isSafeIdentifier,\n isSafeIdentifierPath,\n quoteIdentifier,\n quoteStringLiteral,\n} from '../sql-identifiers.js';\nimport type {\n ColumnDefinition,\n IndexDefinition,\n SchemaDefinition,\n SQLDataType,\n TriggerDefinition,\n} from '../types.js';\nimport type { DatabaseEngine, DDLStrategy } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Validate the column + JSON path used to build a JSON-path index expression.\n *\n * The path segment is embedded as a SQL string literal inside a dialect\n * function/operator (`json_extract(\"col\", '$.path')` / `\"col\"->>'path'`), and\n * the column as a delimited identifier. We escape both, but also reject paths\n * or columns that aren't simple (dotted) identifiers so a malformed `@meta`\n * field name can't smuggle structure into the expression even after escaping.\n * These names are developer-controlled build-time inputs, so an invalid one is\n * a programming error and throwing is the safest, loudest outcome.\n */\nfunction assertSafeJsonPathTarget(jsonColumn: string, path: string): void {\n if (!isSafeIdentifier(jsonColumn)) {\n throw new Error(\n `[DDL] Unsafe JSON-path index column \"${jsonColumn}\": must be a simple identifier`,\n );\n }\n if (!isSafeIdentifierPath(path)) {\n throw new Error(\n `[DDL] Unsafe JSON-path index path \"${path}\": must be a simple (dotted) identifier`,\n );\n }\n}\n\n/**\n * Abstract base class for DDL strategies\n *\n * Implements common DDL generation patterns. Engine-specific strategies\n * extend this class and override methods as needed.\n */\nexport abstract class BaseDDLStrategy implements DDLStrategy {\n abstract readonly engine: DatabaseEngine;\n\n /**\n * Generate CREATE TABLE statement\n */\n generateCreateTable(schema: SchemaDefinition): string {\n const { tableName, columns, indexes = [] } = schema;\n\n let sql = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)} (\\n`;\n\n // Generate column definitions\n const columnDefs: string[] = [];\n for (const [columnName, columnDef] of Object.entries(columns)) {\n columnDefs.push(this.generateColumnDefinition(columnName, columnDef));\n }\n\n // Add inline UNIQUE constraints for engines that require them\n if (this.requiresInlineUnique() && indexes.length > 0) {\n const uniqueConstraints = this.generateInlineUniqueConstraints(indexes);\n columnDefs.push(...uniqueConstraints);\n }\n\n sql += columnDefs.map((def) => ` ${def}`).join(',\\n');\n sql += '\\n);';\n\n return sql;\n }\n\n /**\n * Generate a single column definition\n *\n * Public so SchemaManager can reuse it for ALTER TABLE ADD COLUMN.\n */\n generateColumnDefinition(\n columnName: string,\n columnDef: ColumnDefinition,\n ): string {\n const parts: string[] = [\n quoteIdentifier(columnName),\n this.mapType(columnDef.type),\n ];\n\n // Primary key\n if (columnDef.primaryKey) {\n parts.push('PRIMARY KEY');\n }\n\n // NOT NULL (skip for primary key - it's implicit)\n if (columnDef.notNull && !columnDef.primaryKey) {\n parts.push('NOT NULL');\n }\n\n // UNIQUE (for single-column unique, not composite)\n // Skip if engine requires inline unique - those are handled separately\n if (columnDef.unique && !this.requiresInlineUnique()) {\n parts.push('UNIQUE');\n }\n\n // DEFAULT value\n if (columnDef.defaultValue !== undefined) {\n const formatted = this.formatDefaultValue(\n columnDef.defaultValue,\n columnDef.type,\n );\n parts.push(`DEFAULT ${formatted}`);\n }\n\n // CHECK constraint\n if (columnDef.check) {\n parts.push(`CHECK (${columnDef.check})`);\n }\n\n return parts.join(' ');\n }\n\n /**\n * Generate inline UNIQUE constraints for composite indexes\n * Used by engines that require inline UNIQUE for UPSERT to work (DuckDB)\n */\n protected generateInlineUniqueConstraints(\n indexes: IndexDefinition[],\n ): string[] {\n const constraints: string[] = [];\n\n if (!indexes || !Array.isArray(indexes)) {\n return constraints;\n }\n\n for (const index of indexes) {\n // Skip malformed index entries\n if (!index?.columns || !Array.isArray(index.columns)) {\n continue;\n }\n\n if (index.unique && index.columns.length > 0) {\n const columns = index.columns.map((c) => quoteIdentifier(c)).join(', ');\n constraints.push(`UNIQUE(${columns})`);\n }\n }\n\n return constraints;\n }\n\n /**\n * Generate CREATE INDEX statements\n * Override in engine-specific strategies if needed\n */\n generateIndexes(schema: SchemaDefinition): string[] {\n const { tableName, indexes = [] } = schema;\n const statements: string[] = [];\n\n if (!indexes || indexes.length === 0) {\n return statements;\n }\n\n for (const index of indexes) {\n // Narrow the jsonPath target up-front so the formatter call doesn't\n // need optional-chained args (and so the malformed-entry check has a\n // single source of truth).\n const jsonPath =\n index?.jsonPath?.column && index.jsonPath.path ? index.jsonPath : null;\n if (\n !index ||\n (!jsonPath &&\n (!index.columns ||\n !Array.isArray(index.columns) ||\n index.columns.length === 0))\n ) {\n logger.warn(`[DDL] Skipping malformed index: ${JSON.stringify(index)}`);\n continue;\n }\n\n // Skip UNIQUE indexes if engine requires them inline\n if (index.unique && this.requiresInlineUnique()) {\n continue;\n }\n\n const indexType = index.unique ? 'UNIQUE INDEX' : 'INDEX';\n\n // JSON-path indexes use a dialect-specific expression\n const target = jsonPath\n ? `(${this.formatJsonPathIndexExpression(\n jsonPath.column,\n jsonPath.path,\n )})`\n : index.columns.map((c) => quoteIdentifier(c)).join(', ');\n\n let sql = `CREATE ${indexType} IF NOT EXISTS ${quoteIdentifier(\n index.name,\n )} ON ${quoteIdentifier(tableName)} (${target})`;\n\n // Partial index condition\n if (index.where) {\n sql += ` WHERE ${index.where}`;\n }\n\n sql += ';';\n statements.push(sql);\n }\n\n return statements;\n }\n\n /**\n * Render the SQL expression used to index a JSON path inside a JSONB column.\n * Subclasses override for dialect-specific syntax.\n *\n * Default (ANSI-ish): `<jsonColumn>->>'<path>'` — works on Postgres.\n */\n protected formatJsonPathIndexExpression(\n jsonColumn: string,\n path: string,\n ): string {\n assertSafeJsonPathTarget(jsonColumn, path);\n return `${quoteIdentifier(jsonColumn)}->>${quoteStringLiteral(path)}`;\n }\n\n /**\n * Generate CREATE TRIGGER statements\n * Override in engine-specific strategies - DuckDB returns empty\n */\n generateTriggers(schema: SchemaDefinition): string[] {\n if (!this.supportsTriggers()) {\n return [];\n }\n\n const { tableName, triggers = [] } = schema;\n const statements: string[] = [];\n\n if (!triggers || triggers.length === 0) {\n return statements;\n }\n\n for (const trigger of triggers) {\n const sql = this.generateTriggerStatement(tableName, trigger);\n if (sql) {\n statements.push(sql);\n }\n }\n\n return statements;\n }\n\n /**\n * Generate a single trigger statement\n * Can be overridden for engine-specific trigger syntax\n */\n protected generateTriggerStatement(\n tableName: string,\n trigger: TriggerDefinition,\n ): string {\n // Default SQLite-style trigger syntax\n let sql = `CREATE TRIGGER IF NOT EXISTS ${quoteIdentifier(trigger.name)}\\n`;\n sql += `${trigger.when} ${trigger.event} ON ${quoteIdentifier(tableName)}\\n`;\n\n if (trigger.condition) {\n sql += `WHEN ${trigger.condition}\\n`;\n }\n\n sql += `BEGIN\\n${trigger.body}\\nEND;`;\n\n return sql;\n }\n\n /**\n * Map abstract SQL type to engine-specific type\n * Default implementation - override for engine-specific types\n */\n mapType(type: SQLDataType): string {\n // Default mapping works for most engines\n switch (type) {\n case 'TEXT':\n return 'TEXT';\n case 'INTEGER':\n return 'INTEGER';\n case 'REAL':\n return 'REAL';\n case 'BLOB':\n return 'BLOB';\n case 'BOOLEAN':\n return 'BOOLEAN';\n case 'JSON':\n return 'JSON';\n case 'TIMESTAMP':\n return 'TIMESTAMP';\n case 'UUID':\n // Fallback for engines without a native uuid type (e.g. SQLite):\n // store as TEXT. PostgreSQL/DuckDB override this with their native\n // uuid type. (R11)\n return 'TEXT';\n default:\n return 'TEXT';\n }\n }\n\n /**\n * Format default value for SQL.\n *\n * Delegates to the shared, injection-safe `formatDefaultValue`\n * (`schema/sql-identifiers.ts`) so every DDL path uses one set of rules:\n * an allowlist of SQL keyword/function defaults (not \"contains `(`\"),\n * type-driven literal quoting, and no folding of the string `\"null\"` into\n * the SQL NULL keyword. Boolean rendering is bridged through\n * `formatBooleanDefault` so engine overrides (SQLite → 0/1) still apply.\n */\n formatDefaultValue(value: unknown, type: SQLDataType): string {\n return formatDefaultValueShared(value, type, {\n booleanLiterals: [\n this.formatBooleanDefault(true),\n this.formatBooleanDefault(false),\n ],\n });\n }\n\n /**\n * Format boolean default\n * Override for engines that use INTEGER (SQLite)\n */\n protected formatBooleanDefault(value: boolean): string {\n return value ? 'TRUE' : 'FALSE';\n }\n\n /**\n * Whether this engine supports triggers\n * Override in DuckDB strategy to return false\n */\n supportsTriggers(): boolean {\n return true;\n }\n\n /**\n * Whether UNIQUE constraints must be inline for UPSERT\n * Override in DuckDB strategy to return true\n */\n requiresInlineUnique(): boolean {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-strategy.js","names":[],"sources":["../../../src/schema/ddl/base-strategy.ts"],"sourcesContent":["/**\n * Base DDL Strategy - Shared logic for all database engines\n *\n * Provides common DDL generation logic that engine-specific strategies\n * can override for their particular requirements.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport {\n formatDefaultValue as formatDefaultValueShared,\n isSafeIdentifier,\n isSafeIdentifierPath,\n quoteIdentifier,\n quoteStringLiteral,\n} from '../sql-identifiers.js';\nimport type {\n ColumnDefinition,\n IndexDefinition,\n SchemaDefinition,\n SQLDataType,\n TriggerDefinition,\n} from '../types.js';\nimport type { DatabaseEngine, DDLStrategy } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Validate the column + JSON path used to build a JSON-path index expression.\n *\n * The path segment is embedded as a SQL string literal inside a dialect\n * function/operator (`json_extract(\"col\", '$.path')` / `\"col\"->>'path'`), and\n * the column as a delimited identifier. We escape both, but also reject paths\n * or columns that aren't simple (dotted) identifiers so a malformed `@meta`\n * field name can't smuggle structure into the expression even after escaping.\n * These names are developer-controlled build-time inputs, so an invalid one is\n * a programming error and throwing is the safest, loudest outcome.\n */\nfunction assertSafeJsonPathTarget(jsonColumn: string, path: string): void {\n if (!isSafeIdentifier(jsonColumn)) {\n throw new Error(\n `[DDL] Unsafe JSON-path index column \"${jsonColumn}\": must be a simple identifier`,\n );\n }\n if (!isSafeIdentifierPath(path)) {\n throw new Error(\n `[DDL] Unsafe JSON-path index path \"${path}\": must be a simple (dotted) identifier`,\n );\n }\n}\n\n/**\n * Abstract base class for DDL strategies\n *\n * Implements common DDL generation patterns. Engine-specific strategies\n * extend this class and override methods as needed.\n */\nexport abstract class BaseDDLStrategy implements DDLStrategy {\n abstract readonly engine: DatabaseEngine;\n\n /**\n * Generate CREATE TABLE statement\n */\n generateCreateTable(schema: SchemaDefinition): string {\n const { tableName, columns, indexes = [] } = schema;\n\n let sql = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)} (\\n`;\n\n // Generate column definitions\n const columnDefs: string[] = [];\n for (const [columnName, columnDef] of Object.entries(columns)) {\n columnDefs.push(this.generateColumnDefinition(columnName, columnDef));\n }\n\n // Add inline UNIQUE constraints for engines that require them\n if (this.requiresInlineUnique() && indexes.length > 0) {\n const uniqueConstraints = this.generateInlineUniqueConstraints(indexes);\n columnDefs.push(...uniqueConstraints);\n }\n\n sql += columnDefs.map((def) => ` ${def}`).join(',\\n');\n sql += '\\n);';\n\n return sql;\n }\n\n /**\n * Generate a single column definition\n *\n * Public so SchemaManager can reuse it for ALTER TABLE ADD COLUMN.\n */\n generateColumnDefinition(\n columnName: string,\n columnDef: ColumnDefinition,\n ): string {\n const parts: string[] = [\n quoteIdentifier(columnName),\n this.mapType(columnDef.type),\n ];\n\n // Primary key\n if (columnDef.primaryKey) {\n parts.push('PRIMARY KEY');\n }\n\n // NOT NULL (skip for primary key - it's implicit)\n if (columnDef.notNull && !columnDef.primaryKey) {\n parts.push('NOT NULL');\n }\n\n // Column-level UNIQUE is already inline and must be preserved for every\n // engine. Some published manifests express single-column uniqueness only\n // on the column definition (without a duplicate IndexDefinition), so\n // suppressing it for DuckDB/JSON would silently weaken the schema.\n if (columnDef.unique && !columnDef.primaryKey) {\n parts.push('UNIQUE');\n }\n\n // DEFAULT value\n if (columnDef.defaultValue !== undefined) {\n const formatted = this.formatDefaultValue(\n columnDef.defaultValue,\n columnDef.type,\n );\n parts.push(`DEFAULT ${formatted}`);\n }\n\n // CHECK constraint\n if (columnDef.check) {\n parts.push(`CHECK (${columnDef.check})`);\n }\n\n return parts.join(' ');\n }\n\n /**\n * Generate inline UNIQUE constraints for composite indexes\n * Used by engines that require inline UNIQUE for UPSERT to work (DuckDB)\n */\n protected generateInlineUniqueConstraints(\n indexes: IndexDefinition[],\n ): string[] {\n const constraints: string[] = [];\n\n if (!indexes || !Array.isArray(indexes)) {\n return constraints;\n }\n\n for (const index of indexes) {\n // Skip malformed index entries\n if (!index?.columns || !Array.isArray(index.columns)) {\n continue;\n }\n\n // Partial and expression-backed unique indexes are not valid generic\n // ON CONFLICT(column, ...) targets and cannot be represented as an\n // inline column UNIQUE constraint without changing their semantics.\n if (\n index.unique &&\n !index.where &&\n !index.jsonPath &&\n index.columns.length > 0\n ) {\n const columns = index.columns.map((c) => quoteIdentifier(c)).join(', ');\n constraints.push(`UNIQUE(${columns})`);\n }\n }\n\n return constraints;\n }\n\n /**\n * Generate CREATE INDEX statements\n * Override in engine-specific strategies if needed\n */\n generateIndexes(schema: SchemaDefinition): string[] {\n const { tableName, indexes = [] } = schema;\n const statements: string[] = [];\n\n if (!indexes || indexes.length === 0) {\n return statements;\n }\n\n for (const index of indexes) {\n // Narrow the jsonPath target up-front so the formatter call doesn't\n // need optional-chained args (and so the malformed-entry check has a\n // single source of truth).\n const jsonPath =\n index?.jsonPath?.column && index.jsonPath.path ? index.jsonPath : null;\n if (\n !index ||\n (!jsonPath &&\n (!index.columns ||\n !Array.isArray(index.columns) ||\n index.columns.length === 0))\n ) {\n logger.warn(`[DDL] Skipping malformed index: ${JSON.stringify(index)}`);\n continue;\n }\n\n // Skip UNIQUE indexes if engine requires them inline\n if (index.unique && this.requiresInlineUnique()) {\n continue;\n }\n\n const indexType = index.unique ? 'UNIQUE INDEX' : 'INDEX';\n\n // JSON-path indexes use a dialect-specific expression\n const target = jsonPath\n ? `(${this.formatJsonPathIndexExpression(\n jsonPath.column,\n jsonPath.path,\n )})`\n : index.columns.map((c) => quoteIdentifier(c)).join(', ');\n\n let sql = `CREATE ${indexType} IF NOT EXISTS ${quoteIdentifier(\n index.name,\n )} ON ${quoteIdentifier(tableName)} (${target})`;\n\n // Partial index condition\n if (index.where) {\n sql += ` WHERE ${index.where}`;\n }\n\n sql += ';';\n statements.push(sql);\n }\n\n return statements;\n }\n\n /**\n * Render the SQL expression used to index a JSON path inside a JSONB column.\n * Subclasses override for dialect-specific syntax.\n *\n * Default (ANSI-ish): `<jsonColumn>->>'<path>'` — works on Postgres.\n */\n protected formatJsonPathIndexExpression(\n jsonColumn: string,\n path: string,\n ): string {\n assertSafeJsonPathTarget(jsonColumn, path);\n return `${quoteIdentifier(jsonColumn)}->>${quoteStringLiteral(path)}`;\n }\n\n /**\n * Generate CREATE TRIGGER statements\n * Override in engine-specific strategies - DuckDB returns empty\n */\n generateTriggers(schema: SchemaDefinition): string[] {\n if (!this.supportsTriggers()) {\n return [];\n }\n\n const { tableName, triggers = [] } = schema;\n const statements: string[] = [];\n\n if (!triggers || triggers.length === 0) {\n return statements;\n }\n\n for (const trigger of triggers) {\n const sql = this.generateTriggerStatement(tableName, trigger);\n if (sql) {\n statements.push(sql);\n }\n }\n\n return statements;\n }\n\n /**\n * Generate a single trigger statement\n * Can be overridden for engine-specific trigger syntax\n */\n protected generateTriggerStatement(\n tableName: string,\n trigger: TriggerDefinition,\n ): string {\n // Default SQLite-style trigger syntax\n let sql = `CREATE TRIGGER IF NOT EXISTS ${quoteIdentifier(trigger.name)}\\n`;\n sql += `${trigger.when} ${trigger.event} ON ${quoteIdentifier(tableName)}\\n`;\n\n if (trigger.condition) {\n sql += `WHEN ${trigger.condition}\\n`;\n }\n\n sql += `BEGIN\\n${trigger.body}\\nEND;`;\n\n return sql;\n }\n\n /**\n * Map abstract SQL type to engine-specific type\n * Default implementation - override for engine-specific types\n */\n mapType(type: SQLDataType): string {\n // Default mapping works for most engines\n switch (type) {\n case 'TEXT':\n return 'TEXT';\n case 'INTEGER':\n return 'INTEGER';\n case 'REAL':\n return 'REAL';\n case 'BLOB':\n return 'BLOB';\n case 'BOOLEAN':\n return 'BOOLEAN';\n case 'JSON':\n return 'JSON';\n case 'TIMESTAMP':\n return 'TIMESTAMP';\n case 'UUID':\n // Fallback for engines without a native uuid type (e.g. SQLite):\n // store as TEXT. PostgreSQL/DuckDB override this with their native\n // uuid type. (R11)\n return 'TEXT';\n default:\n return 'TEXT';\n }\n }\n\n /**\n * Format default value for SQL.\n *\n * Delegates to the shared, injection-safe `formatDefaultValue`\n * (`schema/sql-identifiers.ts`) so every DDL path uses one set of rules:\n * an allowlist of SQL keyword/function defaults (not \"contains `(`\"),\n * type-driven literal quoting, and no folding of the string `\"null\"` into\n * the SQL NULL keyword. Boolean rendering is bridged through\n * `formatBooleanDefault` so engine overrides (SQLite → 0/1) still apply.\n */\n formatDefaultValue(value: unknown, type: SQLDataType): string {\n return formatDefaultValueShared(value, type, {\n booleanLiterals: [\n this.formatBooleanDefault(true),\n this.formatBooleanDefault(false),\n ],\n });\n }\n\n /**\n * Format boolean default\n * Override for engines that use INTEGER (SQLite)\n */\n protected formatBooleanDefault(value: boolean): string {\n return value ? 'TRUE' : 'FALSE';\n }\n\n /**\n * Whether this engine supports triggers\n * Override in DuckDB strategy to return false\n */\n supportsTriggers(): boolean {\n return true;\n }\n\n /**\n * Whether UNIQUE constraints must be inline for UPSERT\n * Override in DuckDB strategy to return true\n */\n requiresInlineUnique(): boolean {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duckdb-strategy.d.ts","sourceRoot":"","sources":["../../../src/schema/ddl/duckdb-strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAY;IAE3C;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IAalC;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"duckdb-strategy.d.ts","sourceRoot":"","sources":["../../../src/schema/ddl/duckdb-strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAY;IAE3C;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IAalC;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE;IA4BnD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,EAAE;IAIrD;;OAEG;IACH,gBAAgB,IAAI,OAAO;IAI3B;;;;;OAKG;IACH,oBAAoB,IAAI,OAAO;CAGhC"}
|
|
@@ -36,11 +36,10 @@ var DuckDBStrategy = class extends BaseDDLStrategy {
|
|
|
36
36
|
const { tableName, indexes } = schema;
|
|
37
37
|
const statements = [];
|
|
38
38
|
for (const index of indexes) {
|
|
39
|
-
if (index.unique) continue;
|
|
39
|
+
if (index.unique && !index.where && !index.jsonPath) continue;
|
|
40
40
|
const target = renderIndexTarget(index, this.engine);
|
|
41
41
|
if (!target) continue;
|
|
42
|
-
let sql = `CREATE INDEX IF NOT EXISTS "${index.name}" ON "${tableName}" (${target})`;
|
|
43
|
-
if (index.where) sql += ` WHERE ${index.where}`;
|
|
42
|
+
let sql = `CREATE ${index.unique ? "UNIQUE INDEX" : "INDEX"} IF NOT EXISTS "${index.name}" ON "${tableName}" (${target})`;
|
|
44
43
|
sql += ";";
|
|
45
44
|
statements.push(sql);
|
|
46
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duckdb-strategy.js","names":[],"sources":["../../../src/schema/ddl/duckdb-strategy.ts"],"sourcesContent":["/**\n * DuckDB DDL Strategy\n *\n * DuckDB-specific DDL generation with the following characteristics:\n * - UNIQUE constraints MUST be inline for UPSERT to work (DuckDB issue #12684)\n * - Supports CAST in DEFAULT values\n * - Native BOOLEAN, JSON types\n * - Does NOT support triggers\n *\n * CRITICAL: DuckDB's ON CONFLICT clause only works with inline UNIQUE constraints,\n * not with separate UNIQUE indexes. This is a known DuckDB limitation.\n */\n\nimport { renderIndexTarget } from '../index-utils.js';\nimport type { SchemaDefinition, SQLDataType } from '../types.js';\nimport { BaseDDLStrategy } from './base-strategy.js';\nimport type { DatabaseEngine } from './types.js';\n\nexport class DuckDBStrategy extends BaseDDLStrategy {\n readonly engine: DatabaseEngine = 'duckdb';\n\n /**\n * Map types for DuckDB\n * DuckDB has native support for most types\n */\n mapType(type: SQLDataType): string {\n switch (type) {\n case 'TIMESTAMP':\n return 'TIMESTAMP'; // DuckDB supports TIMESTAMP natively\n case 'JSON':\n return 'JSON'; // DuckDB has native JSON type\n case 'UUID':\n return 'UUID'; // DuckDB has a native UUID type (R11)\n default:\n return super.mapType(type);\n }\n }\n\n /**\n * Generate CREATE INDEX statements\n *\n * CRITICAL: Skip UNIQUE indexes - they must be inline constraints for UPSERT\n */\n generateIndexes(schema: SchemaDefinition): string[] {\n const { tableName, indexes } = schema;\n const statements: string[] = [];\n\n for (const index of indexes) {\n // Skip UNIQUE indexes - they're inline constraints\n if (index.unique) {\n continue;\n }\n\n const target = renderIndexTarget(index, this.engine);\n if (!target) {\n continue;\n }\n let sql = `CREATE
|
|
1
|
+
{"version":3,"file":"duckdb-strategy.js","names":[],"sources":["../../../src/schema/ddl/duckdb-strategy.ts"],"sourcesContent":["/**\n * DuckDB DDL Strategy\n *\n * DuckDB-specific DDL generation with the following characteristics:\n * - UNIQUE constraints MUST be inline for UPSERT to work (DuckDB issue #12684)\n * - Supports CAST in DEFAULT values\n * - Native BOOLEAN, JSON types\n * - Does NOT support triggers\n *\n * CRITICAL: DuckDB's ON CONFLICT clause only works with inline UNIQUE constraints,\n * not with separate UNIQUE indexes. This is a known DuckDB limitation.\n */\n\nimport { renderIndexTarget } from '../index-utils.js';\nimport type { SchemaDefinition, SQLDataType } from '../types.js';\nimport { BaseDDLStrategy } from './base-strategy.js';\nimport type { DatabaseEngine } from './types.js';\n\nexport class DuckDBStrategy extends BaseDDLStrategy {\n readonly engine: DatabaseEngine = 'duckdb';\n\n /**\n * Map types for DuckDB\n * DuckDB has native support for most types\n */\n mapType(type: SQLDataType): string {\n switch (type) {\n case 'TIMESTAMP':\n return 'TIMESTAMP'; // DuckDB supports TIMESTAMP natively\n case 'JSON':\n return 'JSON'; // DuckDB has native JSON type\n case 'UUID':\n return 'UUID'; // DuckDB has a native UUID type (R11)\n default:\n return super.mapType(type);\n }\n }\n\n /**\n * Generate CREATE INDEX statements\n *\n * CRITICAL: Skip UNIQUE indexes - they must be inline constraints for UPSERT\n */\n generateIndexes(schema: SchemaDefinition): string[] {\n const { tableName, indexes } = schema;\n const statements: string[] = [];\n\n for (const index of indexes) {\n // Skip UNIQUE indexes - they're inline constraints\n if (index.unique && !index.where && !index.jsonPath) {\n continue;\n }\n\n const target = renderIndexTarget(index, this.engine);\n if (!target) {\n continue;\n }\n const indexType = index.unique ? 'UNIQUE INDEX' : 'INDEX';\n let sql = `CREATE ${indexType} IF NOT EXISTS \"${index.name}\" ON \"${tableName}\" (${target})`;\n\n // DuckDB (and the JSON adapter backed by it) rejects partial indexes.\n // Match the migration differ's documented compatibility behavior by\n // degrading the declaration to a full index while preserving UNIQUE.\n\n sql += ';';\n statements.push(sql);\n }\n\n return statements;\n }\n\n /**\n * DuckDB does NOT support triggers\n */\n generateTriggers(_schema: SchemaDefinition): string[] {\n return []; // DuckDB doesn't support triggers\n }\n\n /**\n * DuckDB does NOT support triggers\n */\n supportsTriggers(): boolean {\n return false;\n }\n\n /**\n * CRITICAL: DuckDB requires inline UNIQUE for UPSERT\n *\n * DuckDB issue #12684: ON CONFLICT only works with inline UNIQUE constraints,\n * not with separate CREATE UNIQUE INDEX statements.\n */\n requiresInlineUnique(): boolean {\n return true;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,IAAa,iBAAb,cAAoC,gBAAgB;CAClD,SAAkC;;;;;CAMlC,QAAQ,MAA2B;EACjC,QAAQ,MAAR;GACE,KAAK,aACH,OAAO;GACT,KAAK,QACH,OAAO;GACT,KAAK,QACH,OAAO;GACT,SACE,OAAO,MAAM,QAAQ,IAAI;EAC7B;CACF;;;;;;CAOA,gBAAgB,QAAoC;EAClD,MAAM,EAAE,WAAW,YAAY;EAC/B,MAAM,aAAuB,CAAC;EAE9B,KAAK,MAAM,SAAS,SAAS;GAE3B,IAAI,MAAM,UAAU,CAAC,MAAM,SAAS,CAAC,MAAM,UACzC;GAGF,MAAM,SAAS,kBAAkB,OAAO,KAAK,MAAM;GACnD,IAAI,CAAC,QACH;GAGF,IAAI,MAAM,UADQ,MAAM,SAAS,iBAAiB,QACpB,kBAAkB,MAAM,KAAK,QAAQ,UAAU,KAAK,OAAO;GAMzF,OAAO;GACP,WAAW,KAAK,GAAG;EACrB;EAEA,OAAO;CACT;;;;CAKA,iBAAiB,SAAqC;EACpD,OAAO,CAAC;CACV;;;;CAKA,mBAA4B;EAC1B,OAAO;CACT;;;;;;;CAQA,uBAAgC;EAC9B,OAAO;CACT;AACF"}
|
package/dist/schema/ddl/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./base-strategy.js";
|
|
1
|
+
import { BaseDDLStrategy } from "./base-strategy.js";
|
|
2
2
|
import { DuckDBStrategy } from "./duckdb-strategy.js";
|
|
3
3
|
import { JsonDuckDBStrategy } from "./json-duckdb-strategy.js";
|
|
4
4
|
import { PostgresStrategy } from "./postgres-strategy.js";
|
|
@@ -22,6 +22,14 @@ function getDDLStrategy(engine) {
|
|
|
22
22
|
return strategy;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
+
* Get all available DDL strategies
|
|
26
|
+
*
|
|
27
|
+
* @returns Array of all DDL strategies
|
|
28
|
+
*/
|
|
29
|
+
function getAllStrategies() {
|
|
30
|
+
return Object.values(strategies);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
25
33
|
* Generate DDL for a specific engine
|
|
26
34
|
*
|
|
27
35
|
* @param schema - The schema definition
|
|
@@ -37,6 +45,22 @@ function generateDDLForEngine(schema, engine) {
|
|
|
37
45
|
};
|
|
38
46
|
}
|
|
39
47
|
/**
|
|
48
|
+
* Generate DDL for all supported engines
|
|
49
|
+
*
|
|
50
|
+
* Useful for storing pre-generated DDL in manifest at build time.
|
|
51
|
+
*
|
|
52
|
+
* @param schema - The schema definition
|
|
53
|
+
* @returns DDL for all engines
|
|
54
|
+
*/
|
|
55
|
+
function generateMultiEngineDDL(schema) {
|
|
56
|
+
return {
|
|
57
|
+
sqlite: generateDDLForEngine(schema, "sqlite"),
|
|
58
|
+
duckdb: generateDDLForEngine(schema, "duckdb"),
|
|
59
|
+
json: generateDDLForEngine(schema, "json"),
|
|
60
|
+
postgres: generateDDLForEngine(schema, "postgres")
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
40
64
|
* Detect database engine from URL or connection type
|
|
41
65
|
*
|
|
42
66
|
* @param url - Database URL or path
|
|
@@ -62,6 +86,6 @@ function detectEngine(url, type) {
|
|
|
62
86
|
return "sqlite";
|
|
63
87
|
}
|
|
64
88
|
//#endregion
|
|
65
|
-
export { detectEngine, generateDDLForEngine, getDDLStrategy };
|
|
89
|
+
export { BaseDDLStrategy, DuckDBStrategy, JsonDuckDBStrategy, PostgresStrategy, SQLiteStrategy, detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy };
|
|
66
90
|
|
|
67
91
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/schema/ddl/index.ts"],"sourcesContent":["/**\n * DDL Generator Module\n *\n * Per-engine DDL generation for SMRT schema management.\n * SMRT handles ALL database maintenance directly - SDK SQL remains a pure query/CRUD layer.\n */\n\nexport { BaseDDLStrategy } from './base-strategy.js';\nexport { DuckDBStrategy } from './duckdb-strategy.js';\nexport { JsonDuckDBStrategy } from './json-duckdb-strategy.js';\nexport { PostgresStrategy } from './postgres-strategy.js';\nexport { SQLiteStrategy } from './sqlite-strategy.js';\nexport * from './types.js';\n\nimport type { SchemaDefinition } from '../types.js';\nimport { DuckDBStrategy } from './duckdb-strategy.js';\nimport { JsonDuckDBStrategy } from './json-duckdb-strategy.js';\nimport { PostgresStrategy } from './postgres-strategy.js';\nimport { SQLiteStrategy } from './sqlite-strategy.js';\nimport type {\n DatabaseEngine,\n DDLStrategy,\n EngineSpecificDDL,\n MultiEngineDDL,\n} from './types.js';\n\n// Singleton instances for each strategy\nconst strategies: Record<DatabaseEngine, DDLStrategy> = {\n sqlite: new SQLiteStrategy(),\n duckdb: new DuckDBStrategy(),\n json: new JsonDuckDBStrategy(),\n postgres: new PostgresStrategy(),\n};\n\n/**\n * Get the DDL strategy for a specific database engine\n *\n * @param engine - The database engine type\n * @returns The DDL strategy for that engine\n */\nexport function getDDLStrategy(engine: DatabaseEngine): DDLStrategy {\n const strategy = strategies[engine];\n if (!strategy) {\n throw new Error(`Unknown database engine: ${engine}`);\n }\n return strategy;\n}\n\n/**\n * Get all available DDL strategies\n *\n * @returns Array of all DDL strategies\n */\nexport function getAllStrategies(): DDLStrategy[] {\n return Object.values(strategies);\n}\n\n/**\n * Generate DDL for a specific engine\n *\n * @param schema - The schema definition\n * @param engine - The target database engine\n * @returns Engine-specific DDL (createTable, indexes, triggers)\n */\nexport function generateDDLForEngine(\n schema: SchemaDefinition,\n engine: DatabaseEngine,\n): EngineSpecificDDL {\n const strategy = getDDLStrategy(engine);\n\n return {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n}\n\n/**\n * Generate DDL for all supported engines\n *\n * Useful for storing pre-generated DDL in manifest at build time.\n *\n * @param schema - The schema definition\n * @returns DDL for all engines\n */\nexport function generateMultiEngineDDL(\n schema: SchemaDefinition,\n): MultiEngineDDL {\n return {\n sqlite: generateDDLForEngine(schema, 'sqlite'),\n duckdb: generateDDLForEngine(schema, 'duckdb'),\n json: generateDDLForEngine(schema, 'json'),\n postgres: generateDDLForEngine(schema, 'postgres'),\n };\n}\n\n/**\n * Detect database engine from URL or connection type\n *\n * @param url - Database URL or path\n * @param type - Optional explicit type hint\n * @returns Detected database engine\n */\nexport function detectEngine(url: string, type?: string): DatabaseEngine {\n // Explicit type takes precedence\n if (type) {\n switch (type.toLowerCase()) {\n case 'sqlite':\n case 'libsql':\n return 'sqlite';\n case 'duckdb':\n case 'json':\n // Query-level compatibility treats JSON as DuckDB. DDL callers that\n // need JSON's UUID-as-TEXT behavior should detect the adapter\n // structurally (exportTable) or request the `json` strategy directly.\n return 'duckdb';\n case 'postgres':\n case 'postgresql':\n case 'pg':\n return 'postgres';\n default:\n // Fall through to URL detection\n break;\n }\n }\n\n // Detect from URL\n const urlLower = url.toLowerCase();\n\n if (\n urlLower.startsWith('postgres://') ||\n urlLower.startsWith('postgresql://')\n ) {\n return 'postgres';\n }\n\n if (urlLower.endsWith('.duckdb') || urlLower.includes('duckdb')) {\n return 'duckdb';\n }\n\n // JSON adapter detection - matches .json extension, /json/ path, or -json- in name\n // This is needed because JSON adapter uses DuckDB internally and requires\n // inline UNIQUE constraints for UPSERT to work\n if (\n urlLower.endsWith('.json') ||\n urlLower.includes('/json/') ||\n urlLower.includes('-json')\n ) {\n // Query-level compatibility treats JSON as DuckDB. DDL callers that need\n // JSON's UUID-as-TEXT behavior should detect the adapter structurally\n // (exportTable) or request the `json` strategy directly.\n return 'duckdb';\n }\n\n // Default to SQLite for file-based databases\n if (\n urlLower.endsWith('.db') ||\n urlLower.endsWith('.sqlite') ||\n urlLower.endsWith('.sqlite3') ||\n urlLower === ':memory:'\n ) {\n return 'sqlite';\n }\n\n // Default fallback\n return 'sqlite';\n}\n"],"mappings":";;;;;;AA2BA,IAAM,aAAkD;CACtD,QAAQ,IAAI,eAAe;CAC3B,QAAQ,IAAI,eAAe;CAC3B,MAAM,IAAI,mBAAmB;CAC7B,UAAU,IAAI,iBAAiB;AACjC;;;;;;;AAQA,SAAgB,eAAe,QAAqC;CAClE,MAAM,WAAW,WAAW;CAC5B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,4BAA4B,QAAQ;CAEtD,OAAO;AACT;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/schema/ddl/index.ts"],"sourcesContent":["/**\n * DDL Generator Module\n *\n * Per-engine DDL generation for SMRT schema management.\n * SMRT handles ALL database maintenance directly - SDK SQL remains a pure query/CRUD layer.\n */\n\nexport { BaseDDLStrategy } from './base-strategy.js';\nexport { DuckDBStrategy } from './duckdb-strategy.js';\nexport { JsonDuckDBStrategy } from './json-duckdb-strategy.js';\nexport { PostgresStrategy } from './postgres-strategy.js';\nexport { SQLiteStrategy } from './sqlite-strategy.js';\nexport * from './types.js';\n\nimport type { SchemaDefinition } from '../types.js';\nimport { DuckDBStrategy } from './duckdb-strategy.js';\nimport { JsonDuckDBStrategy } from './json-duckdb-strategy.js';\nimport { PostgresStrategy } from './postgres-strategy.js';\nimport { SQLiteStrategy } from './sqlite-strategy.js';\nimport type {\n DatabaseEngine,\n DDLStrategy,\n EngineSpecificDDL,\n MultiEngineDDL,\n} from './types.js';\n\n// Singleton instances for each strategy\nconst strategies: Record<DatabaseEngine, DDLStrategy> = {\n sqlite: new SQLiteStrategy(),\n duckdb: new DuckDBStrategy(),\n json: new JsonDuckDBStrategy(),\n postgres: new PostgresStrategy(),\n};\n\n/**\n * Get the DDL strategy for a specific database engine\n *\n * @param engine - The database engine type\n * @returns The DDL strategy for that engine\n */\nexport function getDDLStrategy(engine: DatabaseEngine): DDLStrategy {\n const strategy = strategies[engine];\n if (!strategy) {\n throw new Error(`Unknown database engine: ${engine}`);\n }\n return strategy;\n}\n\n/**\n * Get all available DDL strategies\n *\n * @returns Array of all DDL strategies\n */\nexport function getAllStrategies(): DDLStrategy[] {\n return Object.values(strategies);\n}\n\n/**\n * Generate DDL for a specific engine\n *\n * @param schema - The schema definition\n * @param engine - The target database engine\n * @returns Engine-specific DDL (createTable, indexes, triggers)\n */\nexport function generateDDLForEngine(\n schema: SchemaDefinition,\n engine: DatabaseEngine,\n): EngineSpecificDDL {\n const strategy = getDDLStrategy(engine);\n\n return {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n}\n\n/**\n * Generate DDL for all supported engines\n *\n * Useful for storing pre-generated DDL in manifest at build time.\n *\n * @param schema - The schema definition\n * @returns DDL for all engines\n */\nexport function generateMultiEngineDDL(\n schema: SchemaDefinition,\n): MultiEngineDDL {\n return {\n sqlite: generateDDLForEngine(schema, 'sqlite'),\n duckdb: generateDDLForEngine(schema, 'duckdb'),\n json: generateDDLForEngine(schema, 'json'),\n postgres: generateDDLForEngine(schema, 'postgres'),\n };\n}\n\n/**\n * Detect database engine from URL or connection type\n *\n * @param url - Database URL or path\n * @param type - Optional explicit type hint\n * @returns Detected database engine\n */\nexport function detectEngine(url: string, type?: string): DatabaseEngine {\n // Explicit type takes precedence\n if (type) {\n switch (type.toLowerCase()) {\n case 'sqlite':\n case 'libsql':\n return 'sqlite';\n case 'duckdb':\n case 'json':\n // Query-level compatibility treats JSON as DuckDB. DDL callers that\n // need JSON's UUID-as-TEXT behavior should detect the adapter\n // structurally (exportTable) or request the `json` strategy directly.\n return 'duckdb';\n case 'postgres':\n case 'postgresql':\n case 'pg':\n return 'postgres';\n default:\n // Fall through to URL detection\n break;\n }\n }\n\n // Detect from URL\n const urlLower = url.toLowerCase();\n\n if (\n urlLower.startsWith('postgres://') ||\n urlLower.startsWith('postgresql://')\n ) {\n return 'postgres';\n }\n\n if (urlLower.endsWith('.duckdb') || urlLower.includes('duckdb')) {\n return 'duckdb';\n }\n\n // JSON adapter detection - matches .json extension, /json/ path, or -json- in name\n // This is needed because JSON adapter uses DuckDB internally and requires\n // inline UNIQUE constraints for UPSERT to work\n if (\n urlLower.endsWith('.json') ||\n urlLower.includes('/json/') ||\n urlLower.includes('-json')\n ) {\n // Query-level compatibility treats JSON as DuckDB. DDL callers that need\n // JSON's UUID-as-TEXT behavior should detect the adapter structurally\n // (exportTable) or request the `json` strategy directly.\n return 'duckdb';\n }\n\n // Default to SQLite for file-based databases\n if (\n urlLower.endsWith('.db') ||\n urlLower.endsWith('.sqlite') ||\n urlLower.endsWith('.sqlite3') ||\n urlLower === ':memory:'\n ) {\n return 'sqlite';\n }\n\n // Default fallback\n return 'sqlite';\n}\n"],"mappings":";;;;;;AA2BA,IAAM,aAAkD;CACtD,QAAQ,IAAI,eAAe;CAC3B,QAAQ,IAAI,eAAe;CAC3B,MAAM,IAAI,mBAAmB;CAC7B,UAAU,IAAI,iBAAiB;AACjC;;;;;;;AAQA,SAAgB,eAAe,QAAqC;CAClE,MAAM,WAAW,WAAW;CAC5B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,4BAA4B,QAAQ;CAEtD,OAAO;AACT;;;;;;AAOA,SAAgB,mBAAkC;CAChD,OAAO,OAAO,OAAO,UAAU;AACjC;;;;;;;;AASA,SAAgB,qBACd,QACA,QACmB;CACnB,MAAM,WAAW,eAAe,MAAM;CAEtC,OAAO;EACL,aAAa,SAAS,oBAAoB,MAAM;EAChD,SAAS,SAAS,gBAAgB,MAAM;EACxC,UAAU,SAAS,iBAAiB,MAAM;CAC5C;AACF;;;;;;;;;AAUA,SAAgB,uBACd,QACgB;CAChB,OAAO;EACL,QAAQ,qBAAqB,QAAQ,QAAQ;EAC7C,QAAQ,qBAAqB,QAAQ,QAAQ;EAC7C,MAAM,qBAAqB,QAAQ,MAAM;EACzC,UAAU,qBAAqB,QAAQ,UAAU;CACnD;AACF;;;;;;;;AASA,SAAgB,aAAa,KAAa,MAA+B;CAEvE,IAAI,MACF,QAAQ,KAAK,YAAY,GAAzB;EACE,KAAK;EACL,KAAK,UACH,OAAO;EACT,KAAK;EACL,KAAK,QAIH,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK,MACH,OAAO;EACT,SAEE;CACJ;CAIF,MAAM,WAAW,IAAI,YAAY;CAEjC,IACE,SAAS,WAAW,aAAa,KACjC,SAAS,WAAW,eAAe,GAEnC,OAAO;CAGT,IAAI,SAAS,SAAS,SAAS,KAAK,SAAS,SAAS,QAAQ,GAC5D,OAAO;CAMT,IACE,SAAS,SAAS,OAAO,KACzB,SAAS,SAAS,QAAQ,KAC1B,SAAS,SAAS,OAAO,GAKzB,OAAO;CAIT,IACE,SAAS,SAAS,KAAK,KACvB,SAAS,SAAS,SAAS,KAC3B,SAAS,SAAS,UAAU,KAC5B,aAAa,YAEb,OAAO;CAIT,OAAO;AACT"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DatabaseEngine } from './types.js';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare function getSQLDefinitionIdentifier(definition: string): {
|
|
4
|
+
name: string;
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
quoted: boolean;
|
|
8
|
+
} | null;
|
|
9
|
+
/** @internal */
|
|
10
|
+
export declare function isSQLTableConstraintDefinition(definition: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Build a conservative comparison key for one DDL definition.
|
|
13
|
+
*
|
|
14
|
+
* SQL trivia and unquoted token case are insignificant. Quoted literals,
|
|
15
|
+
* dollar-quoted bodies, and quoted identifiers remain byte-for-byte distinct,
|
|
16
|
+
* so comparison never conflates predicates such as `'a b'` and `'a b'`.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function getSQLDefinitionComparisonKey(value: string): string;
|
|
21
|
+
/** @internal */
|
|
22
|
+
export declare function findCreateTableBodyRange(ddl: string, statementStart?: number): [number, number] | null;
|
|
23
|
+
/** @internal */
|
|
24
|
+
export declare function tokenizeSQLDDLBody(body: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Make cached engine-neutral manifest DDL safe for direct execution.
|
|
27
|
+
*
|
|
28
|
+
* Cached DDL may contain custom table constraints and dialect-specific clauses,
|
|
29
|
+
* so this does not regenerate the whole statement from structured columns. For
|
|
30
|
+
* PostgreSQL it performs a span-preserving rewrite of only a column
|
|
31
|
+
* definition's leading bare TIMESTAMP token. Explicit WITH/WITHOUT TIME ZONE
|
|
32
|
+
* declarations, comments, dollar-quoted/string literals, identifiers,
|
|
33
|
+
* separators, and table constraints remain untouched.
|
|
34
|
+
*/
|
|
35
|
+
export declare function materializeManifestDDLForEngine(ddl: string, engine: DatabaseEngine): string;
|
|
36
|
+
//# sourceMappingURL=materialize-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materialize-manifest.d.ts","sourceRoot":"","sources":["../../../src/schema/ddl/materialize-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoDjD,gBAAgB;AAChB,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAYtE;AAED,gBAAgB;AAChB,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAS1E;AAuKD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA2GnE;AA0CD,gBAAgB;AAChB,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,cAAc,SAAI,GACjB,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAuBzB;AAED,gBAAgB;AAChB,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,GACrB,MAAM,CA8CR"}
|