@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.
Files changed (91) hide show
  1. package/dist/browser.js +2 -2
  2. package/dist/change-feed.d.ts.map +1 -1
  3. package/dist/change-feed.js +21 -4
  4. package/dist/change-feed.js.map +1 -1
  5. package/dist/class.d.ts.map +1 -1
  6. package/dist/class.js +5 -3
  7. package/dist/class.js.map +1 -1
  8. package/dist/dispatch/bus.d.ts.map +1 -1
  9. package/dist/dispatch/bus.js +11 -6
  10. package/dist/dispatch/bus.js.map +1 -1
  11. package/dist/index.js +2 -2
  12. package/dist/manifest/static-manifest.js +2 -2
  13. package/dist/manifest/static-manifest.js.map +1 -1
  14. package/dist/manifest/store.js +1 -1
  15. package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
  16. package/dist/manifest/test-manifest-stub.js +132 -2
  17. package/dist/manifest/test-manifest-stub.js.map +1 -1
  18. package/dist/manifest.json +2 -2
  19. package/dist/migrations/backfill-tracker.d.ts +1 -0
  20. package/dist/migrations/backfill-tracker.d.ts.map +1 -1
  21. package/dist/migrations/backfill-tracker.js +9 -3
  22. package/dist/migrations/backfill-tracker.js.map +1 -1
  23. package/dist/migrations/differ.d.ts +18 -0
  24. package/dist/migrations/differ.d.ts.map +1 -1
  25. package/dist/migrations/differ.js +28 -5
  26. package/dist/migrations/differ.js.map +1 -1
  27. package/dist/migrations/generator.d.ts +8 -0
  28. package/dist/migrations/generator.d.ts.map +1 -1
  29. package/dist/migrations/generator.js +5 -1
  30. package/dist/migrations/generator.js.map +1 -1
  31. package/dist/migrations/orchestrate.d.ts +10 -0
  32. package/dist/migrations/orchestrate.d.ts.map +1 -1
  33. package/dist/migrations/orchestrate.js +8 -2
  34. package/dist/migrations/orchestrate.js.map +1 -1
  35. package/dist/migrations/tracker.d.ts.map +1 -1
  36. package/dist/migrations/tracker.js +4 -2
  37. package/dist/migrations/tracker.js.map +1 -1
  38. package/dist/registry/schema-builder.d.ts +13 -6
  39. package/dist/registry/schema-builder.d.ts.map +1 -1
  40. package/dist/registry/schema-builder.js +54 -15
  41. package/dist/registry/schema-builder.js.map +1 -1
  42. package/dist/registry.d.ts +3 -2
  43. package/dist/registry.d.ts.map +1 -1
  44. package/dist/registry.js +4 -4
  45. package/dist/registry.js.map +1 -1
  46. package/dist/schema/code-generator.js +123 -0
  47. package/dist/schema/code-generator.js.map +1 -0
  48. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  49. package/dist/schema/ddl/base-strategy.js +2 -2
  50. package/dist/schema/ddl/base-strategy.js.map +1 -1
  51. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  52. package/dist/schema/ddl/duckdb-strategy.js +2 -3
  53. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  54. package/dist/schema/ddl/index.js +26 -2
  55. package/dist/schema/ddl/index.js.map +1 -1
  56. package/dist/schema/ddl/materialize-manifest.d.ts +36 -0
  57. package/dist/schema/ddl/materialize-manifest.d.ts.map +1 -0
  58. package/dist/schema/ddl/materialize-manifest.js +361 -0
  59. package/dist/schema/ddl/materialize-manifest.js.map +1 -0
  60. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  61. package/dist/schema/ddl/postgres-strategy.js +1 -1
  62. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  63. package/dist/schema/index.js +12 -0
  64. package/dist/schema/override-system.js +205 -0
  65. package/dist/schema/override-system.js.map +1 -0
  66. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  67. package/dist/schema/schema-aggregator.js +261 -0
  68. package/dist/schema/schema-aggregator.js.map +1 -0
  69. package/dist/schema/schema-manager.js +11 -1
  70. package/dist/schema/schema-manager.js.map +1 -1
  71. package/dist/schema/utils.d.ts +1 -0
  72. package/dist/schema/utils.d.ts.map +1 -1
  73. package/dist/schema/utils.js +2 -1
  74. package/dist/schema/utils.js.map +1 -1
  75. package/dist/schema.d.ts +2 -0
  76. package/dist/schema.d.ts.map +1 -0
  77. package/dist/schema.js +13 -0
  78. package/dist/smrt-knowledge.json +5 -4
  79. package/dist/system/compatibility.d.ts +25 -0
  80. package/dist/system/compatibility.d.ts.map +1 -1
  81. package/dist/system/compatibility.js +61 -1
  82. package/dist/system/compatibility.js.map +1 -1
  83. package/dist/system/index.js +2 -2
  84. package/dist/system/schema.d.ts +17 -9
  85. package/dist/system/schema.d.ts.map +1 -1
  86. package/dist/system/schema.js +25 -11
  87. package/dist/system/schema.js.map +1 -1
  88. package/dist/testing/database.d.ts.map +1 -1
  89. package/dist/testing/database.js +5 -2
  90. package/dist/testing/database.js.map +1 -1
  91. package/package.json +10 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"override-system.js","names":[],"sources":["../../src/schema/override-system.ts"],"sourcesContent":["/**\n * Schema override system for SMRT objects\n * Allows packages and applications to extend base schemas\n */\n\nimport { createHash } from 'node:crypto';\nimport type {\n ColumnDefinition,\n ForeignKeyDefinition,\n IndexDefinition,\n SchemaDefinition,\n SchemaOverride,\n TriggerDefinition,\n} from './types.js';\n\nexport class SchemaOverrideSystem {\n /**\n * Apply schema override to base schema\n */\n static applyOverride(\n baseSchema: SchemaDefinition,\n override: SchemaOverride,\n ): SchemaDefinition {\n const overriddenSchema: SchemaDefinition = {\n ...baseSchema,\n version: SchemaOverrideSystem.generateOverrideVersion(\n baseSchema,\n override,\n ),\n packageName: override.packageName,\n };\n\n // Apply column additions\n if (override.addColumns) {\n overriddenSchema.columns = {\n ...overriddenSchema.columns,\n ...override.addColumns,\n };\n }\n\n // Apply column removals\n if (override.removeColumns) {\n for (const columnName of override.removeColumns) {\n delete overriddenSchema.columns[columnName];\n }\n }\n\n // Apply index additions\n if (override.addIndexes) {\n overriddenSchema.indexes = [\n ...overriddenSchema.indexes,\n ...override.addIndexes,\n ];\n }\n\n // Apply index removals\n if (override.removeIndexes) {\n overriddenSchema.indexes = overriddenSchema.indexes.filter(\n (index) => !override.removeIndexes?.includes(index.name),\n );\n }\n\n // Apply trigger additions\n if (override.addTriggers) {\n overriddenSchema.triggers = [\n ...overriddenSchema.triggers,\n ...override.addTriggers,\n ];\n }\n\n // Apply trigger removals\n if (override.removeTriggers) {\n overriddenSchema.triggers = overriddenSchema.triggers.filter(\n (trigger) => !override.removeTriggers?.includes(trigger.name),\n );\n }\n\n // Update foreign keys and dependencies\n overriddenSchema.foreignKeys = SchemaOverrideSystem.extractForeignKeys(\n overriddenSchema.columns,\n );\n overriddenSchema.dependencies =\n SchemaOverrideSystem.extractDependencies(overriddenSchema);\n\n return overriddenSchema;\n }\n\n /**\n * Create a schema override for extending a base schema\n */\n static createOverride(\n tableName: string,\n packageName: string,\n extensions: {\n addColumns?: Record<string, ColumnDefinition>;\n removeColumns?: string[];\n addIndexes?: IndexDefinition[];\n removeIndexes?: string[];\n addTriggers?: TriggerDefinition[];\n removeTriggers?: string[];\n },\n ): SchemaOverride {\n return {\n tableName,\n packageName,\n ...extensions,\n };\n }\n\n /**\n * Merge multiple schema overrides\n */\n static mergeOverrides(\n baseSchema: SchemaDefinition,\n overrides: SchemaOverride[],\n ): SchemaDefinition {\n let currentSchema = baseSchema;\n\n for (const override of overrides) {\n if (override.tableName === baseSchema.tableName) {\n currentSchema = SchemaOverrideSystem.applyOverride(\n currentSchema,\n override,\n );\n }\n }\n\n return currentSchema;\n }\n\n /**\n * Create praeco-specific content schema override\n */\n static createPraecoContentOverride(): SchemaOverride {\n return SchemaOverrideSystem.createOverride('contents', 'praeco', {\n addColumns: {\n council_id: {\n type: 'TEXT',\n foreignKey: {\n table: 'councils',\n column: 'id',\n onDelete: 'CASCADE',\n onUpdate: 'CASCADE',\n },\n description: 'Reference to council this content belongs to',\n },\n meeting_id: {\n type: 'TEXT',\n foreignKey: {\n table: 'meetings',\n column: 'id',\n onDelete: 'CASCADE',\n onUpdate: 'CASCADE',\n },\n description: 'Reference to meeting this content relates to',\n },\n content_type: {\n type: 'TEXT',\n notNull: true,\n defaultValue: \"'document'\",\n description: 'Type of content: document, agenda, minutes, etc.',\n },\n source_url: {\n type: 'TEXT',\n description: 'Original URL where content was found',\n },\n extraction_metadata: {\n type: 'JSON',\n description: 'Metadata about content extraction process',\n },\n analysis_results: {\n type: 'JSON',\n description: 'AI analysis results for content',\n },\n priority_score: {\n type: 'INTEGER',\n defaultValue: '0',\n description: 'Priority score for content processing',\n },\n },\n addIndexes: [\n {\n name: 'idx_contents_council_id',\n columns: ['council_id'],\n description: 'Index for council-based content queries',\n },\n {\n name: 'idx_contents_meeting_id',\n columns: ['meeting_id'],\n description: 'Index for meeting-based content queries',\n },\n {\n name: 'idx_contents_type_priority',\n columns: ['content_type', 'priority_score'],\n description: 'Compound index for content type and priority queries',\n },\n ],\n });\n }\n\n /**\n * Create praeco-specific meeting schema override\n */\n static createPraecoMeetingOverride(): SchemaOverride {\n return SchemaOverrideSystem.createOverride('meetings', 'praeco', {\n addColumns: {\n agenda_url: {\n type: 'TEXT',\n description: 'URL to meeting agenda document',\n },\n minutes_url: {\n type: 'TEXT',\n description: 'URL to meeting minutes document',\n },\n video_url: {\n type: 'TEXT',\n description: 'URL to meeting video recording',\n },\n meeting_status: {\n type: 'TEXT',\n notNull: true,\n defaultValue: \"'scheduled'\",\n description: 'Status: scheduled, in_progress, completed, cancelled',\n },\n ai_summary: {\n type: 'JSON',\n description: 'AI-generated summary of meeting content',\n },\n key_decisions: {\n type: 'JSON',\n description: 'Key decisions made in the meeting',\n },\n action_items: {\n type: 'JSON',\n description: 'Action items from the meeting',\n },\n },\n addIndexes: [\n {\n name: 'idx_meetings_status_date',\n columns: ['meeting_status', 'date'],\n description: 'Index for status and date-based meeting queries',\n },\n ],\n });\n }\n\n /**\n * Generate version for overridden schema\n */\n private static generateOverrideVersion(\n baseSchema: SchemaDefinition,\n override: SchemaOverride,\n ): string {\n const overrideContent = JSON.stringify({\n baseVersion: baseSchema.version,\n override: {\n addColumns: override.addColumns,\n removeColumns: override.removeColumns,\n addIndexes: override.addIndexes,\n removeIndexes: override.removeIndexes,\n addTriggers: override.addTriggers,\n removeTriggers: override.removeTriggers,\n },\n packageName: override.packageName,\n });\n\n return createHash('sha256')\n .update(overrideContent)\n .digest('hex')\n .substring(0, 8);\n }\n\n /**\n * Extract foreign keys from columns\n */\n private static extractForeignKeys(\n columns: Record<string, ColumnDefinition>,\n ): ForeignKeyDefinition[] {\n const foreignKeys: ForeignKeyDefinition[] = [];\n\n for (const [columnName, columnDef] of Object.entries(columns)) {\n if (columnDef.foreignKey) {\n foreignKeys.push({\n column: columnName,\n referencesTable: columnDef.foreignKey.table,\n referencesColumn: columnDef.foreignKey.column,\n onDelete: columnDef.foreignKey.onDelete,\n onUpdate: columnDef.foreignKey.onUpdate,\n });\n }\n }\n\n return foreignKeys;\n }\n\n /**\n * Extract dependencies from schema\n */\n private static extractDependencies(schema: SchemaDefinition): string[] {\n const dependencies = new Set<string>();\n\n // Add dependencies from foreign keys\n for (const fk of schema.foreignKeys) {\n dependencies.add(fk.referencesTable);\n }\n\n return Array.from(dependencies);\n }\n}\n"],"mappings":"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"}
@@ -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;AAQ/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;AAiID;;;;;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;IA4DtB;;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;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
@@ -0,0 +1,261 @@
1
+ import { quoteIdentifier } from "./sql-identifiers.js";
2
+ import { renderIndexTarget } from "./index-utils.js";
3
+ import { loadExternalManifestSync } from "../manifest/manifest-loader.js";
4
+ 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";
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ //#region src/schema/schema-aggregator.ts
10
+ /**
11
+ * Schema Aggregator
12
+ *
13
+ * Aggregates database schemas across multiple SMRT package manifests.
14
+ * Handles STI table deduplication, index merging, and sorting for deterministic output.
15
+ *
16
+ * This replaces the manual generate-schema.ts pattern used by downstream
17
+ * projects like blindmanpress.com (Issue #1013).
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { SchemaAggregator } from '@happyvertical/smrt-core/schema';
22
+ *
23
+ * // Auto-discover from installed @happyvertical packages in node_modules
24
+ * const aggregator = new SchemaAggregator();
25
+ * const result = aggregator.aggregate();
26
+ *
27
+ * // Or with explicit packages
28
+ * const result = aggregator.aggregate({
29
+ * packages: ['@happyvertical/smrt-profiles', '@happyvertical/smrt-events'],
30
+ * });
31
+ *
32
+ * console.log(result.sql); // Combined CREATE TABLE + INDEX SQL
33
+ * console.log(result.tables); // Map of table info
34
+ * console.log(result.manifests); // Loaded manifests by package
35
+ * ```
36
+ */
37
+ var DEFAULT_MINIMAL_SKIP_PATTERNS = [
38
+ /^api_key/,
39
+ /^magic_link/,
40
+ /^nostr_/,
41
+ /^audit_log/,
42
+ /_metafield/,
43
+ /_metadata/,
44
+ /_relationship_term/,
45
+ /_relationship_type/
46
+ ];
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
+ /**
96
+ * Aggregates database schemas from multiple SMRT package manifests.
97
+ *
98
+ * Handles the complete workflow of discovering packages, loading manifests,
99
+ * extracting DDL, deduplicating STI tables, and producing combined SQL.
100
+ */
101
+ var SchemaAggregator = class {
102
+ /**
103
+ * Aggregate schemas from SMRT packages.
104
+ *
105
+ * @param options - Aggregation options
106
+ * @returns Aggregated schemas with combined SQL
107
+ */
108
+ aggregate(options = {}) {
109
+ const verbose = options.verbose ?? false;
110
+ const packages = options.packages || discoverSmrtPackages();
111
+ if (verbose) console.log(`[SchemaAggregator] Scanning ${packages.length} packages...`);
112
+ const manifests = /* @__PURE__ */ new Map();
113
+ let packagesSkipped = 0;
114
+ for (const pkg of packages) {
115
+ const manifest = this.loadManifest(pkg, options.localPaths);
116
+ if (manifest) {
117
+ manifests.set(pkg, manifest);
118
+ if (verbose) {
119
+ const objectCount = Object.keys(manifest.objects).length;
120
+ console.log(` ✓ ${pkg} (${objectCount} objects)`);
121
+ }
122
+ } else {
123
+ packagesSkipped++;
124
+ if (verbose) console.log(` [skip] ${pkg} - manifest not found`);
125
+ }
126
+ }
127
+ const tables = this.extractSchemas(Array.from(manifests.values()), options.dialect);
128
+ if (options.minimal) this.applyMinimalFilter(tables, options);
129
+ let stiTables = 0;
130
+ let totalObjects = 0;
131
+ for (const table of tables.values()) if (table.sources.length > 1) stiTables++;
132
+ for (const manifest of manifests.values()) totalObjects += Object.keys(manifest.objects).length;
133
+ return {
134
+ tables,
135
+ sql: this.generateSQL(tables, options),
136
+ manifests,
137
+ stats: {
138
+ packagesLoaded: manifests.size,
139
+ packagesSkipped,
140
+ totalObjects,
141
+ uniqueTables: tables.size,
142
+ stiTables
143
+ }
144
+ };
145
+ }
146
+ /**
147
+ * Load a manifest from local path or node_modules.
148
+ */
149
+ loadManifest(packageName, localPaths) {
150
+ if (localPaths?.[packageName]) {
151
+ const localPath = localPaths[packageName];
152
+ if (existsSync(localPath)) try {
153
+ const content = JSON.parse(readFileSync(localPath, "utf-8"));
154
+ if (Object.values(content.objects || {}).some((obj) => obj.schema?.ddl)) return content;
155
+ } catch {}
156
+ }
157
+ const manifest = loadExternalManifestSync(packageName);
158
+ if (manifest) return manifest;
159
+ const root = process.cwd();
160
+ const paths = [join(root, "node_modules", packageName, "dist", "manifest.json"), join(root, "node_modules", packageName, "manifest.json")];
161
+ for (const manifestPath of paths) if (existsSync(manifestPath)) try {
162
+ return JSON.parse(readFileSync(manifestPath, "utf-8"));
163
+ } catch {}
164
+ return null;
165
+ }
166
+ /**
167
+ * Extract schemas from manifests, deduplicating STI tables.
168
+ *
169
+ * For STI tables (shared by multiple classes across packages),
170
+ * keeps the most comprehensive DDL (largest) and merges indexes.
171
+ */
172
+ extractSchemas(manifests, dialect) {
173
+ const tables = /* @__PURE__ */ new Map();
174
+ const engine = dialect === "sqlite" ? "sqlite" : "postgres";
175
+ for (const manifest of manifests) for (const [_key, object] of Object.entries(manifest.objects)) {
176
+ const schema = object.schema;
177
+ if (!schema?.ddl) continue;
178
+ 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
+ });
203
+ }
204
+ }
205
+ return tables;
206
+ }
207
+ /**
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
+ * Apply minimal filtering to remove infrastructure/collection tables.
218
+ */
219
+ applyMinimalFilter(tables, options) {
220
+ const skipPatterns = options.minimalSkipPatterns || DEFAULT_MINIMAL_SKIP_PATTERNS;
221
+ const skipTables = new Set(options.minimalSkipTables || []);
222
+ for (const tableName of Array.from(tables.keys())) {
223
+ if (skipTables.has(tableName)) {
224
+ tables.delete(tableName);
225
+ continue;
226
+ }
227
+ for (const pattern of skipPatterns) if (pattern.test(tableName)) {
228
+ tables.delete(tableName);
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ /**
234
+ * Generate combined SQL from aggregated tables.
235
+ */
236
+ generateSQL(tables, options) {
237
+ const lines = [
238
+ "-- Combined SMRT Database Schema",
239
+ `-- Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
240
+ ""
241
+ ];
242
+ if (options.dialect === "sqlite") lines.push("PRAGMA foreign_keys = ON;", "");
243
+ const sortedTables = Array.from(tables.entries()).sort((a, b) => a[0].localeCompare(b[0]));
244
+ for (const [tableName, table] of sortedTables) {
245
+ lines.push(`-- Table: ${tableName}`);
246
+ if (table.sources.length > 1) lines.push(`-- STI table, sources: ${table.sources.join(", ")}`);
247
+ else lines.push(`-- Source: ${table.sources[0]}`);
248
+ lines.push(table.ddl);
249
+ lines.push("");
250
+ if (table.indexes.length > 0) {
251
+ for (const idx of table.indexes) lines.push(idx);
252
+ lines.push("");
253
+ }
254
+ }
255
+ return lines.join("\n");
256
+ }
257
+ };
258
+ //#endregion
259
+ export { SchemaAggregator };
260
+
261
+ //# sourceMappingURL=schema-aggregator.js.map
@@ -0,0 +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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"}
@@ -216,7 +216,17 @@ var SchemaManager = class {
216
216
  return sorted;
217
217
  }
218
218
  };
219
+ /**
220
+ * Create a SchemaManager instance
221
+ *
222
+ * @param db - Database interface
223
+ * @param options - Optional configuration
224
+ * @returns SchemaManager instance
225
+ */
226
+ function createSchemaManager(db, options) {
227
+ return new SchemaManager(db, options);
228
+ }
219
229
  //#endregion
220
- export { SchemaManager };
230
+ export { SchemaManager, createSchemaManager };
221
231
 
222
232
  //# sourceMappingURL=schema-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-manager.js","names":[],"sources":["../../src/schema/schema-manager.ts"],"sourcesContent":["/**\n * Schema Manager - Direct DDL Execution for SMRT\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n *\n * This module replaces the use of SDK's syncSchema() with direct DDL execution\n * using engine-specific DDL strategies.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n type DatabaseEngine,\n detectEngine,\n type EngineSpecificDDL,\n getDDLStrategy,\n} from './ddl/index.js';\nimport type { SchemaDefinition } from './types.js';\n\n/**\n * Normalize a `db.query` result into an array of rows.\n *\n * Adapters return either a bare row array or a `{ rows }` envelope. This\n * mirrors the prior inline `Array.isArray(result) ? result : result?.rows ?? []`\n * narrowing while keeping the row shape typed for callers.\n */\nfunction extractRows<Row>(result: unknown): Row[] {\n if (Array.isArray(result)) {\n return result as Row[];\n }\n const rows = (result as { rows?: unknown } | null | undefined)?.rows;\n return Array.isArray(rows) ? (rows as Row[]) : [];\n}\n\n/**\n * Schema Manager Options\n */\nexport interface SchemaManagerOptions {\n /** Force engine type (overrides auto-detection) */\n engine?: DatabaseEngine;\n /** Whether to skip triggers (useful for DuckDB-backed JSON) */\n skipTriggers?: boolean;\n /** Log DDL statements before execution */\n debug?: boolean;\n}\n\n/**\n * Schema Manager\n *\n * Handles direct DDL execution for database schema management.\n * Replaces SDK's syncSchema() with SMRT-controlled schema creation.\n */\nexport class SchemaManager {\n private db: DatabaseInterface;\n private engine: DatabaseEngine;\n private options: SchemaManagerOptions;\n // Per-instance logger: the `debug` option must actually emit debug traces, so\n // the level follows it (a fixed level: 'info' would filter every debug call).\n private logger: ReturnType<typeof createLogger>;\n\n constructor(db: DatabaseInterface, options: SchemaManagerOptions = {}) {\n this.db = db;\n this.options = options;\n this.logger = createLogger({ level: options.debug ? 'debug' : 'info' });\n\n // Detect or use provided engine\n if (options.engine) {\n this.engine = options.engine;\n } else if ((db as { exportTable?: unknown }).exportTable) {\n // JSON adapter detected (has unique exportTable method)\n // JSON adapter uses DuckDB internally, but native UUID values do not\n // round-trip through the SDK row objects as canonical UUID strings.\n this.engine = 'json';\n } else {\n this.engine = detectEngine(db.url);\n }\n }\n\n /**\n * Ensure a table exists with the correct schema\n *\n * If table doesn't exist, creates it with DDL from the engine-specific strategy.\n * If table exists but is missing columns, adds them via ALTER TABLE ADD COLUMN.\n *\n * @param schema - The schema definition\n */\n async ensureTable(schema: SchemaDefinition): Promise<void> {\n const { tableName } = schema;\n\n // Check if table already exists\n const exists = await this.db.tableExists(tableName);\n if (exists) {\n // Table exists — check for missing columns and add them\n // This handles the case where a table was created with a partial schema\n // (e.g., base SmrtObject fields only) and needs additional columns\n await this.addMissingColumns(tableName, schema);\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" already exists, checked for missing columns`,\n );\n }\n return;\n }\n\n // Generate DDL using engine-specific strategy\n const strategy = getDDLStrategy(this.engine);\n const ddl: EngineSpecificDDL = {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n\n // Prefer adapter's syncSchema if available (handles adapter-specific transformations)\n // This is critical for JSON adapter which needs to convert UNIQUE indexes to\n // inline constraints for DuckDB compatibility\n // Adapters that support direct DDL sync expose an optional `syncSchema`\n // method beyond the base `DatabaseInterface`; narrow to it here so both the\n // capability check and the call preserve `this.db` as the receiver.\n const dbWithSync = this.db as DatabaseInterface & {\n syncSchema?: (schema: string) => Promise<void>;\n };\n if (dbWithSync.syncSchema) {\n // Combine DDL into single schema string for adapter's syncSchema\n const fullSchema = [\n ddl.createTable,\n ...ddl.indexes,\n ...(this.options.skipTriggers ? [] : ddl.triggers),\n ].join(';\\n');\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Using adapter syncSchema for \"${tableName}\"`,\n { fullSchema },\n );\n }\n\n try {\n await dbWithSync.syncSchema(fullSchema);\n\n // FIX #735: Verify table was actually created\n // Some adapters (notably PostgreSQL) may silently fail to create tables\n // when syncSchema returns without error but doesn't execute the DDL\n const createdSuccessfully = await this.db.tableExists(tableName);\n if (createdSuccessfully) {\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created via adapter syncSchema`,\n );\n }\n return;\n }\n\n // Table wasn't created - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema returned but table \"${tableName}\" doesn't exist, falling back to direct DDL`,\n );\n } catch (syncError) {\n // syncSchema failed - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema failed for \"${tableName}\", falling back to direct DDL`,\n { error: syncError },\n );\n }\n }\n\n // Fallback: Execute DDL directly (for adapters without syncSchema)\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Creating table \"${tableName}\" for ${this.engine} (direct DDL)`,\n { createTable: ddl.createTable },\n );\n }\n\n await this.db.query(ddl.createTable);\n\n // Execute indexes\n for (const indexSQL of ddl.indexes) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating index`, { indexSQL });\n }\n await this.db.query(indexSQL);\n }\n\n // Execute triggers (unless skipped)\n if (!this.options.skipTriggers) {\n for (const triggerSQL of ddl.triggers) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating trigger`, { triggerSQL });\n }\n await this.db.query(triggerSQL);\n }\n }\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created successfully`,\n );\n }\n }\n\n /**\n * Get existing column names from a database table\n *\n * Uses engine-appropriate introspection:\n * - SQLite/DuckDB: PRAGMA table_info\n * - PostgreSQL: information_schema.columns\n *\n * @param tableName - Name of the table to inspect\n * @returns Set of existing column names (lowercase)\n */\n private async getExistingColumns(tableName: string): Promise<Set<string>> {\n const columns = new Set<string>();\n\n try {\n if (this.engine === 'postgres') {\n const result = await this.db.query(\n 'SELECT column_name FROM information_schema.columns WHERE table_name = $1',\n tableName,\n );\n const rows = extractRows<{ column_name: string }>(result);\n for (const row of rows) {\n columns.add(row.column_name);\n }\n } else {\n // SQLite and DuckDB use PRAGMA\n const result = await this.db.query(\n `PRAGMA table_info(${this.quoteIdentifier(tableName)})`,\n );\n const rows = extractRows<{ name: string }>(result);\n for (const row of rows) {\n columns.add(row.name);\n }\n }\n } catch (error) {\n // If introspection fails, return empty set (no columns will be added)\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to introspect columns for \"${tableName}\"`,\n { error },\n );\n }\n }\n\n return columns;\n }\n\n /**\n * Add missing columns to an existing table via ALTER TABLE ADD COLUMN\n *\n * Compares the schema's expected columns against the table's actual columns\n * and adds any that are missing. This handles the race condition where a table\n * is created with base fields by one code path, then a later code path needs\n * additional columns from a more complete schema.\n *\n * SQLite limitation: ALTER TABLE ADD COLUMN cannot add NOT NULL columns\n * without a DEFAULT value. We relax NOT NULL for such columns.\n *\n * @param tableName - Name of the existing table\n * @param schema - The full schema definition with expected columns\n */\n private async addMissingColumns(\n tableName: string,\n schema: SchemaDefinition,\n ): Promise<void> {\n if (!schema.columns || Object.keys(schema.columns).length === 0) {\n return;\n }\n\n const existingColumns = await this.getExistingColumns(tableName);\n if (existingColumns.size === 0) {\n // Introspection failed or returned no columns — skip to avoid errors\n return;\n }\n\n const strategy = getDDLStrategy(this.engine);\n let addedCount = 0;\n\n for (const [colName, colDef] of Object.entries(schema.columns)) {\n if (existingColumns.has(colName)) {\n continue; // Column already exists\n }\n\n // SQLite limitation: ALTER TABLE ADD COLUMN cannot have NOT NULL\n // without a DEFAULT value. Relax NOT NULL for such columns.\n const safeDef = { ...colDef };\n if (\n safeDef.notNull &&\n safeDef.defaultValue === undefined &&\n !safeDef.primaryKey\n ) {\n safeDef.notNull = false;\n }\n\n // Cannot add PRIMARY KEY columns via ALTER TABLE\n if (safeDef.primaryKey) {\n continue;\n }\n\n const colSQL = strategy.generateColumnDefinition(colName, safeDef);\n const alterSQL = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${colSQL}`;\n\n try {\n await this.db.query(alterSQL);\n addedCount++;\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added column \"${colName}\" to \"${tableName}\"`,\n );\n }\n } catch (error) {\n // Column might already exist (race condition) or other issue\n // Log and continue — don't fail the entire operation\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to add column \"${colName}\" to \"${tableName}\"`,\n { error },\n );\n }\n }\n }\n\n if (addedCount > 0 && this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added ${addedCount} missing column(s) to \"${tableName}\"`,\n );\n }\n }\n\n private quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Create multiple tables with dependency ordering\n *\n * @param schemas - Array of schema definitions\n */\n async ensureTables(schemas: SchemaDefinition[]): Promise<void> {\n // Sort by dependencies (topological sort)\n const sorted = this.sortByDependencies(schemas);\n\n for (const schema of sorted) {\n await this.ensureTable(schema);\n }\n }\n\n /**\n * Generate DDL without executing (useful for debugging/preview)\n *\n * @param schema - The schema definition\n * @returns Engine-specific DDL\n */\n generateDDL(schema: SchemaDefinition): EngineSpecificDDL {\n const strategy = getDDLStrategy(this.engine);\n return {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n }\n\n /**\n * Get the detected/configured database engine\n */\n getEngine(): DatabaseEngine {\n return this.engine;\n }\n\n /**\n * Sort schemas by dependencies (topological sort)\n *\n * Ensures tables are created in the correct order based on foreign key dependencies.\n */\n private sortByDependencies(schemas: SchemaDefinition[]): SchemaDefinition[] {\n const byName = new Map<string, SchemaDefinition>();\n const sorted: SchemaDefinition[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n // Index by table name\n for (const schema of schemas) {\n byName.set(schema.tableName, schema);\n }\n\n // DFS topological sort\n const visit = (tableName: string) => {\n if (visited.has(tableName)) return;\n if (visiting.has(tableName)) {\n // Circular dependency - continue anyway (FK might be nullable)\n this.logger.warn(\n `[SchemaManager] Circular dependency detected involving ${tableName}`,\n );\n return;\n }\n\n const schema = byName.get(tableName);\n if (!schema) return; // External dependency, skip\n\n visiting.add(tableName);\n\n // Visit dependencies first\n for (const dep of schema.dependencies || []) {\n visit(dep);\n }\n\n visiting.delete(tableName);\n visited.add(tableName);\n sorted.push(schema);\n };\n\n // Visit all schemas\n for (const schema of schemas) {\n visit(schema.tableName);\n }\n\n return sorted;\n }\n}\n\n/**\n * Create a SchemaManager instance\n *\n * @param db - Database interface\n * @param options - Optional configuration\n * @returns SchemaManager instance\n */\nexport function createSchemaManager(\n db: DatabaseInterface,\n options?: SchemaManagerOptions,\n): SchemaManager {\n return new SchemaManager(db, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"schema-manager.js","names":[],"sources":["../../src/schema/schema-manager.ts"],"sourcesContent":["/**\n * Schema Manager - Direct DDL Execution for SMRT\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n *\n * This module replaces the use of SDK's syncSchema() with direct DDL execution\n * using engine-specific DDL strategies.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n type DatabaseEngine,\n detectEngine,\n type EngineSpecificDDL,\n getDDLStrategy,\n} from './ddl/index.js';\nimport type { SchemaDefinition } from './types.js';\n\n/**\n * Normalize a `db.query` result into an array of rows.\n *\n * Adapters return either a bare row array or a `{ rows }` envelope. This\n * mirrors the prior inline `Array.isArray(result) ? result : result?.rows ?? []`\n * narrowing while keeping the row shape typed for callers.\n */\nfunction extractRows<Row>(result: unknown): Row[] {\n if (Array.isArray(result)) {\n return result as Row[];\n }\n const rows = (result as { rows?: unknown } | null | undefined)?.rows;\n return Array.isArray(rows) ? (rows as Row[]) : [];\n}\n\n/**\n * Schema Manager Options\n */\nexport interface SchemaManagerOptions {\n /** Force engine type (overrides auto-detection) */\n engine?: DatabaseEngine;\n /** Whether to skip triggers (useful for DuckDB-backed JSON) */\n skipTriggers?: boolean;\n /** Log DDL statements before execution */\n debug?: boolean;\n}\n\n/**\n * Schema Manager\n *\n * Handles direct DDL execution for database schema management.\n * Replaces SDK's syncSchema() with SMRT-controlled schema creation.\n */\nexport class SchemaManager {\n private db: DatabaseInterface;\n private engine: DatabaseEngine;\n private options: SchemaManagerOptions;\n // Per-instance logger: the `debug` option must actually emit debug traces, so\n // the level follows it (a fixed level: 'info' would filter every debug call).\n private logger: ReturnType<typeof createLogger>;\n\n constructor(db: DatabaseInterface, options: SchemaManagerOptions = {}) {\n this.db = db;\n this.options = options;\n this.logger = createLogger({ level: options.debug ? 'debug' : 'info' });\n\n // Detect or use provided engine\n if (options.engine) {\n this.engine = options.engine;\n } else if ((db as { exportTable?: unknown }).exportTable) {\n // JSON adapter detected (has unique exportTable method)\n // JSON adapter uses DuckDB internally, but native UUID values do not\n // round-trip through the SDK row objects as canonical UUID strings.\n this.engine = 'json';\n } else {\n this.engine = detectEngine(db.url);\n }\n }\n\n /**\n * Ensure a table exists with the correct schema\n *\n * If table doesn't exist, creates it with DDL from the engine-specific strategy.\n * If table exists but is missing columns, adds them via ALTER TABLE ADD COLUMN.\n *\n * @param schema - The schema definition\n */\n async ensureTable(schema: SchemaDefinition): Promise<void> {\n const { tableName } = schema;\n\n // Check if table already exists\n const exists = await this.db.tableExists(tableName);\n if (exists) {\n // Table exists — check for missing columns and add them\n // This handles the case where a table was created with a partial schema\n // (e.g., base SmrtObject fields only) and needs additional columns\n await this.addMissingColumns(tableName, schema);\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" already exists, checked for missing columns`,\n );\n }\n return;\n }\n\n // Generate DDL using engine-specific strategy\n const strategy = getDDLStrategy(this.engine);\n const ddl: EngineSpecificDDL = {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n\n // Prefer adapter's syncSchema if available (handles adapter-specific transformations)\n // This is critical for JSON adapter which needs to convert UNIQUE indexes to\n // inline constraints for DuckDB compatibility\n // Adapters that support direct DDL sync expose an optional `syncSchema`\n // method beyond the base `DatabaseInterface`; narrow to it here so both the\n // capability check and the call preserve `this.db` as the receiver.\n const dbWithSync = this.db as DatabaseInterface & {\n syncSchema?: (schema: string) => Promise<void>;\n };\n if (dbWithSync.syncSchema) {\n // Combine DDL into single schema string for adapter's syncSchema\n const fullSchema = [\n ddl.createTable,\n ...ddl.indexes,\n ...(this.options.skipTriggers ? [] : ddl.triggers),\n ].join(';\\n');\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Using adapter syncSchema for \"${tableName}\"`,\n { fullSchema },\n );\n }\n\n try {\n await dbWithSync.syncSchema(fullSchema);\n\n // FIX #735: Verify table was actually created\n // Some adapters (notably PostgreSQL) may silently fail to create tables\n // when syncSchema returns without error but doesn't execute the DDL\n const createdSuccessfully = await this.db.tableExists(tableName);\n if (createdSuccessfully) {\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created via adapter syncSchema`,\n );\n }\n return;\n }\n\n // Table wasn't created - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema returned but table \"${tableName}\" doesn't exist, falling back to direct DDL`,\n );\n } catch (syncError) {\n // syncSchema failed - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema failed for \"${tableName}\", falling back to direct DDL`,\n { error: syncError },\n );\n }\n }\n\n // Fallback: Execute DDL directly (for adapters without syncSchema)\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Creating table \"${tableName}\" for ${this.engine} (direct DDL)`,\n { createTable: ddl.createTable },\n );\n }\n\n await this.db.query(ddl.createTable);\n\n // Execute indexes\n for (const indexSQL of ddl.indexes) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating index`, { indexSQL });\n }\n await this.db.query(indexSQL);\n }\n\n // Execute triggers (unless skipped)\n if (!this.options.skipTriggers) {\n for (const triggerSQL of ddl.triggers) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating trigger`, { triggerSQL });\n }\n await this.db.query(triggerSQL);\n }\n }\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created successfully`,\n );\n }\n }\n\n /**\n * Get existing column names from a database table\n *\n * Uses engine-appropriate introspection:\n * - SQLite/DuckDB: PRAGMA table_info\n * - PostgreSQL: information_schema.columns\n *\n * @param tableName - Name of the table to inspect\n * @returns Set of existing column names (lowercase)\n */\n private async getExistingColumns(tableName: string): Promise<Set<string>> {\n const columns = new Set<string>();\n\n try {\n if (this.engine === 'postgres') {\n const result = await this.db.query(\n 'SELECT column_name FROM information_schema.columns WHERE table_name = $1',\n tableName,\n );\n const rows = extractRows<{ column_name: string }>(result);\n for (const row of rows) {\n columns.add(row.column_name);\n }\n } else {\n // SQLite and DuckDB use PRAGMA\n const result = await this.db.query(\n `PRAGMA table_info(${this.quoteIdentifier(tableName)})`,\n );\n const rows = extractRows<{ name: string }>(result);\n for (const row of rows) {\n columns.add(row.name);\n }\n }\n } catch (error) {\n // If introspection fails, return empty set (no columns will be added)\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to introspect columns for \"${tableName}\"`,\n { error },\n );\n }\n }\n\n return columns;\n }\n\n /**\n * Add missing columns to an existing table via ALTER TABLE ADD COLUMN\n *\n * Compares the schema's expected columns against the table's actual columns\n * and adds any that are missing. This handles the race condition where a table\n * is created with base fields by one code path, then a later code path needs\n * additional columns from a more complete schema.\n *\n * SQLite limitation: ALTER TABLE ADD COLUMN cannot add NOT NULL columns\n * without a DEFAULT value. We relax NOT NULL for such columns.\n *\n * @param tableName - Name of the existing table\n * @param schema - The full schema definition with expected columns\n */\n private async addMissingColumns(\n tableName: string,\n schema: SchemaDefinition,\n ): Promise<void> {\n if (!schema.columns || Object.keys(schema.columns).length === 0) {\n return;\n }\n\n const existingColumns = await this.getExistingColumns(tableName);\n if (existingColumns.size === 0) {\n // Introspection failed or returned no columns — skip to avoid errors\n return;\n }\n\n const strategy = getDDLStrategy(this.engine);\n let addedCount = 0;\n\n for (const [colName, colDef] of Object.entries(schema.columns)) {\n if (existingColumns.has(colName)) {\n continue; // Column already exists\n }\n\n // SQLite limitation: ALTER TABLE ADD COLUMN cannot have NOT NULL\n // without a DEFAULT value. Relax NOT NULL for such columns.\n const safeDef = { ...colDef };\n if (\n safeDef.notNull &&\n safeDef.defaultValue === undefined &&\n !safeDef.primaryKey\n ) {\n safeDef.notNull = false;\n }\n\n // Cannot add PRIMARY KEY columns via ALTER TABLE\n if (safeDef.primaryKey) {\n continue;\n }\n\n const colSQL = strategy.generateColumnDefinition(colName, safeDef);\n const alterSQL = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${colSQL}`;\n\n try {\n await this.db.query(alterSQL);\n addedCount++;\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added column \"${colName}\" to \"${tableName}\"`,\n );\n }\n } catch (error) {\n // Column might already exist (race condition) or other issue\n // Log and continue — don't fail the entire operation\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to add column \"${colName}\" to \"${tableName}\"`,\n { error },\n );\n }\n }\n }\n\n if (addedCount > 0 && this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added ${addedCount} missing column(s) to \"${tableName}\"`,\n );\n }\n }\n\n private quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Create multiple tables with dependency ordering\n *\n * @param schemas - Array of schema definitions\n */\n async ensureTables(schemas: SchemaDefinition[]): Promise<void> {\n // Sort by dependencies (topological sort)\n const sorted = this.sortByDependencies(schemas);\n\n for (const schema of sorted) {\n await this.ensureTable(schema);\n }\n }\n\n /**\n * Generate DDL without executing (useful for debugging/preview)\n *\n * @param schema - The schema definition\n * @returns Engine-specific DDL\n */\n generateDDL(schema: SchemaDefinition): EngineSpecificDDL {\n const strategy = getDDLStrategy(this.engine);\n return {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n }\n\n /**\n * Get the detected/configured database engine\n */\n getEngine(): DatabaseEngine {\n return this.engine;\n }\n\n /**\n * Sort schemas by dependencies (topological sort)\n *\n * Ensures tables are created in the correct order based on foreign key dependencies.\n */\n private sortByDependencies(schemas: SchemaDefinition[]): SchemaDefinition[] {\n const byName = new Map<string, SchemaDefinition>();\n const sorted: SchemaDefinition[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n // Index by table name\n for (const schema of schemas) {\n byName.set(schema.tableName, schema);\n }\n\n // DFS topological sort\n const visit = (tableName: string) => {\n if (visited.has(tableName)) return;\n if (visiting.has(tableName)) {\n // Circular dependency - continue anyway (FK might be nullable)\n this.logger.warn(\n `[SchemaManager] Circular dependency detected involving ${tableName}`,\n );\n return;\n }\n\n const schema = byName.get(tableName);\n if (!schema) return; // External dependency, skip\n\n visiting.add(tableName);\n\n // Visit dependencies first\n for (const dep of schema.dependencies || []) {\n visit(dep);\n }\n\n visiting.delete(tableName);\n visited.add(tableName);\n sorted.push(schema);\n };\n\n // Visit all schemas\n for (const schema of schemas) {\n visit(schema.tableName);\n }\n\n return sorted;\n }\n}\n\n/**\n * Create a SchemaManager instance\n *\n * @param db - Database interface\n * @param options - Optional configuration\n * @returns SchemaManager instance\n */\nexport function createSchemaManager(\n db: DatabaseInterface,\n options?: SchemaManagerOptions,\n): SchemaManager {\n return new SchemaManager(db, options);\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { DatabaseInterface } from '@happyvertical/sql';
2
2
  import { SmrtObject } from '../object.js';
3
3
  import { FieldDefinition } from '../scanner/types.js';
4
4
  import { DatabaseEngine } from './ddl/types.js';
5
+ export { materializeManifestDDLForEngine, tokenizeSQLDDLBody, } from './ddl/materialize-manifest.js';
5
6
  export { isJsonPathIndex, renderIndexTarget } from './index-utils.js';
6
7
  /**
7
8
  * Generates a complete database schema SQL statement for a class
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/schema/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKrD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAMlC,SAAS,EAAE;IAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACxE,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAC7C,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,cAAc,CAAA;CAAO,mBAyI1C;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyEf"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/schema/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAMlC,SAAS,EAAE;IAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACxE,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAC7C,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,cAAc,CAAA;CAAO,mBAyI1C;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyEf"}
@@ -2,6 +2,7 @@ import { isJsonPathIndex, renderIndexTarget } from "./index-utils.js";
2
2
  import { tableNameFromClass } from "../utils.js";
3
3
  import { ObjectRegistry } from "../registry.js";
4
4
  import { SchemaManager } from "./schema-manager.js";
5
+ import { materializeManifestDDLForEngine, tokenizeSQLDDLBody } from "./ddl/materialize-manifest.js";
5
6
  //#region src/schema/utils.ts
6
7
  /**
7
8
  * Generates a complete database schema SQL statement for a class
@@ -97,6 +98,6 @@ async function ensureSchema(db, className) {
97
98
  await new SchemaManager(db, { skipTriggers: typeof db.exportTable === "function" }).ensureTable(effectiveSchemaDefinition);
98
99
  }
99
100
  //#endregion
100
- export { ensureSchema, generateSchema, isJsonPathIndex, renderIndexTarget };
101
+ export { ensureSchema, generateSchema, isJsonPathIndex, materializeManifestDDLForEngine, renderIndexTarget, tokenizeSQLDDLBody };
101
102
 
102
103
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../src/schema/utils.ts"],"sourcesContent":["/**\n * Schema generation utilities - Node.js only\n *\n * These functions use SchemaGenerator which depends on node:crypto.\n * Separated from main utils.ts to prevent bundling in browser builds.\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtObject } from '../object.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { FieldDefinition } from '../scanner/types.js';\nimport { tableNameFromClass } from '../utils.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport { SchemaManager } from './schema-manager.js';\n\n// Index-rendering helpers live in a separate file to avoid pulling the\n// heavyweight registry/collection module graph into the DDL strategies.\nexport { isJsonPathIndex, renderIndexTarget } from './index-utils.js';\n\n/**\n * Generates a complete database schema SQL statement for a class\n *\n * This is a thin wrapper around SchemaGenerator that provides the\n * single source of truth for schema generation. Uses ObjectRegistry\n * cached fields from AST manifest for consistent schema generation.\n *\n * **Note**: Uses dynamic import for SchemaGenerator to avoid bundling\n * Node.js-only code (node:crypto) in browser builds.\n *\n * @param ClassType - Class constructor to generate schema for\n * @param providedFields - Optional fields map (used during registration)\n * @returns SQL schema creation statement with CREATE TABLE and CREATE INDEX statements\n */\nexport async function generateSchema(\n // Accepts any SmrtObject constructor: the registry-stored `typeof SmrtObject`\n // as well as collection item classes whose construct signature is narrower\n // than the full static surface. Only `.name` and the construct identity are\n // used here. `never[]` keeps the construct signature contravariantly\n // compatible with constructors that declare their own argument shapes.\n ClassType: { new (...args: never[]): SmrtObject; readonly name: string },\n providedFields?: Map<string, FieldDefinition>,\n options: { engine?: DatabaseEngine } = {},\n) {\n const className = ClassType.name;\n const tableName = tableNameFromClass(ClassType);\n\n // For external packages, ensure manifest is loaded before proceeding\n if (!providedFields || providedFields.size === 0) {\n await ObjectRegistry.ensureManifestLoaded(className);\n }\n\n // Use provided fields if available AND non-empty (during registration), otherwise get from registry\n // NEW: Use getAllFields() to include inherited fields from parent classes\n const cachedFields =\n providedFields && providedFields.size > 0\n ? providedFields\n : await ObjectRegistry.getAllFields(className);\n\n // Throw error if no fields found\n if (cachedFields.size === 0) {\n // Detect if running in test environment\n const testGlobals = globalThis as {\n describe?: unknown;\n it?: unknown;\n };\n const isTestEnv =\n process.env.NODE_ENV === 'test' ||\n process.env.VITEST === 'true' ||\n typeof testGlobals.describe !== 'undefined' ||\n typeof testGlobals.it !== 'undefined';\n\n const testHint = isTestEnv\n ? `\\n\\n⚠️ Are you using 'smrt test'? ` +\n `Tests require manifest generation.\\n` +\n ` ✅ Use: smrt test\\n` +\n ` ❌ NOT: npx vitest\\n`\n : '';\n\n // Check if class is actually registered (decorator ran but no fields loaded)\n const isRegistered = ObjectRegistry.hasClass(className);\n\n if (isRegistered) {\n // Class registered but no fields - manifest problem\n throw new Error(\n `No field metadata found for class '${className}'. ` +\n `The class is registered (decorator ran) but has no field definitions. ` +\n `This usually means the manifest file is missing or stale.` +\n testHint,\n );\n } else {\n // Class not registered - decorator never ran\n throw new Error(\n `Cannot generate schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() for schema generation to work. ` +\n `Runtime introspection has been removed per issue #131.` +\n testHint,\n );\n }\n }\n\n // Check if class uses STI strategy\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n\n // Dynamic import SchemaGenerator (Node.js-only, uses node:crypto)\n // This prevents bundling it into browser builds\n const { SchemaGenerator } = await import('./generator.js');\n const generator = new SchemaGenerator();\n const registeredClass = ObjectRegistry.getClass(className);\n const runtimeSchemaConfig = registeredClass?.config\n ? {\n conflictColumns: registeredClass.config.conflictColumns,\n idType: registeredClass.config.idType,\n registry: ObjectRegistry,\n }\n : undefined;\n\n let schemaDefinition: Awaited<\n ReturnType<\n InstanceType<typeof SchemaGenerator>['generateSchemaFromRegistry']\n >\n >;\n\n if (tableStrategy === 'sti') {\n // STI: Generate shared table for base class\n const stiBase = ObjectRegistry.getSTIBase(className);\n\n if (!stiBase) {\n throw new Error(\n `STI strategy detected for '${className}' but no STI base class found. ` +\n `This should not happen - please report this bug.`,\n );\n }\n\n // Only generate schema for the base class (not for children).\n // R5-canon: `getSTIBase` returns the qualified name; compare against\n // the qualified form of this class so an STI base isn't\n // mis-classified as a subclass and skipped.\n const qualifiedClassName =\n registeredClass?.qualifiedName ?? registeredClass?.name ?? className;\n if (qualifiedClassName === stiBase || className === stiBase) {\n // This is the base class - generate STI schema\n schemaDefinition = await generator.generateSTISchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n } else {\n // This is a child class - return null or empty schema\n // The base class schema already includes all fields\n // Child classes don't need their own tables\n return ''; // Empty SQL - table already created by base class\n }\n } else {\n // CTI: Generate separate table for each class\n schemaDefinition = generator.generateSchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n }\n\n // Store the full schema definition in the registry\n // This is critical for:\n // - STI tables where descendants have additional columns (issue #427)\n // - Per-engine DDL generation via SchemaManager\n if (registeredClass) {\n // Store the full SchemaDefinition for SchemaManager to use\n registeredClass.schema = schemaDefinition;\n // Also store generated DDL for backward compatibility\n registeredClass.schema.ddl = generator.generateSQL(\n schemaDefinition,\n 'sqlite',\n );\n }\n\n return generator.generateSQL(schemaDefinition, options.engine);\n}\n\n/**\n * Ensure schema exists for a registered class.\n *\n * This compatibility helper is kept for tooling flows such as CLI commands that\n * explicitly prepare schema ahead of runtime. Core runtime no longer calls this\n * automatically.\n *\n * @deprecated Prefer explicit migration/bootstrap tooling. Runtime verifies\n * schema and fails fast when tables are missing.\n */\nexport async function ensureSchema(\n db: DatabaseInterface,\n className: string,\n): Promise<void> {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n throw new Error(\n `Cannot ensure schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() and registered in the ObjectRegistry.`,\n );\n }\n\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(className);\n // R5-canon: `getSTIBase` returns the qualified name. Compare\n // against the qualified form of `className` so an STI base isn't\n // mis-classified as a child and recursed on. Falls back to a\n // simple-name compare for classes without a package context.\n const qualifiedClassName =\n registered.qualifiedName ?? registered.name ?? className;\n if (stiBase && stiBase !== qualifiedClassName && stiBase !== className) {\n await ensureSchema(db, stiBase);\n return;\n }\n }\n\n let schemaDefinition = ObjectRegistry.getSchema(className);\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n const providedFields =\n registered.fields.size > 0 ? registered.fields : undefined;\n await generateSchema(registered.constructor, providedFields);\n schemaDefinition = ObjectRegistry.getSchema(className);\n\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n throw new Error(\n `No schema definition found for class '${className}'. ` +\n `Run the manifest generation/build step before preparing schema.`,\n );\n }\n\n // Use the merged table definition for shared tables (especially STI).\n // Per-class schema metadata can reflect only the current class, while\n // the database table must include columns from all classes sharing it.\n const mergedSchemaDefinition =\n ObjectRegistry.getAllSchemasAsDefinitions()[schemaDefinition.tableName];\n const effectiveSchemaDefinition = mergedSchemaDefinition ?? schemaDefinition;\n\n const schemaManager = new SchemaManager(db, {\n skipTriggers:\n typeof (db as { exportTable?: unknown }).exportTable === 'function',\n });\n await schemaManager.ensureTable(effectiveSchemaDefinition);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../src/schema/utils.ts"],"sourcesContent":["/**\n * Schema generation utilities - Node.js only\n *\n * These functions use SchemaGenerator which depends on node:crypto.\n * Separated from main utils.ts to prevent bundling in browser builds.\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtObject } from '../object.js';\nimport { ObjectRegistry } from '../registry.js';\nimport type { FieldDefinition } from '../scanner/types.js';\nimport { tableNameFromClass } from '../utils.js';\nimport type { DatabaseEngine } from './ddl/types.js';\nimport { SchemaManager } from './schema-manager.js';\n\nexport {\n materializeManifestDDLForEngine,\n tokenizeSQLDDLBody,\n} from './ddl/materialize-manifest.js';\n// Index-rendering helpers live in a separate file to avoid pulling the\n// heavyweight registry/collection module graph into the DDL strategies.\nexport { isJsonPathIndex, renderIndexTarget } from './index-utils.js';\n\n/**\n * Generates a complete database schema SQL statement for a class\n *\n * This is a thin wrapper around SchemaGenerator that provides the\n * single source of truth for schema generation. Uses ObjectRegistry\n * cached fields from AST manifest for consistent schema generation.\n *\n * **Note**: Uses dynamic import for SchemaGenerator to avoid bundling\n * Node.js-only code (node:crypto) in browser builds.\n *\n * @param ClassType - Class constructor to generate schema for\n * @param providedFields - Optional fields map (used during registration)\n * @returns SQL schema creation statement with CREATE TABLE and CREATE INDEX statements\n */\nexport async function generateSchema(\n // Accepts any SmrtObject constructor: the registry-stored `typeof SmrtObject`\n // as well as collection item classes whose construct signature is narrower\n // than the full static surface. Only `.name` and the construct identity are\n // used here. `never[]` keeps the construct signature contravariantly\n // compatible with constructors that declare their own argument shapes.\n ClassType: { new (...args: never[]): SmrtObject; readonly name: string },\n providedFields?: Map<string, FieldDefinition>,\n options: { engine?: DatabaseEngine } = {},\n) {\n const className = ClassType.name;\n const tableName = tableNameFromClass(ClassType);\n\n // For external packages, ensure manifest is loaded before proceeding\n if (!providedFields || providedFields.size === 0) {\n await ObjectRegistry.ensureManifestLoaded(className);\n }\n\n // Use provided fields if available AND non-empty (during registration), otherwise get from registry\n // NEW: Use getAllFields() to include inherited fields from parent classes\n const cachedFields =\n providedFields && providedFields.size > 0\n ? providedFields\n : await ObjectRegistry.getAllFields(className);\n\n // Throw error if no fields found\n if (cachedFields.size === 0) {\n // Detect if running in test environment\n const testGlobals = globalThis as {\n describe?: unknown;\n it?: unknown;\n };\n const isTestEnv =\n process.env.NODE_ENV === 'test' ||\n process.env.VITEST === 'true' ||\n typeof testGlobals.describe !== 'undefined' ||\n typeof testGlobals.it !== 'undefined';\n\n const testHint = isTestEnv\n ? `\\n\\n⚠️ Are you using 'smrt test'? ` +\n `Tests require manifest generation.\\n` +\n ` ✅ Use: smrt test\\n` +\n ` ❌ NOT: npx vitest\\n`\n : '';\n\n // Check if class is actually registered (decorator ran but no fields loaded)\n const isRegistered = ObjectRegistry.hasClass(className);\n\n if (isRegistered) {\n // Class registered but no fields - manifest problem\n throw new Error(\n `No field metadata found for class '${className}'. ` +\n `The class is registered (decorator ran) but has no field definitions. ` +\n `This usually means the manifest file is missing or stale.` +\n testHint,\n );\n } else {\n // Class not registered - decorator never ran\n throw new Error(\n `Cannot generate schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() for schema generation to work. ` +\n `Runtime introspection has been removed per issue #131.` +\n testHint,\n );\n }\n }\n\n // Check if class uses STI strategy\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n\n // Dynamic import SchemaGenerator (Node.js-only, uses node:crypto)\n // This prevents bundling it into browser builds\n const { SchemaGenerator } = await import('./generator.js');\n const generator = new SchemaGenerator();\n const registeredClass = ObjectRegistry.getClass(className);\n const runtimeSchemaConfig = registeredClass?.config\n ? {\n conflictColumns: registeredClass.config.conflictColumns,\n idType: registeredClass.config.idType,\n registry: ObjectRegistry,\n }\n : undefined;\n\n let schemaDefinition: Awaited<\n ReturnType<\n InstanceType<typeof SchemaGenerator>['generateSchemaFromRegistry']\n >\n >;\n\n if (tableStrategy === 'sti') {\n // STI: Generate shared table for base class\n const stiBase = ObjectRegistry.getSTIBase(className);\n\n if (!stiBase) {\n throw new Error(\n `STI strategy detected for '${className}' but no STI base class found. ` +\n `This should not happen - please report this bug.`,\n );\n }\n\n // Only generate schema for the base class (not for children).\n // R5-canon: `getSTIBase` returns the qualified name; compare against\n // the qualified form of this class so an STI base isn't\n // mis-classified as a subclass and skipped.\n const qualifiedClassName =\n registeredClass?.qualifiedName ?? registeredClass?.name ?? className;\n if (qualifiedClassName === stiBase || className === stiBase) {\n // This is the base class - generate STI schema\n schemaDefinition = await generator.generateSTISchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n } else {\n // This is a child class - return null or empty schema\n // The base class schema already includes all fields\n // Child classes don't need their own tables\n return ''; // Empty SQL - table already created by base class\n }\n } else {\n // CTI: Generate separate table for each class\n schemaDefinition = generator.generateSchemaFromRegistry(\n className,\n tableName,\n cachedFields,\n runtimeSchemaConfig,\n );\n }\n\n // Store the full schema definition in the registry\n // This is critical for:\n // - STI tables where descendants have additional columns (issue #427)\n // - Per-engine DDL generation via SchemaManager\n if (registeredClass) {\n // Store the full SchemaDefinition for SchemaManager to use\n registeredClass.schema = schemaDefinition;\n // Also store generated DDL for backward compatibility\n registeredClass.schema.ddl = generator.generateSQL(\n schemaDefinition,\n 'sqlite',\n );\n }\n\n return generator.generateSQL(schemaDefinition, options.engine);\n}\n\n/**\n * Ensure schema exists for a registered class.\n *\n * This compatibility helper is kept for tooling flows such as CLI commands that\n * explicitly prepare schema ahead of runtime. Core runtime no longer calls this\n * automatically.\n *\n * @deprecated Prefer explicit migration/bootstrap tooling. Runtime verifies\n * schema and fails fast when tables are missing.\n */\nexport async function ensureSchema(\n db: DatabaseInterface,\n className: string,\n): Promise<void> {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n throw new Error(\n `Cannot ensure schema for unregistered class '${className}'. ` +\n `Ensure the class is decorated with @smrt() and registered in the ObjectRegistry.`,\n );\n }\n\n const tableStrategy = ObjectRegistry.getTableStrategy(className);\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(className);\n // R5-canon: `getSTIBase` returns the qualified name. Compare\n // against the qualified form of `className` so an STI base isn't\n // mis-classified as a child and recursed on. Falls back to a\n // simple-name compare for classes without a package context.\n const qualifiedClassName =\n registered.qualifiedName ?? registered.name ?? className;\n if (stiBase && stiBase !== qualifiedClassName && stiBase !== className) {\n await ensureSchema(db, stiBase);\n return;\n }\n }\n\n let schemaDefinition = ObjectRegistry.getSchema(className);\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n const providedFields =\n registered.fields.size > 0 ? registered.fields : undefined;\n await generateSchema(registered.constructor, providedFields);\n schemaDefinition = ObjectRegistry.getSchema(className);\n\n if (tableStrategy === 'sti') {\n const tableName = ObjectRegistry.getTableName(className);\n if (tableName) {\n const mergedSchema =\n ObjectRegistry.getAllSchemasAsDefinitions()[tableName];\n if (mergedSchema) {\n schemaDefinition = mergedSchema;\n }\n }\n }\n }\n\n if (!schemaDefinition?.tableName) {\n throw new Error(\n `No schema definition found for class '${className}'. ` +\n `Run the manifest generation/build step before preparing schema.`,\n );\n }\n\n // Use the merged table definition for shared tables (especially STI).\n // Per-class schema metadata can reflect only the current class, while\n // the database table must include columns from all classes sharing it.\n const mergedSchemaDefinition =\n ObjectRegistry.getAllSchemasAsDefinitions()[schemaDefinition.tableName];\n const effectiveSchemaDefinition = mergedSchemaDefinition ?? schemaDefinition;\n\n const schemaManager = new SchemaManager(db, {\n skipTriggers:\n typeof (db as { exportTable?: unknown }).exportTable === 'function',\n });\n await schemaManager.ensureTable(effectiveSchemaDefinition);\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from './schema/index.js';
2
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,13 @@
1
+ import { BaseDDLStrategy } from "./schema/ddl/base-strategy.js";
2
+ import { DuckDBStrategy } from "./schema/ddl/duckdb-strategy.js";
3
+ import { JsonDuckDBStrategy } from "./schema/ddl/json-duckdb-strategy.js";
4
+ import { PostgresStrategy } from "./schema/ddl/postgres-strategy.js";
5
+ import { SQLiteStrategy } from "./schema/ddl/sqlite-strategy.js";
6
+ import { detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy } from "./schema/ddl/index.js";
7
+ import { SchemaGenerator } from "./schema/generator.js";
8
+ import { SchemaManager, createSchemaManager } from "./schema/schema-manager.js";
9
+ import { SchemaCodeGenerator } from "./schema/code-generator.js";
10
+ import { SchemaOverrideSystem } from "./schema/override-system.js";
11
+ import { SchemaAggregator } from "./schema/schema-aggregator.js";
12
+ import "./schema/index.js";
13
+ export { BaseDDLStrategy, DuckDBStrategy, JsonDuckDBStrategy, PostgresStrategy, SQLiteStrategy, SchemaAggregator, SchemaCodeGenerator, SchemaGenerator, SchemaManager, SchemaOverrideSystem, createSchemaManager, detectEngine, generateDDLForEngine, generateMultiEngineDDL, getAllStrategies, getDDLStrategy };
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-19T20:16:53.140Z",
3
+ "generatedAt": "2026-07-20T07:54:57.646Z",
4
4
  "packageName": "@happyvertical/smrt-core",
5
- "packageVersion": "0.40.15",
5
+ "packageVersion": "0.40.16",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "ed4e75275272dc4f148ae0b548d249f383488892067701bcc88185a56eed2b4b",
10
- "packageJson": "c7084b29e477b54c719233867cb25b24f6048b64a9802d5decf403496e1ea59e",
9
+ "manifest": "b62db27abc29af7cae6c40dfed07d3acf9b783a9cd497138f5c94816507f9873",
10
+ "packageJson": "1e806f1527ab791b537f584c5fadea9db772addf2e90b059cc7bf09389a788de",
11
11
  "agents": "47595afe250576ab37ee6372fe4c9d4cdda6e165277bfe25b7bb4fe0e9efc462"
12
12
  },
13
13
  "exports": [
@@ -30,6 +30,7 @@
30
30
  "./runtime",
31
31
  "./scanner",
32
32
  "./scanner/types",
33
+ "./schema",
33
34
  "./schema/utils",
34
35
  "./smrt-knowledge.json",
35
36
  "./table-cache",