@happyvertical/smrt-core 0.40.69 → 0.40.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/AGENTS.md +29 -4
  2. package/README.md +20 -1
  3. package/agents/query-bounds.md +45 -0
  4. package/agents/schema-paths.md +328 -0
  5. package/dist/browser.d.ts +1 -0
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +2 -1
  8. package/dist/class.d.ts.map +1 -1
  9. package/dist/class.js +28 -4
  10. package/dist/class.js.map +1 -1
  11. package/dist/collection.d.ts +101 -2
  12. package/dist/collection.d.ts.map +1 -1
  13. package/dist/collection.js +224 -43
  14. package/dist/collection.js.map +1 -1
  15. package/dist/database.d.ts +8 -0
  16. package/dist/database.d.ts.map +1 -1
  17. package/dist/database.js +16 -8
  18. package/dist/database.js.map +1 -1
  19. package/dist/db-errors.d.ts +105 -0
  20. package/dist/db-errors.d.ts.map +1 -0
  21. package/dist/db-errors.js +382 -0
  22. package/dist/db-errors.js.map +1 -0
  23. package/dist/decorators/index.d.ts +17 -1
  24. package/dist/decorators/index.d.ts.map +1 -1
  25. package/dist/decorators/index.js +65 -7
  26. package/dist/decorators/index.js.map +1 -1
  27. package/dist/dispatch/bus.d.ts.map +1 -1
  28. package/dist/dispatch/bus.js +4 -3
  29. package/dist/dispatch/bus.js.map +1 -1
  30. package/dist/errors.d.ts +30 -2
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +34 -2
  33. package/dist/errors.js.map +1 -1
  34. package/dist/generators/changes-route.d.ts.map +1 -1
  35. package/dist/generators/changes-route.js +6 -3
  36. package/dist/generators/changes-route.js.map +1 -1
  37. package/dist/generators/mcp-runtime-template.d.ts +8 -0
  38. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  39. package/dist/generators/mcp-runtime-template.js +38 -4
  40. package/dist/generators/mcp-runtime-template.js.map +1 -1
  41. package/dist/generators/mcp.d.ts +16 -0
  42. package/dist/generators/mcp.d.ts.map +1 -1
  43. package/dist/generators/mcp.js +41 -3
  44. package/dist/generators/mcp.js.map +1 -1
  45. package/dist/generators/rest.d.ts +22 -0
  46. package/dist/generators/rest.d.ts.map +1 -1
  47. package/dist/generators/rest.js +34 -3
  48. package/dist/generators/rest.js.map +1 -1
  49. package/dist/index.d.ts +6 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +8 -3
  52. package/dist/manifest/static-manifest.js +1 -1
  53. package/dist/manifest/static-manifest.js.map +1 -1
  54. package/dist/manifest/store.js +1 -1
  55. package/dist/manifest.json +1 -1
  56. package/dist/migrations/differ.d.ts +202 -8
  57. package/dist/migrations/differ.d.ts.map +1 -1
  58. package/dist/migrations/differ.js +604 -46
  59. package/dist/migrations/differ.js.map +1 -1
  60. package/dist/migrations/generator.d.ts +31 -4
  61. package/dist/migrations/generator.d.ts.map +1 -1
  62. package/dist/migrations/generator.js +49 -5
  63. package/dist/migrations/generator.js.map +1 -1
  64. package/dist/migrations/index.d.ts +3 -2
  65. package/dist/migrations/index.d.ts.map +1 -1
  66. package/dist/migrations/index.js +5 -3
  67. package/dist/migrations/orchestrate.js +35 -6
  68. package/dist/migrations/orchestrate.js.map +1 -1
  69. package/dist/migrations/sqlite-rebuild.d.ts +142 -0
  70. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
  71. package/dist/migrations/sqlite-rebuild.js +514 -0
  72. package/dist/migrations/sqlite-rebuild.js.map +1 -0
  73. package/dist/migrations/tracker.d.ts +114 -1
  74. package/dist/migrations/tracker.d.ts.map +1 -1
  75. package/dist/migrations/tracker.js +331 -16
  76. package/dist/migrations/tracker.js.map +1 -1
  77. package/dist/migrations/types.d.ts +19 -4
  78. package/dist/migrations/types.d.ts.map +1 -1
  79. package/dist/migrations.js +5 -3
  80. package/dist/object.d.ts +45 -6
  81. package/dist/object.d.ts.map +1 -1
  82. package/dist/object.js +60 -13
  83. package/dist/object.js.map +1 -1
  84. package/dist/postgres-timeouts.d.ts +240 -0
  85. package/dist/postgres-timeouts.d.ts.map +1 -0
  86. package/dist/postgres-timeouts.js +204 -0
  87. package/dist/postgres-timeouts.js.map +1 -0
  88. package/dist/query-bounds.d.ts +97 -0
  89. package/dist/query-bounds.d.ts.map +1 -0
  90. package/dist/query-bounds.js +173 -0
  91. package/dist/query-bounds.js.map +1 -0
  92. package/dist/registry/types.d.ts +32 -1
  93. package/dist/registry/types.d.ts.map +1 -1
  94. package/dist/registry.js +1 -1
  95. package/dist/scanner/types.d.ts +5 -0
  96. package/dist/scanner/types.d.ts.map +1 -1
  97. package/dist/scanner/types.js.map +1 -1
  98. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  99. package/dist/schema/ddl/base-strategy.js +2 -2
  100. package/dist/schema/ddl/base-strategy.js.map +1 -1
  101. package/dist/schema/generator.d.ts +70 -5
  102. package/dist/schema/generator.d.ts.map +1 -1
  103. package/dist/schema/generator.js +124 -6
  104. package/dist/schema/generator.js.map +1 -1
  105. package/dist/schema/index.d.ts +3 -0
  106. package/dist/schema/index.d.ts.map +1 -1
  107. package/dist/schema/index.js +4 -1
  108. package/dist/schema/live-parity.d.ts +90 -0
  109. package/dist/schema/live-parity.d.ts.map +1 -0
  110. package/dist/schema/live-parity.js +600 -0
  111. package/dist/schema/live-parity.js.map +1 -0
  112. package/dist/schema/manifest-schema.d.ts +121 -0
  113. package/dist/schema/manifest-schema.d.ts.map +1 -0
  114. package/dist/schema/manifest-schema.js +267 -0
  115. package/dist/schema/manifest-schema.js.map +1 -0
  116. package/dist/schema/schema-aggregator.d.ts +24 -10
  117. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  118. package/dist/schema/schema-aggregator.js +35 -90
  119. package/dist/schema/schema-aggregator.js.map +1 -1
  120. package/dist/schema/system-table-shapes.d.ts +52 -0
  121. package/dist/schema/system-table-shapes.d.ts.map +1 -0
  122. package/dist/schema/system-table-shapes.js +174 -0
  123. package/dist/schema/system-table-shapes.js.map +1 -0
  124. package/dist/schema/types.d.ts +103 -4
  125. package/dist/schema/types.d.ts.map +1 -1
  126. package/dist/schema/utils.d.ts +1 -0
  127. package/dist/schema/utils.d.ts.map +1 -1
  128. package/dist/schema/utils.js +3 -1
  129. package/dist/schema/utils.js.map +1 -1
  130. package/dist/schema.js +4 -1
  131. package/dist/smrt-knowledge.json +18 -6
  132. package/dist/sync/apply.d.ts.map +1 -1
  133. package/dist/sync/apply.js +9 -16
  134. package/dist/sync/apply.js.map +1 -1
  135. package/dist/testing/database.d.ts.map +1 -1
  136. package/dist/testing/database.js +1 -0
  137. package/dist/testing/database.js.map +1 -1
  138. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  139. package/dist/vite-plugin/sveltekit-generator.js +71 -5
  140. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  141. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  142. package/dist/vite-plugin/web-collections.js +6 -4
  143. package/dist/vite-plugin/web-collections.js.map +1 -1
  144. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"schema-aggregator.d.ts","sourceRoot":"","sources":["../../src/schema/schema-aggregator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAgB/D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACrC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC5C,yBAAyB;IACzB,KAAK,EAAE;QACL,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;IAEhC,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA4FD;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,iBAAiB;IAoE5D;;OAEG;IACH,OAAO,CAAC,YAAY;IA6CpB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAgEtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAkBtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAuCpB"}
1
+ {"version":3,"file":"schema-aggregator.d.ts","sourceRoot":"","sources":["../../src/schema/schema-aggregator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,sBAAsB,CAAC;AAM9B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACrC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC5C,yBAAyB;IACzB,KAAK,EAAE;QACL,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;IAEhC,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAqBD;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,iBAAiB;IAoE5D;;OAEG;IACH,OAAO,CAAC,YAAY;IAgDpB;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAqDtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAuCpB"}
@@ -1,9 +1,6 @@
1
- import { quoteIdentifier } from "./sql-identifiers.js";
2
- import { renderIndexTarget } from "./index-utils.js";
3
1
  import { loadExternalManifestSync } from "../manifest/manifest-loader.js";
4
2
  import { discoverSmrtPackages } from "../manifest/discover-smrt-packages.js";
5
- import { findCreateTableBodyRange, getSQLDefinitionComparisonKey, getSQLDefinitionIdentifier, isSQLTableConstraintDefinition, materializeManifestDDLForEngine, tokenizeSQLDDLBody } from "./ddl/materialize-manifest.js";
6
- import "./utils.js";
3
+ import { collectManifestTables, renderCollectedManifestTable } from "./manifest-schema.js";
7
4
  import { existsSync, readFileSync } from "node:fs";
8
5
  import { join } from "node:path";
9
6
  //#region src/schema/schema-aggregator.ts
@@ -13,6 +10,12 @@ import { join } from "node:path";
13
10
  * Aggregates database schemas across multiple SMRT package manifests.
14
11
  * Handles STI table deduplication, index merging, and sorting for deterministic output.
15
12
  *
13
+ * Tables and indexes are rendered from the manifests' structured `columns` /
14
+ * `indexes` through the dialect's DDL strategy (`getDDLStrategy`), the same
15
+ * renderer `smrt db:migrate` uses — never from the cached `schema.ddl` string,
16
+ * which carries no indexes and no per-engine types (#2358). Objects that expose
17
+ * no structured columns fall back to their cached `ddl` for the CREATE TABLE.
18
+ *
16
19
  * This replaces the manual generate-schema.ts pattern used by downstream
17
20
  * projects like blindmanpress.com (Issue #1013).
18
21
  *
@@ -44,54 +47,6 @@ var DEFAULT_MINIMAL_SKIP_PATTERNS = [
44
47
  /_relationship_term/,
45
48
  /_relationship_type/
46
49
  ];
47
- function parseDefinitionsFromDDL(ddl) {
48
- const columns = /* @__PURE__ */ new Map();
49
- const tableElements = [];
50
- const bodyRange = findCreateTableBodyRange(ddl);
51
- if (!bodyRange) return {
52
- columns,
53
- tableElements
54
- };
55
- const [bodyStart, bodyEnd] = bodyRange;
56
- const segments = tokenizeSQLDDLBody(ddl.slice(bodyStart + 1, bodyEnd));
57
- for (const segment of segments) {
58
- const identifier = getSQLDefinitionIdentifier(segment);
59
- if (!identifier) continue;
60
- if (isSQLTableConstraintDefinition(segment)) {
61
- tableElements.push(segment);
62
- continue;
63
- }
64
- columns.set(identifier.name, segment);
65
- }
66
- return {
67
- columns,
68
- tableElements
69
- };
70
- }
71
- function mergeDDL(existingDDL, newDDL) {
72
- const existingDefinitions = parseDefinitionsFromDDL(existingDDL);
73
- const newDefinitions = parseDefinitionsFromDDL(newDDL);
74
- const missingCols = [];
75
- for (const [name, def] of newDefinitions.columns) if (!existingDefinitions.columns.has(name)) missingCols.push(def);
76
- const existingTableElements = new Set(existingDefinitions.tableElements.map(getSQLDefinitionComparisonKey));
77
- const missingTableElements = newDefinitions.tableElements.filter((definition) => !existingTableElements.has(getSQLDefinitionComparisonKey(definition)));
78
- if (missingCols.length === 0 && missingTableElements.length === 0) return existingDDL;
79
- const bodyRange = findCreateTableBodyRange(existingDDL);
80
- if (!bodyRange) return existingDDL;
81
- const [bodyStart, bodyEnd] = bodyRange;
82
- const prefix = existingDDL.slice(0, bodyStart + 1);
83
- const body = existingDDL.slice(bodyStart + 1, bodyEnd);
84
- const suffix = existingDDL.slice(bodyEnd);
85
- const segments = tokenizeSQLDDLBody(body);
86
- const firstConstraintIndex = segments.findIndex((segment) => isSQLTableConstraintDefinition(segment));
87
- const insertionIndex = firstConstraintIndex === -1 ? segments.length : firstConstraintIndex;
88
- return `${prefix}\n${[
89
- ...segments.slice(0, insertionIndex),
90
- ...missingCols,
91
- ...segments.slice(insertionIndex),
92
- ...missingTableElements
93
- ].map((segment) => ` ${segment}`).join(",\n")}\n${suffix}`;
94
- }
95
50
  /**
96
51
  * Aggregates database schemas from multiple SMRT package manifests.
97
52
  *
@@ -151,7 +106,7 @@ var SchemaAggregator = class {
151
106
  const localPath = localPaths[packageName];
152
107
  if (existsSync(localPath)) try {
153
108
  const content = JSON.parse(readFileSync(localPath, "utf-8"));
154
- if (Object.values(content.objects || {}).some((obj) => obj.schema?.ddl)) return content;
109
+ if (Object.values(content.objects || {}).some((obj) => obj.schema?.tableName && (Object.keys(obj.schema.columns || {}).length > 0 || obj.schema.ddl))) return content;
155
110
  } catch {}
156
111
  }
157
112
  const manifest = loadExternalManifestSync(packageName);
@@ -166,54 +121,44 @@ var SchemaAggregator = class {
166
121
  /**
167
122
  * Extract schemas from manifests, deduplicating STI tables.
168
123
  *
169
- * For STI tables (shared by multiple classes across packages),
170
- * keeps the most comprehensive DDL (largest) and merges indexes.
124
+ * STI tables (shared by multiple classes across packages) are merged
125
+ * structurally columns first-wins by name, indexes deduplicated by name —
126
+ * and every table is rendered through the dialect's DDL strategy, the same
127
+ * renderer `db:migrate` uses. The manifest's cached `schema.ddl` string is
128
+ * consulted only for objects that expose no structured columns (#2358).
171
129
  */
172
130
  extractSchemas(manifests, dialect) {
173
- const tables = /* @__PURE__ */ new Map();
174
131
  const engine = dialect === "sqlite" ? "sqlite" : "postgres";
132
+ const entries = [];
133
+ const manifestColumns = /* @__PURE__ */ new Map();
175
134
  for (const manifest of manifests) for (const [_key, object] of Object.entries(manifest.objects)) {
176
135
  const schema = object.schema;
177
- if (!schema?.ddl) continue;
136
+ if (!schema?.tableName) continue;
178
137
  const className = object.className || _key;
179
- const tableName = schema.tableName;
180
- const existing = tables.get(tableName);
181
- const executableDDL = materializeManifestDDLForEngine(schema.ddl, engine);
182
- if (existing) {
183
- existing.ddl = mergeDDL(existing.ddl, executableDDL);
184
- existing.sources.push(`${manifest.packageName || "?"}:${className}`);
185
- if (schema.columns) existing.columns = {
186
- ...existing.columns || {},
187
- ...schema.columns
188
- };
189
- for (const idx of schema.indexes || []) {
190
- const indexDdl = this.formatIndexDdl(idx, tableName, engine);
191
- if (!existing.indexes.includes(indexDdl)) existing.indexes.push(indexDdl);
192
- }
193
- } else {
194
- const indexes = [];
195
- for (const idx of schema.indexes || []) indexes.push(this.formatIndexDdl(idx, tableName, engine));
196
- tables.set(tableName, {
197
- tableName,
198
- ddl: executableDDL,
199
- indexes,
200
- sources: [`${manifest.packageName || "?"}:${className}`],
201
- columns: schema.columns
202
- });
138
+ entries.push({
139
+ schema,
140
+ source: `${manifest.packageName || "?"}:${className}`
141
+ });
142
+ if (schema.columns && Object.keys(schema.columns).length > 0) {
143
+ const merged = manifestColumns.get(schema.tableName) ?? {};
144
+ for (const [name, column] of Object.entries(schema.columns)) if (!(name in merged)) merged[name] = column;
145
+ manifestColumns.set(schema.tableName, merged);
203
146
  }
204
147
  }
148
+ const tables = /* @__PURE__ */ new Map();
149
+ for (const [tableName, table] of collectManifestTables(entries)) {
150
+ const rendered = renderCollectedManifestTable(table, engine);
151
+ tables.set(tableName, {
152
+ tableName,
153
+ ddl: rendered.createTable,
154
+ indexes: rendered.indexes,
155
+ sources: table.sources,
156
+ columns: manifestColumns.get(tableName)
157
+ });
158
+ }
205
159
  return tables;
206
160
  }
207
161
  /**
208
- * Format an index definition into a CREATE INDEX statement.
209
- */
210
- formatIndexDdl(idx, tableName, engine = "sqlite") {
211
- const target = renderIndexTarget(idx, engine);
212
- const indexName = quoteIdentifier(idx.name);
213
- const table = quoteIdentifier(tableName);
214
- return idx.unique ? `CREATE UNIQUE INDEX IF NOT EXISTS ${indexName} ON ${table} (${target});` : `CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} (${target});`;
215
- }
216
- /**
217
162
  * Apply minimal filtering to remove infrastructure/collection tables.
218
163
  */
219
164
  applyMinimalFilter(tables, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"schema-aggregator.js","names":[],"sources":["../../src/schema/schema-aggregator.ts"],"sourcesContent":["/**\n * Schema Aggregator\n *\n * Aggregates database schemas across multiple SMRT package manifests.\n * Handles STI table deduplication, index merging, and sorting for deterministic output.\n *\n * This replaces the manual generate-schema.ts pattern used by downstream\n * projects like blindmanpress.com (Issue #1013).\n *\n * @example\n * ```typescript\n * import { SchemaAggregator } from '@happyvertical/smrt-core/schema';\n *\n * // Auto-discover from installed @happyvertical packages in node_modules\n * const aggregator = new SchemaAggregator();\n * const result = aggregator.aggregate();\n *\n * // Or with explicit packages\n * const result = aggregator.aggregate({\n * packages: ['@happyvertical/smrt-profiles', '@happyvertical/smrt-events'],\n * });\n *\n * console.log(result.sql); // Combined CREATE TABLE + INDEX SQL\n * console.log(result.tables); // Map of table info\n * console.log(result.manifests); // Loaded manifests by package\n * ```\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { discoverSmrtPackages } from '../manifest/discover-smrt-packages.js';\nimport { loadExternalManifestSync } from '../manifest/manifest-loader.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport {\n findCreateTableBodyRange,\n getSQLDefinitionComparisonKey,\n getSQLDefinitionIdentifier,\n isSQLTableConstraintDefinition,\n materializeManifestDDLForEngine,\n tokenizeSQLDDLBody,\n} from './ddl/materialize-manifest.js';\nimport { quoteIdentifier } from './sql-identifiers.js';\nimport { renderIndexTarget } from './utils.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Aggregated table information including DDL, indexes, and source tracking.\n */\nexport interface AggregatedTable {\n /** Table name */\n tableName: string;\n /** CREATE TABLE statement (most comprehensive version for STI tables) */\n ddl: string;\n /** CREATE INDEX statements (deduplicated across sources) */\n indexes: string[];\n /** Package:className sources that contribute to this table */\n sources: string[];\n /** Schema definition metadata */\n columns?: Record<string, unknown>;\n}\n\n/**\n * Result of schema aggregation across packages.\n */\nexport interface AggregationResult {\n /** Aggregated tables keyed by table name */\n tables: Map<string, AggregatedTable>;\n /** Combined SQL string (CREATE TABLE + CREATE INDEX statements) */\n sql: string;\n /** Loaded manifests keyed by package name */\n manifests: Map<string, SmartObjectManifest>;\n /** Summary statistics */\n stats: {\n packagesLoaded: number;\n packagesSkipped: number;\n totalObjects: number;\n uniqueTables: number;\n stiTables: number;\n };\n}\n\n/**\n * Options for schema aggregation.\n */\nexport interface AggregateOptions {\n /**\n * Explicit list of packages to scan. If omitted, auto-discovers\n * installed `@happyvertical/smrt-*` packages from `node_modules`\n * via `discoverSmrtPackages()`.\n */\n packages?: string[];\n\n /**\n * Additional local filesystem paths to check for manifests,\n * keyed by package name. Useful for development when local\n * packages have newer schemas than node_modules.\n */\n localPaths?: Record<string, string>;\n\n /**\n * Skip collection tables and infrastructure tables.\n * Useful for generating minimal schemas for lightweight deployments.\n */\n minimal?: boolean;\n\n /**\n * Table name patterns to skip in minimal mode.\n * Defaults to common infrastructure tables.\n */\n minimalSkipPatterns?: RegExp[];\n\n /**\n * Specific table names to skip in minimal mode.\n */\n minimalSkipTables?: string[];\n\n /**\n * SQL dialect for the output. Affects PRAGMA statements and syntax.\n * @default 'postgres'\n */\n dialect?: 'postgres' | 'sqlite';\n\n /** Log progress to console */\n verbose?: boolean;\n}\n\n// ============================================================================\n// Default minimal-mode skip lists\n// ============================================================================\n\nconst DEFAULT_MINIMAL_SKIP_PATTERNS: RegExp[] = [\n /^api_key/,\n /^magic_link/,\n /^nostr_/,\n /^audit_log/,\n /_metafield/,\n /_metadata/,\n /_relationship_term/,\n /_relationship_type/,\n];\n\nfunction parseDefinitionsFromDDL(ddl: string): {\n columns: Map<string, string>;\n tableElements: string[];\n} {\n const columns = new Map<string, string>();\n const tableElements: string[] = [];\n const bodyRange = findCreateTableBodyRange(ddl);\n if (!bodyRange) return { columns, tableElements };\n const [bodyStart, bodyEnd] = bodyRange;\n\n const segments = tokenizeSQLDDLBody(ddl.slice(bodyStart + 1, bodyEnd));\n\n for (const segment of segments) {\n const identifier = getSQLDefinitionIdentifier(segment);\n if (!identifier) continue;\n if (isSQLTableConstraintDefinition(segment)) {\n tableElements.push(segment);\n continue;\n }\n\n columns.set(identifier.name, segment);\n }\n\n return { columns, tableElements };\n}\n\nfunction mergeDDL(existingDDL: string, newDDL: string): string {\n const existingDefinitions = parseDefinitionsFromDDL(existingDDL);\n const newDefinitions = parseDefinitionsFromDDL(newDDL);\n\n const missingCols: string[] = [];\n for (const [name, def] of newDefinitions.columns) {\n if (!existingDefinitions.columns.has(name)) {\n missingCols.push(def);\n }\n }\n\n const existingTableElements = new Set(\n existingDefinitions.tableElements.map(getSQLDefinitionComparisonKey),\n );\n const missingTableElements = newDefinitions.tableElements.filter(\n (definition) =>\n !existingTableElements.has(getSQLDefinitionComparisonKey(definition)),\n );\n\n if (missingCols.length === 0 && missingTableElements.length === 0) {\n return existingDDL;\n }\n\n const bodyRange = findCreateTableBodyRange(existingDDL);\n if (!bodyRange) return existingDDL;\n const [bodyStart, bodyEnd] = bodyRange;\n const prefix = existingDDL.slice(0, bodyStart + 1);\n const body = existingDDL.slice(bodyStart + 1, bodyEnd);\n const suffix = existingDDL.slice(bodyEnd);\n const segments = tokenizeSQLDDLBody(body);\n const firstConstraintIndex = segments.findIndex((segment) =>\n isSQLTableConstraintDefinition(segment),\n );\n const insertionIndex =\n firstConstraintIndex === -1 ? segments.length : firstConstraintIndex;\n const mergedSegments = [\n ...segments.slice(0, insertionIndex),\n ...missingCols,\n ...segments.slice(insertionIndex),\n ...missingTableElements,\n ];\n\n return `${prefix}\\n${mergedSegments.map((segment) => ` ${segment}`).join(',\\n')}\\n${suffix}`;\n}\n\n// ============================================================================\n// SchemaAggregator\n// ============================================================================\n\n/**\n * Aggregates database schemas from multiple SMRT package manifests.\n *\n * Handles the complete workflow of discovering packages, loading manifests,\n * extracting DDL, deduplicating STI tables, and producing combined SQL.\n */\nexport class SchemaAggregator {\n /**\n * Aggregate schemas from SMRT packages.\n *\n * @param options - Aggregation options\n * @returns Aggregated schemas with combined SQL\n */\n aggregate(options: AggregateOptions = {}): AggregationResult {\n const verbose = options.verbose ?? false;\n\n // 1. Discover packages\n const packages = options.packages || discoverSmrtPackages();\n\n if (verbose) {\n console.log(`[SchemaAggregator] Scanning ${packages.length} packages...`);\n }\n\n // 2. Load manifests\n const manifests = new Map<string, SmartObjectManifest>();\n let packagesSkipped = 0;\n\n for (const pkg of packages) {\n const manifest = this.loadManifest(pkg, options.localPaths);\n if (manifest) {\n manifests.set(pkg, manifest);\n if (verbose) {\n const objectCount = Object.keys(manifest.objects).length;\n console.log(` ✓ ${pkg} (${objectCount} objects)`);\n }\n } else {\n packagesSkipped++;\n if (verbose) {\n console.log(` [skip] ${pkg} - manifest not found`);\n }\n }\n }\n\n // 3. Extract and deduplicate schemas\n const tables = this.extractSchemas(\n Array.from(manifests.values()),\n options.dialect,\n );\n\n // 4. Apply minimal filtering if requested\n if (options.minimal) {\n this.applyMinimalFilter(tables, options);\n }\n\n // 5. Count STI tables and total objects\n let stiTables = 0;\n let totalObjects = 0;\n for (const table of tables.values()) {\n if (table.sources.length > 1) stiTables++;\n }\n for (const manifest of manifests.values()) {\n totalObjects += Object.keys(manifest.objects).length;\n }\n\n // 6. Generate combined SQL\n const sql = this.generateSQL(tables, options);\n\n return {\n tables,\n sql,\n manifests,\n stats: {\n packagesLoaded: manifests.size,\n packagesSkipped,\n totalObjects,\n uniqueTables: tables.size,\n stiTables,\n },\n };\n }\n\n /**\n * Load a manifest from local path or node_modules.\n */\n private loadManifest(\n packageName: string,\n localPaths?: Record<string, string>,\n ): SmartObjectManifest | null {\n // Try local development path first (may have newer schemas)\n if (localPaths?.[packageName]) {\n const localPath = localPaths[packageName];\n if (existsSync(localPath)) {\n try {\n const content = JSON.parse(\n readFileSync(localPath, 'utf-8'),\n ) as SmartObjectManifest;\n // Only use local if it has schemas\n const hasSchemas = Object.values(content.objects || {}).some(\n (obj) => obj.schema?.ddl,\n );\n if (hasSchemas) return content;\n } catch {\n // Fall through to node_modules\n }\n }\n }\n\n // Try loadExternalManifestSync (handles node_modules resolution)\n const manifest = loadExternalManifestSync(packageName);\n if (manifest) return manifest;\n\n // Fallback: try common node_modules paths relative to cwd\n const root = process.cwd();\n const paths = [\n join(root, 'node_modules', packageName, 'dist', 'manifest.json'),\n join(root, 'node_modules', packageName, 'manifest.json'),\n ];\n\n for (const manifestPath of paths) {\n if (existsSync(manifestPath)) {\n try {\n return JSON.parse(readFileSync(manifestPath, 'utf-8'));\n } catch {}\n }\n }\n\n return null;\n }\n\n /**\n * Extract schemas from manifests, deduplicating STI tables.\n *\n * For STI tables (shared by multiple classes across packages),\n * keeps the most comprehensive DDL (largest) and merges indexes.\n */\n private extractSchemas(\n manifests: SmartObjectManifest[],\n dialect?: 'postgres' | 'sqlite',\n ): Map<string, AggregatedTable> {\n const tables = new Map<string, AggregatedTable>();\n // The aggregator only knows sqlite vs postgres; jsonPath rendering\n // treats anything non-sqlite as the postgres-style `->>` form, which\n // is also what DuckDB accepts. Default mirrors `AggregateOptions.dialect`\n // (`@default 'postgres'`) so unspecified callers get the documented shape.\n const engine: 'sqlite' | 'postgres' | 'duckdb' =\n dialect === 'sqlite' ? 'sqlite' : 'postgres';\n\n for (const manifest of manifests) {\n for (const [_key, object] of Object.entries(manifest.objects)) {\n const schema = object.schema;\n if (!schema?.ddl) continue;\n\n const className: string = object.className || _key;\n const tableName: string = schema.tableName;\n const existing = tables.get(tableName);\n const executableDDL = materializeManifestDDLForEngine(\n schema.ddl,\n engine,\n );\n\n if (existing) {\n // STI table — merge columns from every subtype into the shared DDL.\n existing.ddl = mergeDDL(existing.ddl, executableDDL);\n existing.sources.push(`${manifest.packageName || '?'}:${className}`);\n if (schema.columns) {\n existing.columns = {\n ...(existing.columns || {}),\n ...schema.columns,\n };\n }\n\n // Merge indexes (deduplicate by SQL text)\n for (const idx of schema.indexes || []) {\n const indexDdl = this.formatIndexDdl(idx, tableName, engine);\n if (!existing.indexes.includes(indexDdl)) {\n existing.indexes.push(indexDdl);\n }\n }\n } else {\n // New table\n const indexes: string[] = [];\n for (const idx of schema.indexes || []) {\n indexes.push(this.formatIndexDdl(idx, tableName, engine));\n }\n\n tables.set(tableName, {\n tableName,\n ddl: executableDDL,\n indexes,\n sources: [`${manifest.packageName || '?'}:${className}`],\n columns: schema.columns,\n });\n }\n }\n }\n\n return tables;\n }\n\n /**\n * Format an index definition into a CREATE INDEX statement.\n */\n private formatIndexDdl(\n idx: {\n name: string;\n columns: string[];\n unique?: boolean;\n jsonPath?: { column: string; path: string };\n },\n tableName: string,\n engine: 'sqlite' | 'postgres' | 'duckdb' = 'sqlite',\n ): string {\n const target = renderIndexTarget(idx, engine);\n const indexName = quoteIdentifier(idx.name);\n const table = quoteIdentifier(tableName);\n return idx.unique\n ? `CREATE UNIQUE INDEX IF NOT EXISTS ${indexName} ON ${table} (${target});`\n : `CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} (${target});`;\n }\n\n /**\n * Apply minimal filtering to remove infrastructure/collection tables.\n */\n private applyMinimalFilter(\n tables: Map<string, AggregatedTable>,\n options: AggregateOptions,\n ): void {\n const skipPatterns =\n options.minimalSkipPatterns || DEFAULT_MINIMAL_SKIP_PATTERNS;\n const skipTables = new Set(options.minimalSkipTables || []);\n\n for (const tableName of Array.from(tables.keys())) {\n if (skipTables.has(tableName)) {\n tables.delete(tableName);\n continue;\n }\n for (const pattern of skipPatterns) {\n if (pattern.test(tableName)) {\n tables.delete(tableName);\n break;\n }\n }\n }\n }\n\n /**\n * Generate combined SQL from aggregated tables.\n */\n private generateSQL(\n tables: Map<string, AggregatedTable>,\n options: AggregateOptions,\n ): string {\n const lines: string[] = [\n '-- Combined SMRT Database Schema',\n `-- Generated: ${new Date().toISOString()}`,\n '',\n ];\n\n if (options.dialect === 'sqlite') {\n lines.push('PRAGMA foreign_keys = ON;', '');\n }\n\n // Sort tables by name for deterministic output\n const sortedTables = Array.from(tables.entries()).sort((a, b) =>\n a[0].localeCompare(b[0]),\n );\n\n for (const [tableName, table] of sortedTables) {\n lines.push(`-- Table: ${tableName}`);\n if (table.sources.length > 1) {\n lines.push(`-- STI table, sources: ${table.sources.join(', ')}`);\n } else {\n lines.push(`-- Source: ${table.sources[0]}`);\n }\n lines.push(table.ddl);\n lines.push('');\n\n if (table.indexes.length > 0) {\n for (const idx of table.indexes) {\n lines.push(idx);\n }\n lines.push('');\n }\n }\n\n return lines.join('\\n');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"schema-aggregator.js","names":[],"sources":["../../src/schema/schema-aggregator.ts"],"sourcesContent":["/**\n * Schema Aggregator\n *\n * Aggregates database schemas across multiple SMRT package manifests.\n * Handles STI table deduplication, index merging, and sorting for deterministic output.\n *\n * Tables and indexes are rendered from the manifests' structured `columns` /\n * `indexes` through the dialect's DDL strategy (`getDDLStrategy`), the same\n * renderer `smrt db:migrate` uses — never from the cached `schema.ddl` string,\n * which carries no indexes and no per-engine types (#2358). Objects that expose\n * no structured columns fall back to their cached `ddl` for the CREATE TABLE.\n *\n * This replaces the manual generate-schema.ts pattern used by downstream\n * projects like blindmanpress.com (Issue #1013).\n *\n * @example\n * ```typescript\n * import { SchemaAggregator } from '@happyvertical/smrt-core/schema';\n *\n * // Auto-discover from installed @happyvertical packages in node_modules\n * const aggregator = new SchemaAggregator();\n * const result = aggregator.aggregate();\n *\n * // Or with explicit packages\n * const result = aggregator.aggregate({\n * packages: ['@happyvertical/smrt-profiles', '@happyvertical/smrt-events'],\n * });\n *\n * console.log(result.sql); // Combined CREATE TABLE + INDEX SQL\n * console.log(result.tables); // Map of table info\n * console.log(result.manifests); // Loaded manifests by package\n * ```\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { discoverSmrtPackages } from '../manifest/discover-smrt-packages.js';\nimport { loadExternalManifestSync } from '../manifest/manifest-loader.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport {\n collectManifestTables,\n type ManifestColumnLike,\n renderCollectedManifestTable,\n} from './manifest-schema.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Aggregated table information including DDL, indexes, and source tracking.\n */\nexport interface AggregatedTable {\n /** Table name */\n tableName: string;\n /**\n * CREATE TABLE statement rendered by the dialect's DDL strategy from the\n * merged structured columns of every contributing class (STI union). A\n * table with a column-less contributor (hand-authored manifest) has that\n * contributor's cached `ddl` merged on top; a table with no structured\n * columns at all is the merged cached `ddl`.\n */\n ddl: string;\n /**\n * CREATE INDEX statements rendered by the same strategy (partial `WHERE`\n * and JSON-path targets included), deduplicated by index name across\n * sources.\n */\n indexes: string[];\n /** Package:className sources that contribute to this table */\n sources: string[];\n /**\n * Manifest column metadata as published in `manifest.json` (`default`,\n * not `defaultValue`), merged across contributors — first contributor wins\n * per column name, matching the rendered DDL.\n */\n columns?: Record<string, ManifestColumnLike>;\n}\n\n/**\n * Result of schema aggregation across packages.\n */\nexport interface AggregationResult {\n /** Aggregated tables keyed by table name */\n tables: Map<string, AggregatedTable>;\n /** Combined SQL string (CREATE TABLE + CREATE INDEX statements) */\n sql: string;\n /** Loaded manifests keyed by package name */\n manifests: Map<string, SmartObjectManifest>;\n /** Summary statistics */\n stats: {\n packagesLoaded: number;\n packagesSkipped: number;\n totalObjects: number;\n uniqueTables: number;\n stiTables: number;\n };\n}\n\n/**\n * Options for schema aggregation.\n */\nexport interface AggregateOptions {\n /**\n * Explicit list of packages to scan. If omitted, auto-discovers\n * installed `@happyvertical/smrt-*` packages from `node_modules`\n * via `discoverSmrtPackages()`.\n */\n packages?: string[];\n\n /**\n * Additional local filesystem paths to check for manifests,\n * keyed by package name. Useful for development when local\n * packages have newer schemas than node_modules.\n */\n localPaths?: Record<string, string>;\n\n /**\n * Skip collection tables and infrastructure tables.\n * Useful for generating minimal schemas for lightweight deployments.\n */\n minimal?: boolean;\n\n /**\n * Table name patterns to skip in minimal mode.\n * Defaults to common infrastructure tables.\n */\n minimalSkipPatterns?: RegExp[];\n\n /**\n * Specific table names to skip in minimal mode.\n */\n minimalSkipTables?: string[];\n\n /**\n * SQL dialect for the output. Affects PRAGMA statements and syntax.\n * @default 'postgres'\n */\n dialect?: 'postgres' | 'sqlite';\n\n /** Log progress to console */\n verbose?: boolean;\n}\n\n// ============================================================================\n// Default minimal-mode skip lists\n// ============================================================================\n\nconst DEFAULT_MINIMAL_SKIP_PATTERNS: RegExp[] = [\n /^api_key/,\n /^magic_link/,\n /^nostr_/,\n /^audit_log/,\n /_metafield/,\n /_metadata/,\n /_relationship_term/,\n /_relationship_type/,\n];\n\n// ============================================================================\n// SchemaAggregator\n// ============================================================================\n\n/**\n * Aggregates database schemas from multiple SMRT package manifests.\n *\n * Handles the complete workflow of discovering packages, loading manifests,\n * extracting DDL, deduplicating STI tables, and producing combined SQL.\n */\nexport class SchemaAggregator {\n /**\n * Aggregate schemas from SMRT packages.\n *\n * @param options - Aggregation options\n * @returns Aggregated schemas with combined SQL\n */\n aggregate(options: AggregateOptions = {}): AggregationResult {\n const verbose = options.verbose ?? false;\n\n // 1. Discover packages\n const packages = options.packages || discoverSmrtPackages();\n\n if (verbose) {\n console.log(`[SchemaAggregator] Scanning ${packages.length} packages...`);\n }\n\n // 2. Load manifests\n const manifests = new Map<string, SmartObjectManifest>();\n let packagesSkipped = 0;\n\n for (const pkg of packages) {\n const manifest = this.loadManifest(pkg, options.localPaths);\n if (manifest) {\n manifests.set(pkg, manifest);\n if (verbose) {\n const objectCount = Object.keys(manifest.objects).length;\n console.log(` ✓ ${pkg} (${objectCount} objects)`);\n }\n } else {\n packagesSkipped++;\n if (verbose) {\n console.log(` [skip] ${pkg} - manifest not found`);\n }\n }\n }\n\n // 3. Extract and deduplicate schemas\n const tables = this.extractSchemas(\n Array.from(manifests.values()),\n options.dialect,\n );\n\n // 4. Apply minimal filtering if requested\n if (options.minimal) {\n this.applyMinimalFilter(tables, options);\n }\n\n // 5. Count STI tables and total objects\n let stiTables = 0;\n let totalObjects = 0;\n for (const table of tables.values()) {\n if (table.sources.length > 1) stiTables++;\n }\n for (const manifest of manifests.values()) {\n totalObjects += Object.keys(manifest.objects).length;\n }\n\n // 6. Generate combined SQL\n const sql = this.generateSQL(tables, options);\n\n return {\n tables,\n sql,\n manifests,\n stats: {\n packagesLoaded: manifests.size,\n packagesSkipped,\n totalObjects,\n uniqueTables: tables.size,\n stiTables,\n },\n };\n }\n\n /**\n * Load a manifest from local path or node_modules.\n */\n private loadManifest(\n packageName: string,\n localPaths?: Record<string, string>,\n ): SmartObjectManifest | null {\n // Try local development path first (may have newer schemas)\n if (localPaths?.[packageName]) {\n const localPath = localPaths[packageName];\n if (existsSync(localPath)) {\n try {\n const content = JSON.parse(\n readFileSync(localPath, 'utf-8'),\n ) as SmartObjectManifest;\n // Only use local if it has schemas\n const hasSchemas = Object.values(content.objects || {}).some(\n (obj) =>\n obj.schema?.tableName &&\n (Object.keys(obj.schema.columns || {}).length > 0 ||\n obj.schema.ddl),\n );\n if (hasSchemas) return content;\n } catch {\n // Fall through to node_modules\n }\n }\n }\n\n // Try loadExternalManifestSync (handles node_modules resolution)\n const manifest = loadExternalManifestSync(packageName);\n if (manifest) return manifest;\n\n // Fallback: try common node_modules paths relative to cwd\n const root = process.cwd();\n const paths = [\n join(root, 'node_modules', packageName, 'dist', 'manifest.json'),\n join(root, 'node_modules', packageName, 'manifest.json'),\n ];\n\n for (const manifestPath of paths) {\n if (existsSync(manifestPath)) {\n try {\n return JSON.parse(readFileSync(manifestPath, 'utf-8'));\n } catch {}\n }\n }\n\n return null;\n }\n\n /**\n * Extract schemas from manifests, deduplicating STI tables.\n *\n * STI tables (shared by multiple classes across packages) are merged\n * structurally — columns first-wins by name, indexes deduplicated by name —\n * and every table is rendered through the dialect's DDL strategy, the same\n * renderer `db:migrate` uses. The manifest's cached `schema.ddl` string is\n * consulted only for objects that expose no structured columns (#2358).\n */\n private extractSchemas(\n manifests: SmartObjectManifest[],\n dialect?: 'postgres' | 'sqlite',\n ): Map<string, AggregatedTable> {\n // Default mirrors `AggregateOptions.dialect` (`@default 'postgres'`) so\n // unspecified callers get the documented shape.\n const engine: DatabaseEngine = dialect === 'sqlite' ? 'sqlite' : 'postgres';\n\n const entries: Array<{\n schema: NonNullable<SmartObjectManifest['objects'][string]['schema']>;\n source: string;\n }> = [];\n // Published manifest column metadata, merged first-wins per table so\n // `AggregatedTable.columns` keeps its manifest shape for downstream\n // generate-schema scripts.\n const manifestColumns = new Map<\n string,\n Record<string, ManifestColumnLike>\n >();\n for (const manifest of manifests) {\n for (const [_key, object] of Object.entries(manifest.objects)) {\n const schema = object.schema;\n if (!schema?.tableName) continue;\n const className: string = object.className || _key;\n entries.push({\n schema,\n source: `${manifest.packageName || '?'}:${className}`,\n });\n if (schema.columns && Object.keys(schema.columns).length > 0) {\n const merged = manifestColumns.get(schema.tableName) ?? {};\n for (const [name, column] of Object.entries(schema.columns)) {\n if (!(name in merged)) merged[name] = column;\n }\n manifestColumns.set(schema.tableName, merged);\n }\n }\n }\n\n const tables = new Map<string, AggregatedTable>();\n for (const [tableName, table] of collectManifestTables(entries)) {\n const rendered = renderCollectedManifestTable(table, engine);\n tables.set(tableName, {\n tableName,\n ddl: rendered.createTable,\n indexes: rendered.indexes,\n sources: table.sources,\n columns: manifestColumns.get(tableName),\n });\n }\n\n return tables;\n }\n\n /**\n * Apply minimal filtering to remove infrastructure/collection tables.\n */\n private applyMinimalFilter(\n tables: Map<string, AggregatedTable>,\n options: AggregateOptions,\n ): void {\n const skipPatterns =\n options.minimalSkipPatterns || DEFAULT_MINIMAL_SKIP_PATTERNS;\n const skipTables = new Set(options.minimalSkipTables || []);\n\n for (const tableName of Array.from(tables.keys())) {\n if (skipTables.has(tableName)) {\n tables.delete(tableName);\n continue;\n }\n for (const pattern of skipPatterns) {\n if (pattern.test(tableName)) {\n tables.delete(tableName);\n break;\n }\n }\n }\n }\n\n /**\n * Generate combined SQL from aggregated tables.\n */\n private generateSQL(\n tables: Map<string, AggregatedTable>,\n options: AggregateOptions,\n ): string {\n const lines: string[] = [\n '-- Combined SMRT Database Schema',\n `-- Generated: ${new Date().toISOString()}`,\n '',\n ];\n\n if (options.dialect === 'sqlite') {\n lines.push('PRAGMA foreign_keys = ON;', '');\n }\n\n // Sort tables by name for deterministic output\n const sortedTables = Array.from(tables.entries()).sort((a, b) =>\n a[0].localeCompare(b[0]),\n );\n\n for (const [tableName, table] of sortedTables) {\n lines.push(`-- Table: ${tableName}`);\n if (table.sources.length > 1) {\n lines.push(`-- STI table, sources: ${table.sources.join(', ')}`);\n } else {\n lines.push(`-- Source: ${table.sources[0]}`);\n }\n lines.push(table.ddl);\n lines.push('');\n\n if (table.indexes.length > 0) {\n for (const idx of table.indexes) {\n lines.push(idx);\n }\n lines.push('');\n }\n }\n\n return lines.join('\\n');\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { DatabaseEngine } from './ddl/types.js';
2
+ /** One column as declared by hand-written system-table DDL. */
3
+ export interface SystemTableColumnShape {
4
+ name: string;
5
+ /**
6
+ * The engine-materialized type text exactly as declared (`TEXT`, `BIGINT`,
7
+ * `TIMESTAMPTZ`, …). System DDL is materialized per engine by
8
+ * `getSystemTableDDL()`, so no abstract-type mapping is involved.
9
+ */
10
+ type: string;
11
+ primaryKey: boolean;
12
+ notNull: boolean;
13
+ unique: boolean;
14
+ hasDefault: boolean;
15
+ }
16
+ /** One index as declared by hand-written system-table DDL. */
17
+ export interface SystemTableIndexShape {
18
+ name: string;
19
+ columns: string[];
20
+ unique: boolean;
21
+ where?: string;
22
+ }
23
+ /** The complete declared shape of one `_smrt_*` table. */
24
+ export interface SystemTableShape {
25
+ tableName: string;
26
+ columns: SystemTableColumnShape[];
27
+ indexes: SystemTableIndexShape[];
28
+ /**
29
+ * Column sets declared by inline `UNIQUE (...)` table constraints. These
30
+ * back framework upserts but materialize as implicit indexes
31
+ * (`sqlite_autoindex_*` / `<table>_key`) that name-based introspection
32
+ * misses, so they are tracked separately from {@link indexes}.
33
+ */
34
+ uniqueConstraints: string[][];
35
+ }
36
+ /**
37
+ * Parse the complete system-table DDL for an engine into table shapes.
38
+ *
39
+ * @param engine - Target engine; selects the materialized DDL variant
40
+ * (PostgreSQL uses TIMESTAMPTZ where the portable DDL says TIMESTAMP).
41
+ * @returns One shape per declared `_smrt_*` table, keyed by table name.
42
+ */
43
+ export declare function getSystemTableShapes(engine: DatabaseEngine): Map<string, SystemTableShape>;
44
+ /**
45
+ * Parse an arbitrary DDL script into table shapes.
46
+ *
47
+ * Exported for tests and for callers that materialize system DDL themselves.
48
+ * Statements the parser does not understand are skipped silently — this is a
49
+ * diagnostic input, never an execution path.
50
+ */
51
+ export declare function parseTableShapes(ddl: string): Map<string, SystemTableShape>;
52
+ //# sourceMappingURL=system-table-shapes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-table-shapes.d.ts","sourceRoot":"","sources":["../../src/schema/system-table-shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,+DAA+D;AAC/D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,EAAE,EAAE,CAAC;CAC/B;AAqBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,GACrB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CA8B3E"}
@@ -0,0 +1,174 @@
1
+ import { getSystemTableDDL } from "../system/schema.js";
2
+ //#region src/schema/system-table-shapes.ts
3
+ /**
4
+ * System-table shape parsing (#2368)
5
+ *
6
+ * `_smrt_*` tables are created from hand-written DDL in `src/system/schema.ts`
7
+ * rather than from decorated classes, so they never enter the manifest and the
8
+ * manifest-driven differ has never compared them against a live database.
9
+ *
10
+ * This module parses that DDL back into a structural description — columns,
11
+ * declared indexes, and inline `UNIQUE (...)` constraints — so the live-schema
12
+ * parity check can hold system tables to the same standard as application
13
+ * tables. The DDL is the source of truth for these tables; parsing it (rather
14
+ * than duplicating a hand-maintained expectation table) keeps the two from
15
+ * drifting apart.
16
+ *
17
+ * The parser is deliberately narrow: it understands the portable subset SMRT
18
+ * actually writes (`CREATE TABLE IF NOT EXISTS`, `CREATE [UNIQUE] INDEX IF NOT
19
+ * EXISTS`, simple column definitions, inline `UNIQUE`/`PRIMARY KEY` table
20
+ * constraints) and ignores anything else rather than guessing.
21
+ */
22
+ var CREATE_TABLE_RE = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?("?)([A-Za-z_][\w$]*)\1\s*\(/i;
23
+ var CREATE_INDEX_RE = /^CREATE\s+(UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?("?)([A-Za-z_][\w$]*)\2\s+ON\s+("?)([A-Za-z_][\w$]*)\4\s*\(([^)]*)\)\s*(?:WHERE\s+([\s\S]+))?$/i;
24
+ /**
25
+ * Table-level constraint clauses that carry no column-shape information.
26
+ *
27
+ * Anchored on a following delimiter rather than a bare prefix so a column
28
+ * legitimately named `checksum` is not mistaken for a `CHECK` constraint.
29
+ */
30
+ var IGNORED_CONSTRAINT_PATTERNS = [
31
+ /^CHECK\s*\(/i,
32
+ /^FOREIGN\s+KEY\b/i,
33
+ /^CONSTRAINT\s+/i,
34
+ /^EXCLUDE\b/i
35
+ ];
36
+ /**
37
+ * Parse the complete system-table DDL for an engine into table shapes.
38
+ *
39
+ * @param engine - Target engine; selects the materialized DDL variant
40
+ * (PostgreSQL uses TIMESTAMPTZ where the portable DDL says TIMESTAMP).
41
+ * @returns One shape per declared `_smrt_*` table, keyed by table name.
42
+ */
43
+ function getSystemTableShapes(engine) {
44
+ return parseTableShapes(getSystemTableDDL(engine).join("\n"));
45
+ }
46
+ /**
47
+ * Parse an arbitrary DDL script into table shapes.
48
+ *
49
+ * Exported for tests and for callers that materialize system DDL themselves.
50
+ * Statements the parser does not understand are skipped silently — this is a
51
+ * diagnostic input, never an execution path.
52
+ */
53
+ function parseTableShapes(ddl) {
54
+ const shapes = /* @__PURE__ */ new Map();
55
+ for (const statement of splitStatements(stripComments(ddl))) {
56
+ const tableMatch = statement.match(CREATE_TABLE_RE);
57
+ if (tableMatch) {
58
+ const shape = parseCreateTable(statement, tableMatch[2]);
59
+ if (shape) shapes.set(shape.tableName, shape);
60
+ continue;
61
+ }
62
+ const indexMatch = statement.match(CREATE_INDEX_RE);
63
+ if (!indexMatch) continue;
64
+ const [, uniqueKeyword, , indexName, , tableName, columnList, where] = indexMatch;
65
+ const shape = shapes.get(tableName);
66
+ if (!shape) continue;
67
+ shape.indexes.push({
68
+ name: indexName,
69
+ columns: splitColumnList(columnList),
70
+ unique: Boolean(uniqueKeyword),
71
+ ...where ? { where: where.trim() } : {}
72
+ });
73
+ }
74
+ return shapes;
75
+ }
76
+ /** Remove `--` line comments so they cannot be mistaken for statements. */
77
+ function stripComments(ddl) {
78
+ return ddl.replace(/--[^\n]*/g, "");
79
+ }
80
+ /** Split a DDL script on statement terminators, dropping empty fragments. */
81
+ function splitStatements(ddl) {
82
+ return ddl.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
83
+ }
84
+ function parseCreateTable(statement, tableName) {
85
+ const open = statement.indexOf("(");
86
+ const close = statement.lastIndexOf(")");
87
+ if (open === -1 || close === -1 || close <= open) return null;
88
+ const shape = {
89
+ tableName,
90
+ columns: [],
91
+ indexes: [],
92
+ uniqueConstraints: []
93
+ };
94
+ for (const item of splitTopLevel(statement.slice(open + 1, close))) {
95
+ const uniqueConstraint = item.match(/^UNIQUE\s*\(([^)]*)\)$/i);
96
+ if (uniqueConstraint) {
97
+ shape.uniqueConstraints.push(splitColumnList(uniqueConstraint[1]));
98
+ continue;
99
+ }
100
+ if (/^PRIMARY\s+KEY\s*\(/i.test(item)) {
101
+ const primaryKeyColumns = item.match(/^PRIMARY\s+KEY\s*\(([^)]*)\)$/i);
102
+ if (primaryKeyColumns) {
103
+ const columnNames = new Set(splitColumnList(primaryKeyColumns[1]));
104
+ for (const column of shape.columns) if (columnNames.has(column.name)) column.primaryKey = true;
105
+ }
106
+ continue;
107
+ }
108
+ if (IGNORED_CONSTRAINT_PATTERNS.some((pattern) => pattern.test(item))) continue;
109
+ const column = parseColumnDefinition(item);
110
+ if (column) shape.columns.push(column);
111
+ }
112
+ return shape;
113
+ }
114
+ /**
115
+ * Multi-word type tails SMRT's portable DDL can produce. Kept explicit so a
116
+ * trailing constraint keyword is never absorbed into the type text.
117
+ */
118
+ var MULTI_WORD_TYPE_TAILS = [
119
+ "WITH TIME ZONE",
120
+ "WITHOUT TIME ZONE",
121
+ "PRECISION",
122
+ "VARYING"
123
+ ];
124
+ function parseColumnDefinition(item) {
125
+ const tokens = item.split(/\s+/);
126
+ if (tokens.length < 2) return null;
127
+ const name = unquoteIdentifier(tokens[0]);
128
+ if (!/^[A-Za-z_][\w$]*$/.test(name)) return null;
129
+ let type = tokens[1];
130
+ let rest = tokens.slice(2).join(" ");
131
+ for (const tail of MULTI_WORD_TYPE_TAILS) if (rest.toUpperCase().startsWith(tail)) {
132
+ type = `${type} ${rest.slice(0, tail.length)}`;
133
+ rest = rest.slice(tail.length).trim();
134
+ break;
135
+ }
136
+ const restUpper = rest.toUpperCase();
137
+ return {
138
+ name,
139
+ type,
140
+ primaryKey: /\bPRIMARY\s+KEY\b/.test(restUpper),
141
+ notNull: /\bNOT\s+NULL\b/.test(restUpper),
142
+ unique: /\bUNIQUE\b/.test(restUpper),
143
+ hasDefault: /\bDEFAULT\b/.test(restUpper)
144
+ };
145
+ }
146
+ /** Split a parenthesized column list into bare column names. */
147
+ function splitColumnList(columnList) {
148
+ return columnList.split(",").map((column) => unquoteIdentifier(column.trim())).filter((column) => column.length > 0);
149
+ }
150
+ /** Split on commas that sit outside any parenthesis nesting. */
151
+ function splitTopLevel(body) {
152
+ const items = [];
153
+ let depth = 0;
154
+ let current = "";
155
+ for (const char of body) {
156
+ if (char === "(") depth++;
157
+ if (char === ")") depth--;
158
+ if (char === "," && depth === 0) {
159
+ items.push(current.trim());
160
+ current = "";
161
+ continue;
162
+ }
163
+ current += char;
164
+ }
165
+ if (current.trim().length > 0) items.push(current.trim());
166
+ return items.map((item) => item.replace(/\s+/g, " ").trim()).filter((item) => item.length > 0);
167
+ }
168
+ function unquoteIdentifier(identifier) {
169
+ return identifier.replace(/^["`[]|["`\]]$/g, "");
170
+ }
171
+ //#endregion
172
+ export { getSystemTableShapes, parseTableShapes };
173
+
174
+ //# sourceMappingURL=system-table-shapes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-table-shapes.js","names":[],"sources":["../../src/schema/system-table-shapes.ts"],"sourcesContent":["/**\n * System-table shape parsing (#2368)\n *\n * `_smrt_*` tables are created from hand-written DDL in `src/system/schema.ts`\n * rather than from decorated classes, so they never enter the manifest and the\n * manifest-driven differ has never compared them against a live database.\n *\n * This module parses that DDL back into a structural description — columns,\n * declared indexes, and inline `UNIQUE (...)` constraints — so the live-schema\n * parity check can hold system tables to the same standard as application\n * tables. The DDL is the source of truth for these tables; parsing it (rather\n * than duplicating a hand-maintained expectation table) keeps the two from\n * drifting apart.\n *\n * The parser is deliberately narrow: it understands the portable subset SMRT\n * actually writes (`CREATE TABLE IF NOT EXISTS`, `CREATE [UNIQUE] INDEX IF NOT\n * EXISTS`, simple column definitions, inline `UNIQUE`/`PRIMARY KEY` table\n * constraints) and ignores anything else rather than guessing.\n */\n\nimport { getSystemTableDDL } from '../system/schema.js';\nimport type { DatabaseEngine } from './ddl/types.js';\n\n/** One column as declared by hand-written system-table DDL. */\nexport interface SystemTableColumnShape {\n name: string;\n /**\n * The engine-materialized type text exactly as declared (`TEXT`, `BIGINT`,\n * `TIMESTAMPTZ`, …). System DDL is materialized per engine by\n * `getSystemTableDDL()`, so no abstract-type mapping is involved.\n */\n type: string;\n primaryKey: boolean;\n notNull: boolean;\n unique: boolean;\n hasDefault: boolean;\n}\n\n/** One index as declared by hand-written system-table DDL. */\nexport interface SystemTableIndexShape {\n name: string;\n columns: string[];\n unique: boolean;\n where?: string;\n}\n\n/** The complete declared shape of one `_smrt_*` table. */\nexport interface SystemTableShape {\n tableName: string;\n columns: SystemTableColumnShape[];\n indexes: SystemTableIndexShape[];\n /**\n * Column sets declared by inline `UNIQUE (...)` table constraints. These\n * back framework upserts but materialize as implicit indexes\n * (`sqlite_autoindex_*` / `<table>_key`) that name-based introspection\n * misses, so they are tracked separately from {@link indexes}.\n */\n uniqueConstraints: string[][];\n}\n\nconst CREATE_TABLE_RE =\n /^CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(\"?)([A-Za-z_][\\w$]*)\\1\\s*\\(/i;\n\nconst CREATE_INDEX_RE =\n /^CREATE\\s+(UNIQUE\\s+)?INDEX\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(\"?)([A-Za-z_][\\w$]*)\\2\\s+ON\\s+(\"?)([A-Za-z_][\\w$]*)\\4\\s*\\(([^)]*)\\)\\s*(?:WHERE\\s+([\\s\\S]+))?$/i;\n\n/**\n * Table-level constraint clauses that carry no column-shape information.\n *\n * Anchored on a following delimiter rather than a bare prefix so a column\n * legitimately named `checksum` is not mistaken for a `CHECK` constraint.\n */\nconst IGNORED_CONSTRAINT_PATTERNS = [\n /^CHECK\\s*\\(/i,\n /^FOREIGN\\s+KEY\\b/i,\n /^CONSTRAINT\\s+/i,\n /^EXCLUDE\\b/i,\n];\n\n/**\n * Parse the complete system-table DDL for an engine into table shapes.\n *\n * @param engine - Target engine; selects the materialized DDL variant\n * (PostgreSQL uses TIMESTAMPTZ where the portable DDL says TIMESTAMP).\n * @returns One shape per declared `_smrt_*` table, keyed by table name.\n */\nexport function getSystemTableShapes(\n engine: DatabaseEngine,\n): Map<string, SystemTableShape> {\n return parseTableShapes(getSystemTableDDL(engine).join('\\n'));\n}\n\n/**\n * Parse an arbitrary DDL script into table shapes.\n *\n * Exported for tests and for callers that materialize system DDL themselves.\n * Statements the parser does not understand are skipped silently — this is a\n * diagnostic input, never an execution path.\n */\nexport function parseTableShapes(ddl: string): Map<string, SystemTableShape> {\n const shapes = new Map<string, SystemTableShape>();\n\n for (const statement of splitStatements(stripComments(ddl))) {\n const tableMatch = statement.match(CREATE_TABLE_RE);\n if (tableMatch) {\n const shape = parseCreateTable(statement, tableMatch[2]);\n if (shape) {\n shapes.set(shape.tableName, shape);\n }\n continue;\n }\n\n const indexMatch = statement.match(CREATE_INDEX_RE);\n if (!indexMatch) continue;\n\n const [, uniqueKeyword, , indexName, , tableName, columnList, where] =\n indexMatch;\n const shape = shapes.get(tableName);\n if (!shape) continue;\n\n shape.indexes.push({\n name: indexName,\n columns: splitColumnList(columnList),\n unique: Boolean(uniqueKeyword),\n ...(where ? { where: where.trim() } : {}),\n });\n }\n\n return shapes;\n}\n\n/** Remove `--` line comments so they cannot be mistaken for statements. */\nfunction stripComments(ddl: string): string {\n return ddl.replace(/--[^\\n]*/g, '');\n}\n\n/** Split a DDL script on statement terminators, dropping empty fragments. */\nfunction splitStatements(ddl: string): string[] {\n return ddl\n .split(';')\n .map((statement) => statement.trim())\n .filter((statement) => statement.length > 0);\n}\n\nfunction parseCreateTable(\n statement: string,\n tableName: string,\n): SystemTableShape | null {\n const open = statement.indexOf('(');\n const close = statement.lastIndexOf(')');\n if (open === -1 || close === -1 || close <= open) {\n return null;\n }\n\n const shape: SystemTableShape = {\n tableName,\n columns: [],\n indexes: [],\n uniqueConstraints: [],\n };\n\n for (const item of splitTopLevel(statement.slice(open + 1, close))) {\n const uniqueConstraint = item.match(/^UNIQUE\\s*\\(([^)]*)\\)$/i);\n if (uniqueConstraint) {\n shape.uniqueConstraints.push(splitColumnList(uniqueConstraint[1]));\n continue;\n }\n\n if (/^PRIMARY\\s+KEY\\s*\\(/i.test(item)) {\n const primaryKeyColumns = item.match(/^PRIMARY\\s+KEY\\s*\\(([^)]*)\\)$/i);\n if (primaryKeyColumns) {\n const columnNames = new Set(splitColumnList(primaryKeyColumns[1]));\n for (const column of shape.columns) {\n if (columnNames.has(column.name)) {\n column.primaryKey = true;\n }\n }\n }\n continue;\n }\n\n if (IGNORED_CONSTRAINT_PATTERNS.some((pattern) => pattern.test(item))) {\n continue;\n }\n\n const column = parseColumnDefinition(item);\n if (column) {\n shape.columns.push(column);\n }\n }\n\n return shape;\n}\n\n/**\n * Multi-word type tails SMRT's portable DDL can produce. Kept explicit so a\n * trailing constraint keyword is never absorbed into the type text.\n */\nconst MULTI_WORD_TYPE_TAILS = [\n 'WITH TIME ZONE',\n 'WITHOUT TIME ZONE',\n 'PRECISION',\n 'VARYING',\n];\n\nfunction parseColumnDefinition(item: string): SystemTableColumnShape | null {\n const tokens = item.split(/\\s+/);\n if (tokens.length < 2) return null;\n\n const name = unquoteIdentifier(tokens[0]);\n if (!/^[A-Za-z_][\\w$]*$/.test(name)) return null;\n\n let type = tokens[1];\n let rest = tokens.slice(2).join(' ');\n for (const tail of MULTI_WORD_TYPE_TAILS) {\n if (rest.toUpperCase().startsWith(tail)) {\n type = `${type} ${rest.slice(0, tail.length)}`;\n rest = rest.slice(tail.length).trim();\n break;\n }\n }\n\n const restUpper = rest.toUpperCase();\n return {\n name,\n type,\n primaryKey: /\\bPRIMARY\\s+KEY\\b/.test(restUpper),\n notNull: /\\bNOT\\s+NULL\\b/.test(restUpper),\n unique: /\\bUNIQUE\\b/.test(restUpper),\n hasDefault: /\\bDEFAULT\\b/.test(restUpper),\n };\n}\n\n/** Split a parenthesized column list into bare column names. */\nfunction splitColumnList(columnList: string): string[] {\n return columnList\n .split(',')\n .map((column) => unquoteIdentifier(column.trim()))\n .filter((column) => column.length > 0);\n}\n\n/** Split on commas that sit outside any parenthesis nesting. */\nfunction splitTopLevel(body: string): string[] {\n const items: string[] = [];\n let depth = 0;\n let current = '';\n\n for (const char of body) {\n if (char === '(') depth++;\n if (char === ')') depth--;\n if (char === ',' && depth === 0) {\n items.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n\n if (current.trim().length > 0) {\n items.push(current.trim());\n }\n\n return items\n .map((item) => item.replace(/\\s+/g, ' ').trim())\n .filter((item) => item.length > 0);\n}\n\nfunction unquoteIdentifier(identifier: string): string {\n return identifier.replace(/^[\"`[]|[\"`\\]]$/g, '');\n}\n"],"mappings":"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"}