@happyvertical/smrt-core 0.40.69 → 0.41.0

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 (237) hide show
  1. package/AGENTS.md +29 -4
  2. package/README.md +20 -1
  3. package/agents/change-feed.md +1 -1
  4. package/agents/query-bounds.md +45 -0
  5. package/agents/schema-paths.md +786 -0
  6. package/dist/browser.d.ts +1 -0
  7. package/dist/browser.d.ts.map +1 -1
  8. package/dist/browser.js +5 -3
  9. package/dist/cascade.d.ts +120 -0
  10. package/dist/cascade.d.ts.map +1 -0
  11. package/dist/cascade.js +430 -0
  12. package/dist/cascade.js.map +1 -0
  13. package/dist/change-feed.d.ts +34 -2
  14. package/dist/change-feed.d.ts.map +1 -1
  15. package/dist/change-feed.js +52 -11
  16. package/dist/change-feed.js.map +1 -1
  17. package/dist/class.d.ts +36 -3
  18. package/dist/class.d.ts.map +1 -1
  19. package/dist/class.js +87 -9
  20. package/dist/class.js.map +1 -1
  21. package/dist/collection-cache.js +0 -0
  22. package/dist/collection-cache.js.map +1 -1
  23. package/dist/collection.d.ts +130 -2
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +290 -57
  26. package/dist/collection.js.map +1 -1
  27. package/dist/config.d.ts +10 -0
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js.map +1 -1
  30. package/dist/database.d.ts +8 -0
  31. package/dist/database.d.ts.map +1 -1
  32. package/dist/database.js +16 -8
  33. package/dist/database.js.map +1 -1
  34. package/dist/db-errors.d.ts +105 -0
  35. package/dist/db-errors.d.ts.map +1 -0
  36. package/dist/db-errors.js +382 -0
  37. package/dist/db-errors.js.map +1 -0
  38. package/dist/decorators/index.d.ts +80 -6
  39. package/dist/decorators/index.d.ts.map +1 -1
  40. package/dist/decorators/index.js +102 -12
  41. package/dist/decorators/index.js.map +1 -1
  42. package/dist/dispatch/bus.d.ts.map +1 -1
  43. package/dist/dispatch/bus.js +4 -3
  44. package/dist/dispatch/bus.js.map +1 -1
  45. package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
  46. package/dist/dispatch/collections/Dispatches.js +19 -4
  47. package/dist/dispatch/collections/Dispatches.js.map +1 -1
  48. package/dist/dispatch/types.d.ts +5 -0
  49. package/dist/dispatch/types.d.ts.map +1 -1
  50. package/dist/embedded-write-queue.d.ts +46 -0
  51. package/dist/embedded-write-queue.d.ts.map +1 -0
  52. package/dist/embedded-write-queue.js +66 -0
  53. package/dist/embedded-write-queue.js.map +1 -0
  54. package/dist/embeddings/storage.d.ts +7 -0
  55. package/dist/embeddings/storage.d.ts.map +1 -1
  56. package/dist/embeddings/storage.js +29 -12
  57. package/dist/embeddings/storage.js.map +1 -1
  58. package/dist/errors.d.ts +31 -3
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +34 -2
  61. package/dist/errors.js.map +1 -1
  62. package/dist/generators/changes-route.d.ts.map +1 -1
  63. package/dist/generators/changes-route.js +6 -3
  64. package/dist/generators/changes-route.js.map +1 -1
  65. package/dist/generators/mcp-runtime-template.d.ts +8 -0
  66. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  67. package/dist/generators/mcp-runtime-template.js +38 -4
  68. package/dist/generators/mcp-runtime-template.js.map +1 -1
  69. package/dist/generators/mcp.d.ts +16 -0
  70. package/dist/generators/mcp.d.ts.map +1 -1
  71. package/dist/generators/mcp.js +41 -3
  72. package/dist/generators/mcp.js.map +1 -1
  73. package/dist/generators/rest.d.ts +22 -0
  74. package/dist/generators/rest.d.ts.map +1 -1
  75. package/dist/generators/rest.js +34 -3
  76. package/dist/generators/rest.js.map +1 -1
  77. package/dist/hierarchical.js +1 -1
  78. package/dist/index.d.ts +7 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +12 -5
  81. package/dist/interceptors.d.ts +21 -0
  82. package/dist/interceptors.d.ts.map +1 -1
  83. package/dist/interceptors.js +27 -1
  84. package/dist/interceptors.js.map +1 -1
  85. package/dist/manifest/generator.d.ts.map +1 -1
  86. package/dist/manifest/generator.js +4 -7
  87. package/dist/manifest/generator.js.map +1 -1
  88. package/dist/manifest/static-manifest.js +10 -10
  89. package/dist/manifest/static-manifest.js.map +1 -1
  90. package/dist/manifest/store.js +1 -1
  91. package/dist/manifest/store.js.map +1 -1
  92. package/dist/manifest.json +19 -19
  93. package/dist/migrations/differ.d.ts +211 -9
  94. package/dist/migrations/differ.d.ts.map +1 -1
  95. package/dist/migrations/differ.js +613 -50
  96. package/dist/migrations/differ.js.map +1 -1
  97. package/dist/migrations/generator.d.ts +31 -4
  98. package/dist/migrations/generator.d.ts.map +1 -1
  99. package/dist/migrations/generator.js +49 -5
  100. package/dist/migrations/generator.js.map +1 -1
  101. package/dist/migrations/index.d.ts +4 -2
  102. package/dist/migrations/index.d.ts.map +1 -1
  103. package/dist/migrations/index.js +6 -3
  104. package/dist/migrations/minor-units.d.ts +162 -0
  105. package/dist/migrations/minor-units.d.ts.map +1 -0
  106. package/dist/migrations/minor-units.js +381 -0
  107. package/dist/migrations/minor-units.js.map +1 -0
  108. package/dist/migrations/orchestrate.js +35 -6
  109. package/dist/migrations/orchestrate.js.map +1 -1
  110. package/dist/migrations/sqlite-rebuild.d.ts +142 -0
  111. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
  112. package/dist/migrations/sqlite-rebuild.js +514 -0
  113. package/dist/migrations/sqlite-rebuild.js.map +1 -0
  114. package/dist/migrations/tracker.d.ts +114 -1
  115. package/dist/migrations/tracker.d.ts.map +1 -1
  116. package/dist/migrations/tracker.js +331 -16
  117. package/dist/migrations/tracker.js.map +1 -1
  118. package/dist/migrations/types.d.ts +19 -4
  119. package/dist/migrations/types.d.ts.map +1 -1
  120. package/dist/migrations.js +6 -3
  121. package/dist/object.d.ts +142 -10
  122. package/dist/object.d.ts.map +1 -1
  123. package/dist/object.js +196 -41
  124. package/dist/object.js.map +1 -1
  125. package/dist/postgres-timeouts.d.ts +240 -0
  126. package/dist/postgres-timeouts.d.ts.map +1 -0
  127. package/dist/postgres-timeouts.js +204 -0
  128. package/dist/postgres-timeouts.js.map +1 -0
  129. package/dist/query-bounds.d.ts +101 -0
  130. package/dist/query-bounds.d.ts.map +1 -0
  131. package/dist/query-bounds.js +177 -0
  132. package/dist/query-bounds.js.map +1 -0
  133. package/dist/registry/class-registration.d.ts.map +1 -1
  134. package/dist/registry/class-registration.js +3 -1
  135. package/dist/registry/class-registration.js.map +1 -1
  136. package/dist/registry/manifest-field-merge.d.ts +12 -0
  137. package/dist/registry/manifest-field-merge.d.ts.map +1 -1
  138. package/dist/registry/manifest-field-merge.js +14 -2
  139. package/dist/registry/manifest-field-merge.js.map +1 -1
  140. package/dist/registry/schema-builder.d.ts +22 -1
  141. package/dist/registry/schema-builder.d.ts.map +1 -1
  142. package/dist/registry/schema-builder.js +205 -165
  143. package/dist/registry/schema-builder.js.map +1 -1
  144. package/dist/registry/types.d.ts +35 -3
  145. package/dist/registry/types.d.ts.map +1 -1
  146. package/dist/registry.d.ts +41 -46
  147. package/dist/registry.d.ts.map +1 -1
  148. package/dist/registry.js +61 -83
  149. package/dist/registry.js.map +1 -1
  150. package/dist/scanner/manifest-generator.d.ts +45 -0
  151. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  152. package/dist/scanner/manifest-generator.js +92 -28
  153. package/dist/scanner/manifest-generator.js.map +1 -1
  154. package/dist/scanner/types.d.ts +5 -0
  155. package/dist/scanner/types.d.ts.map +1 -1
  156. package/dist/scanner/types.js.map +1 -1
  157. package/dist/schema/conflict-target.d.ts +104 -0
  158. package/dist/schema/conflict-target.d.ts.map +1 -0
  159. package/dist/schema/conflict-target.js +129 -0
  160. package/dist/schema/conflict-target.js.map +1 -0
  161. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  162. package/dist/schema/ddl/base-strategy.js +2 -2
  163. package/dist/schema/ddl/base-strategy.js.map +1 -1
  164. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  165. package/dist/schema/ddl/duckdb-strategy.js +2 -1
  166. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  167. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  168. package/dist/schema/ddl/postgres-strategy.js +12 -1
  169. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  170. package/dist/schema/generator.d.ts +307 -6
  171. package/dist/schema/generator.d.ts.map +1 -1
  172. package/dist/schema/generator.js +510 -87
  173. package/dist/schema/generator.js.map +1 -1
  174. package/dist/schema/index-utils.d.ts +120 -0
  175. package/dist/schema/index-utils.d.ts.map +1 -1
  176. package/dist/schema/index-utils.js +242 -1
  177. package/dist/schema/index-utils.js.map +1 -1
  178. package/dist/schema/index.d.ts +3 -0
  179. package/dist/schema/index.d.ts.map +1 -1
  180. package/dist/schema/index.js +4 -1
  181. package/dist/schema/live-parity.d.ts +90 -0
  182. package/dist/schema/live-parity.d.ts.map +1 -0
  183. package/dist/schema/live-parity.js +602 -0
  184. package/dist/schema/live-parity.js.map +1 -0
  185. package/dist/schema/manifest-schema.d.ts +121 -0
  186. package/dist/schema/manifest-schema.d.ts.map +1 -0
  187. package/dist/schema/manifest-schema.js +267 -0
  188. package/dist/schema/manifest-schema.js.map +1 -0
  189. package/dist/schema/schema-aggregator.d.ts +24 -10
  190. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  191. package/dist/schema/schema-aggregator.js +35 -90
  192. package/dist/schema/schema-aggregator.js.map +1 -1
  193. package/dist/schema/system-table-shapes.d.ts +65 -0
  194. package/dist/schema/system-table-shapes.d.ts.map +1 -0
  195. package/dist/schema/system-table-shapes.js +187 -0
  196. package/dist/schema/system-table-shapes.js.map +1 -0
  197. package/dist/schema/types.d.ts +103 -4
  198. package/dist/schema/types.d.ts.map +1 -1
  199. package/dist/schema/utils.d.ts +2 -1
  200. package/dist/schema/utils.d.ts.map +1 -1
  201. package/dist/schema/utils.js +5 -3
  202. package/dist/schema/utils.js.map +1 -1
  203. package/dist/schema.js +4 -1
  204. package/dist/smrt-knowledge.json +20 -8
  205. package/dist/sync/apply.d.ts.map +1 -1
  206. package/dist/sync/apply.js +9 -16
  207. package/dist/sync/apply.js.map +1 -1
  208. package/dist/system/compatibility.d.ts +42 -0
  209. package/dist/system/compatibility.d.ts.map +1 -1
  210. package/dist/system/compatibility.js +182 -9
  211. package/dist/system/compatibility.js.map +1 -1
  212. package/dist/system/index.d.ts +1 -0
  213. package/dist/system/index.d.ts.map +1 -1
  214. package/dist/system/index.js +3 -2
  215. package/dist/system/retention.d.ts +237 -0
  216. package/dist/system/retention.d.ts.map +1 -0
  217. package/dist/system/retention.js +497 -0
  218. package/dist/system/retention.js.map +1 -0
  219. package/dist/system/schema.d.ts +100 -15
  220. package/dist/system/schema.d.ts.map +1 -1
  221. package/dist/system/schema.js +81 -45
  222. package/dist/system/schema.js.map +1 -1
  223. package/dist/system/types.d.ts +0 -2
  224. package/dist/system/types.d.ts.map +1 -1
  225. package/dist/testing/database.d.ts.map +1 -1
  226. package/dist/testing/database.js +1 -0
  227. package/dist/testing/database.js.map +1 -1
  228. package/dist/vite-plugin/index.d.ts.map +1 -1
  229. package/dist/vite-plugin/index.js +4 -9
  230. package/dist/vite-plugin/index.js.map +1 -1
  231. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  232. package/dist/vite-plugin/sveltekit-generator.js +71 -5
  233. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  234. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  235. package/dist/vite-plugin/web-collections.js +6 -4
  236. package/dist/vite-plugin/web-collections.js.map +1 -1
  237. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":[],"sources":["../../src/manifest/store.ts"],"sourcesContent":["/**\n * Leaf manifest storage + pure fs/URL helpers.\n *\n * This module has **no dependency on ObjectRegistry, registry.ts, or\n * class-registration.ts**. It owns the globalThis manifest caches and the\n * filesystem helpers that resolve and read manifest.json files from the\n * workspace, installed node_modules, and smrt-core's own bundle.\n *\n * Breaking the cycle: Before this module existed, `registry.ts` and\n * `manifest-loader.ts` both maintained their own copies of these helpers\n * (`registry.ts` had its own because `registerPackageManifest` needed them\n * and could not import `manifest-loader.ts` due to a module cycle —\n * `registry.ts → class-registration.ts → manifest-loader.ts → registry.ts`).\n * Centralizing them here lets both files import from a leaf, so the cycle\n * goes away and Release B's `ManifestSource` abstraction can live above\n * this leaf without re-creating it.\n *\n * @see https://github.com/happyvertical/smrt/issues/1133\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { recordRegistryDiagnostic } from '../registry/diagnostics.js';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport type { ColumnDefinition } from '../schema/types.js';\nimport {\n createQualifiedName,\n isQualifiedName,\n parseQualifiedName,\n} from '../utils/qualified-names.js';\n\nconst logger = createLogger({ level: 'info' });\n\nexport type ManifestLoadOptions = {\n warn?: boolean;\n};\n\n// ── Runtime environment detection ────────────────────────────────────────\n\n/**\n * Detect whether we're running inside a test runner. Test manifests must\n * never load in production to avoid test classes colliding with real ones.\n */\nexport function isTestEnvironment(): boolean {\n return (\n process.env.NODE_ENV === 'test' ||\n process.env.VITEST === 'true' ||\n process.env.JEST_WORKER_ID !== undefined\n );\n}\n\n/**\n * Best-effort detection of the current package under test.\n *\n * In workspace consumers, `process.cwd()` points at the consuming app/package,\n * while in smrt-core's own tests it points at `packages/core`. We use that\n * distinction to keep smrt-core's internal test manifest scoped to its own\n * test suite instead of leaking those fixtures into every consumer test run.\n */\nexport function getCurrentPackageName(): string | null {\n const explicitPackageName = process.env.npm_package_name;\n if (explicitPackageName) {\n return explicitPackageName;\n }\n\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n let currentDir = process.cwd();\n\n while (true) {\n const packageJsonPath = builtins.path.join(currentDir, 'package.json');\n if (builtins.fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(\n builtins.fs.readFileSync(packageJsonPath, 'utf-8'),\n ) as { name?: string };\n return packageJson.name ?? null;\n } catch {\n return null;\n }\n }\n\n const parentDir = builtins.path.dirname(currentDir);\n if (parentDir === currentDir) {\n return null;\n }\n\n currentDir = parentDir;\n }\n}\n\n/**\n * smrt-core's committed test manifest is for smrt-core's own tests only.\n * Consumer packages should expose only their local test manifest plus real\n * published manifests from dependencies.\n */\nexport function shouldLoadCoreTestManifest(): boolean {\n if (!isTestEnvironment()) {\n return false;\n }\n\n const currentPackageName = getCurrentPackageName();\n if (currentPackageName) {\n return currentPackageName === '@happyvertical/smrt-core';\n }\n\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return false;\n }\n\n const moduleDir = builtins.path.dirname(\n builtins.url.fileURLToPath(import.meta.url),\n );\n const workspaceRoot = findWorkspaceRootSync(moduleDir);\n if (!workspaceRoot) {\n return false;\n }\n\n const corePackageDir = builtins.path.join(workspaceRoot, 'packages', 'core');\n const relative = builtins.path.relative(corePackageDir, process.cwd());\n return (\n relative === '' ||\n (!relative.startsWith('..') && !builtins.path.isAbsolute(relative))\n );\n}\n\n// ── Node builtins access ─────────────────────────────────────────────────\n\n/**\n * Resolve Node's built-in `fs`/`path`/`url` modules without a static import.\n *\n * `process.getBuiltinModule()` is a Node 22+ API that returns the live\n * built-in without going through the module loader. This avoids Vite's\n * static-import analysis flagging these builtins and simulated\n * \"externalized for browser\" warnings in bundled browser builds.\n */\nexport function getNodeBuiltins() {\n const getBuiltinModule = (\n process as typeof process & {\n getBuiltinModule?: (id: string) => unknown;\n }\n ).getBuiltinModule;\n if (typeof getBuiltinModule !== 'function') {\n return null;\n }\n\n const fs = getBuiltinModule('node:fs') as\n | typeof import('node:fs')\n | undefined;\n const path = getBuiltinModule('node:path') as\n | typeof import('node:path')\n | undefined;\n const url = getBuiltinModule('node:url') as\n | typeof import('node:url')\n | undefined;\n\n if (!fs || !path || !url) {\n return null;\n }\n\n return { fs, path, url };\n}\n\n// ── globalThis cache accessors ───────────────────────────────────────────\n\ntype ManifestGlobals = typeof globalThis & {\n __smrtManifestStatic?: SmartObjectManifest | null;\n __smrtManifestStaticLoadAttempted?: boolean;\n __smrtManifestTest?: SmartObjectManifest | null;\n __smrtManifestLocalTest?: SmartObjectManifest | null;\n __smrtManifestCache?: Map<string, SmartObjectManifest>;\n};\n\nfunction getManifestGlobals(): ManifestGlobals {\n return globalThis as ManifestGlobals;\n}\n\n/**\n * Return the mutable external-manifest cache, creating it if absent.\n *\n * Writers: `registerPackageManifest()` (Release A, each package's shim),\n * `loadExternalManifestSyncWithNode()`, `loadManifestFromPathSyncWithNode()`.\n * Readers: all discovery paths.\n */\nexport function getManifestCache(): Map<string, SmartObjectManifest> {\n const globals = getManifestGlobals();\n if (!globals.__smrtManifestCache) {\n globals.__smrtManifestCache = new Map();\n }\n return globals.__smrtManifestCache;\n}\n\n/** Read-only snapshot of the static manifest, if cached. */\nexport function getStaticManifestCache(): SmartObjectManifest | null {\n return getManifestGlobals().__smrtManifestStatic ?? null;\n}\n\n/** Read-only snapshot of the core test manifest, if cached. */\nexport function getTestManifestCache(): SmartObjectManifest | null {\n return getManifestGlobals().__smrtManifestTest ?? null;\n}\n\n/**\n * Read-only snapshot of the local (domain package) test manifest.\n *\n * Preserves the three-state semantics that `discoverManifestEntry()` relies\n * on: `undefined` means \"never attempted to load\" and triggers the lazy\n * loader; `null` means \"attempted but no local test manifest in this\n * package\"; a manifest object means \"loaded\". Do not collapse `undefined`\n * to `null` here — the async-discovery lazy-load path keys off the\n * distinction (see manifest-loader.ts:`if (getLocalTestManifestCache() === undefined) loadLocalTestManifestSync()`).\n */\nexport function getLocalTestManifestCache():\n | SmartObjectManifest\n | null\n | undefined {\n return getManifestGlobals().__smrtManifestLocalTest;\n}\n\n// ── Manifest shape helpers ───────────────────────────────────────────────\n\nexport function createEmptyStaticManifest(): SmartObjectManifest {\n return {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n packageName: '@happyvertical/smrt-core',\n };\n}\n\n/**\n * Deep-clone a manifest's schema column map so downstream mutators don't\n * corrupt the cached manifest. Foreign-key descriptors get their own copy\n * because manifest-loader.ts and registerFromManifest() both splice them\n * into live schema entries.\n */\nexport function cloneManifestSchemaColumns(\n columns: Record<string, unknown> | undefined,\n): Record<string, ColumnDefinition> {\n return Object.fromEntries(\n Object.entries(columns || {}).map(([columnName, column]) => {\n const clonedColumn = {\n ...(column as ColumnDefinition & Record<string, unknown>),\n } as ColumnDefinition;\n const manifestDefault = (\n clonedColumn as ColumnDefinition & {\n default?: unknown;\n }\n ).default;\n\n if (\n clonedColumn.defaultValue === undefined &&\n manifestDefault !== undefined\n ) {\n clonedColumn.defaultValue = manifestDefault;\n }\n\n if (clonedColumn.foreignKey) {\n clonedColumn.foreignKey = { ...clonedColumn.foreignKey };\n }\n\n return [columnName, clonedColumn];\n }),\n ) as Record<string, ColumnDefinition>;\n}\n\n/**\n * Look up a class definition inside a single manifest.\n *\n * Tries qualified-key, simple-name, and lowercase-name lookups, then falls\n * back to iterating every entry in the manifest comparing lowered names.\n * This is the low-level primitive used by every sync discovery path.\n */\nexport function lookupCachedManifestEntry(\n manifest: SmartObjectManifest | null | undefined,\n className: string,\n): SmartObjectDefinition | undefined {\n if (!manifest?.objects) {\n return undefined;\n }\n\n const requestedQualifiedName = isQualifiedName(className)\n ? className\n : undefined;\n const requestedClassName = requestedQualifiedName\n ? parseQualifiedName(requestedQualifiedName).className\n : className;\n const lowerClassName = requestedClassName.toLowerCase();\n const packageName = manifest.packageName;\n const qualifiedKey =\n requestedQualifiedName ||\n (packageName\n ? createQualifiedName(packageName, requestedClassName)\n : undefined);\n\n return (\n (qualifiedKey ? manifest.objects[qualifiedKey] : undefined) ||\n manifest.objects[requestedClassName] ||\n manifest.objects[lowerClassName] ||\n Object.values(manifest.objects).find((entry) => {\n if (!entry) {\n return false;\n }\n\n if (entry.className?.toLowerCase() === lowerClassName) {\n return true;\n }\n\n return (\n requestedQualifiedName !== undefined &&\n packageName !== undefined &&\n createQualifiedName(packageName, entry.className || '') ===\n requestedQualifiedName\n );\n })\n );\n}\n\n// ── Filesystem helpers ───────────────────────────────────────────────────\n\nexport function findWorkspaceRootSync(startDir: string): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n let currentDir = startDir;\n\n while (true) {\n if (\n builtins.fs.existsSync(\n builtins.path.join(currentDir, 'pnpm-workspace.yaml'),\n )\n ) {\n return currentDir;\n }\n\n const parentDir = builtins.path.dirname(currentDir);\n if (parentDir === currentDir) {\n return null;\n }\n\n currentDir = parentDir;\n }\n}\n\nexport function resolveInstalledPackageJsonPathSync(\n packageName: string,\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n let currentDir = process.cwd();\n\n while (true) {\n const packageJsonPath = builtins.path.join(\n currentDir,\n 'node_modules',\n packageName,\n 'package.json',\n );\n\n if (builtins.fs.existsSync(packageJsonPath)) {\n return packageJsonPath;\n }\n\n const parentDir = builtins.path.dirname(currentDir);\n if (parentDir === currentDir) {\n return null;\n }\n\n currentDir = parentDir;\n }\n}\n\nexport function resolveWorkspacePackageJsonPathSync(\n packageName: string,\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const workspaceRoot = findWorkspaceRootSync(process.cwd());\n if (!workspaceRoot) {\n return null;\n }\n\n const workspacePackagesDir = builtins.path.join(workspaceRoot, 'packages');\n if (!builtins.fs.existsSync(workspacePackagesDir)) {\n return null;\n }\n\n for (const packageDirName of builtins.fs.readdirSync(workspacePackagesDir)) {\n const packageJsonPath = builtins.path.join(\n workspacePackagesDir,\n packageDirName,\n 'package.json',\n );\n if (!builtins.fs.existsSync(packageJsonPath)) {\n continue;\n }\n\n try {\n const packageJson = JSON.parse(\n builtins.fs.readFileSync(packageJsonPath, 'utf-8'),\n ) as { name?: string };\n if (packageJson.name === packageName) {\n return packageJsonPath;\n }\n } catch {\n // Ignore unreadable workspace package metadata and continue scanning.\n }\n }\n\n return null;\n}\n\nexport function resolveWorkspaceSourceManifestPathSync(\n packageDir: string,\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const candidates = [\n builtins.path.join(packageDir, 'src', 'manifest', 'manifest.json'),\n builtins.path.join(packageDir, '.smrt', 'manifest.json'),\n ];\n\n for (const candidate of candidates) {\n if (builtins.fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function resolveManifestExportPathSync(\n packageName: string,\n options: ManifestLoadOptions = {},\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const shouldWarn = options.warn ?? true;\n const packageJsonPath =\n resolveInstalledPackageJsonPathSync(packageName) ||\n resolveWorkspacePackageJsonPathSync(packageName);\n\n if (!packageJsonPath) {\n return null;\n }\n\n const packageDir = builtins.path.dirname(packageJsonPath);\n const packageJson = JSON.parse(\n builtins.fs.readFileSync(packageJsonPath, 'utf-8'),\n ) as {\n exports?: Record<\n string,\n string | { default?: string; import?: string; require?: string }\n >;\n };\n const manifestExports = ['./manifest.json', './manifest'];\n\n for (const exportKey of manifestExports) {\n const manifestExport = packageJson.exports?.[exportKey];\n if (!manifestExport) {\n continue;\n }\n\n const manifestRelativePath =\n typeof manifestExport === 'string'\n ? manifestExport\n : manifestExport.import ||\n manifestExport.default ||\n manifestExport.require;\n\n if (!manifestRelativePath) {\n recordRegistryDiagnostic(\n 'error',\n 'MANIFEST_EXPORT_INVALID',\n `Package ${packageName} has invalid manifest export configuration for ${exportKey}`,\n { packageName, exportKey },\n );\n if (shouldWarn) {\n logger.warn(\n `Package ${packageName} has invalid manifest export configuration for ${exportKey}`,\n );\n }\n return null;\n }\n\n if (!manifestRelativePath.endsWith('.json')) {\n recordRegistryDiagnostic(\n 'error',\n 'MANIFEST_EXPORT_NOT_JSON',\n `Package ${packageName} must export a JSON manifest for ${exportKey}, received ${manifestRelativePath}`,\n { packageName, exportKey, manifestRelativePath },\n );\n if (shouldWarn) {\n logger.warn(\n `Package ${packageName} must export a JSON manifest for ${exportKey}, received ${manifestRelativePath}`,\n );\n }\n return null;\n }\n\n const manifestPath = builtins.path.join(packageDir, manifestRelativePath);\n if (builtins.fs.existsSync(manifestPath)) {\n return manifestPath;\n }\n\n const workspaceSourceManifest =\n resolveWorkspaceSourceManifestPathSync(packageDir);\n if (workspaceSourceManifest) {\n return workspaceSourceManifest;\n }\n\n recordRegistryDiagnostic(\n 'error',\n 'MANIFEST_EXPORT_NOT_FOUND',\n `Package ${packageName} declares manifest export ${manifestRelativePath}, but no manifest file was found.`,\n { packageName, exportKey, manifestRelativePath },\n );\n if (shouldWarn) {\n logger.warn(\n `Package ${packageName} declares manifest export ${manifestRelativePath}, but no manifest file was found.`,\n );\n }\n return null;\n }\n\n return null;\n}\n\n// ── Manifest reading (seeds caches) ──────────────────────────────────────\n\n/**\n * Load smrt-core's own bundled manifest from disk, caching in\n * `__smrtManifestStatic`. Resolves against this module's own `import.meta.url`\n * so paths work whether the code is running from `dist/` or `src/`.\n */\nexport function loadStaticManifestSyncWithNode(): SmartObjectManifest | null {\n const globals = getManifestGlobals();\n\n if (globals.__smrtManifestStatic !== undefined) {\n return globals.__smrtManifestStatic ?? null;\n }\n\n const builtins = getNodeBuiltins();\n globals.__smrtManifestStaticLoadAttempted = true;\n if (!builtins) {\n const fallbackManifest = createEmptyStaticManifest();\n globals.__smrtManifestStatic = fallbackManifest;\n return fallbackManifest;\n }\n\n // store.ts lives at src/manifest/store.ts (source) → dist/manifest/store.js\n // (compiled). In src, the static manifest is a sibling JSON; in dist, it is\n // the package's dist/manifest.json one level up.\n const candidates = import.meta.url.endsWith('.ts')\n ? [\n new URL('./static-manifest.json', import.meta.url),\n new URL('../../dist/manifest.json', import.meta.url),\n ]\n : [new URL('../manifest.json', import.meta.url)];\n\n for (const candidate of candidates) {\n try {\n if (!builtins.fs.existsSync(candidate)) {\n continue;\n }\n\n const manifest = JSON.parse(\n builtins.fs.readFileSync(candidate, 'utf-8'),\n ) as SmartObjectManifest;\n\n if (!manifest.objects || typeof manifest.objects !== 'object') {\n continue;\n }\n\n const cachedManifest = manifest.packageName\n ? manifest\n : { ...manifest, packageName: '@happyvertical/smrt-core' };\n globals.__smrtManifestStatic = cachedManifest;\n return cachedManifest;\n } catch {\n // Try the next candidate and fall back to the empty manifest if needed.\n }\n }\n\n const fallbackManifest = createEmptyStaticManifest();\n globals.__smrtManifestStatic = fallbackManifest;\n return fallbackManifest;\n}\n\n/**\n * Load a package's published manifest by package name, caching in\n * `__smrtManifestCache` under the package key. Returns null on miss or\n * invalid shape; records a diagnostic instead of throwing.\n */\nexport function loadExternalManifestSyncWithNode(\n packageName: string,\n options: ManifestLoadOptions = {},\n): SmartObjectManifest | null {\n const cache = getManifestCache();\n\n if (cache.has(packageName)) {\n return cache.get(packageName) || null;\n }\n\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const manifestPath = resolveManifestExportPathSync(packageName, options);\n if (!manifestPath) {\n return null;\n }\n\n try {\n const manifest = JSON.parse(\n builtins.fs.readFileSync(manifestPath, 'utf-8'),\n ) as SmartObjectManifest;\n\n if (!manifest.objects || typeof manifest.objects !== 'object') {\n if (options.warn ?? true) {\n logger.warn(`Invalid manifest structure for package ${packageName}`);\n }\n return null;\n }\n\n const cachedManifest = manifest.packageName\n ? manifest\n : { ...manifest, packageName };\n\n cache.set(packageName, cachedManifest);\n\n return cachedManifest;\n } catch (error) {\n if (options.warn ?? true) {\n logger.warn(\n `Failed to load manifest for package ${packageName}: ${error instanceof Error ? error.message : error}`,\n );\n }\n return null;\n }\n}\n\n/**\n * Load a manifest from an explicit filesystem path, caching in\n * `__smrtManifestCache` under the manifest's packageName (or the path as a\n * fallback key). Used by `loadAllManifests({ manifestPaths })` in registry.ts\n * and by the Release B `ExplicitPathsManifestSource`.\n */\nexport function loadManifestFromPathSyncWithNode(\n manifestPath: string,\n): SmartObjectManifest | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n try {\n const manifest = JSON.parse(\n builtins.fs.readFileSync(manifestPath, 'utf-8'),\n ) as SmartObjectManifest;\n\n if (!manifest.objects || typeof manifest.objects !== 'object') {\n logger.warn(`Invalid manifest structure at ${manifestPath}`);\n return null;\n }\n\n getManifestCache().set(manifest.packageName || manifestPath, manifest);\n\n return manifest;\n } catch (error) {\n logger.warn(\n `Failed to load manifest from path ${manifestPath}: ${error instanceof Error ? error.message : error}`,\n );\n return null;\n }\n}\n\n/**\n * Synchronous lookup across every cached manifest source, in priority order.\n *\n * Matches the historical order from manifest-loader.ts and registry.ts:\n * local test → core test → static (smrt-core bundled) → external cache.\n * The Release B `CompositeManifestSource` wraps the same priority behind a\n * higher-level abstraction.\n */\nexport function discoverCachedManifestSync(\n className: string,\n): SmartObjectDefinition | undefined {\n const manifests: Array<SmartObjectManifest | null | undefined> = [\n getLocalTestManifestCache(),\n getTestManifestCache(),\n loadStaticManifestSyncWithNode(),\n ...getManifestCache().values(),\n ];\n\n for (const manifest of manifests) {\n const entry = lookupCachedManifestEntry(manifest, className);\n if (entry) {\n return entry;\n }\n }\n\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":[],"sources":["../../src/manifest/store.ts"],"sourcesContent":["/**\n * Leaf manifest storage + pure fs/URL helpers.\n *\n * This module has **no dependency on ObjectRegistry, registry.ts, or\n * class-registration.ts**. It owns the globalThis manifest caches and the\n * filesystem helpers that resolve and read manifest.json files from the\n * workspace, installed node_modules, and smrt-core's own bundle.\n *\n * Breaking the cycle: Before this module existed, `registry.ts` and\n * `manifest-loader.ts` both maintained their own copies of these helpers\n * (`registry.ts` had its own because `registerPackageManifest` needed them\n * and could not import `manifest-loader.ts` due to a module cycle —\n * `registry.ts → class-registration.ts → manifest-loader.ts → registry.ts`).\n * Centralizing them here lets both files import from a leaf, so the cycle\n * goes away and Release B's `ManifestSource` abstraction can live above\n * this leaf without re-creating it.\n *\n * @see https://github.com/happyvertical/smrt/issues/1133\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { recordRegistryDiagnostic } from '../registry/diagnostics.js';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport type { ColumnDefinition } from '../schema/types.js';\nimport {\n createQualifiedName,\n isQualifiedName,\n parseQualifiedName,\n} from '../utils/qualified-names.js';\n\nconst logger = createLogger({ level: 'info' });\n\nexport type ManifestLoadOptions = {\n warn?: boolean;\n};\n\n// ── Runtime environment detection ────────────────────────────────────────\n\n/**\n * Detect whether we're running inside a test runner. Test manifests must\n * never load in production to avoid test classes colliding with real ones.\n */\nexport function isTestEnvironment(): boolean {\n return (\n process.env.NODE_ENV === 'test' ||\n process.env.VITEST === 'true' ||\n process.env.JEST_WORKER_ID !== undefined\n );\n}\n\n/**\n * Best-effort detection of the current package under test.\n *\n * In workspace consumers, `process.cwd()` points at the consuming app/package,\n * while in smrt-core's own tests it points at `packages/core`. We use that\n * distinction to keep smrt-core's internal test manifest scoped to its own\n * test suite instead of leaking those fixtures into every consumer test run.\n */\nexport function getCurrentPackageName(): string | null {\n const explicitPackageName = process.env.npm_package_name;\n if (explicitPackageName) {\n return explicitPackageName;\n }\n\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n let currentDir = process.cwd();\n\n while (true) {\n const packageJsonPath = builtins.path.join(currentDir, 'package.json');\n if (builtins.fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(\n builtins.fs.readFileSync(packageJsonPath, 'utf-8'),\n ) as { name?: string };\n return packageJson.name ?? null;\n } catch {\n return null;\n }\n }\n\n const parentDir = builtins.path.dirname(currentDir);\n if (parentDir === currentDir) {\n return null;\n }\n\n currentDir = parentDir;\n }\n}\n\n/**\n * smrt-core's committed test manifest is for smrt-core's own tests only.\n * Consumer packages should expose only their local test manifest plus real\n * published manifests from dependencies.\n */\nexport function shouldLoadCoreTestManifest(): boolean {\n if (!isTestEnvironment()) {\n return false;\n }\n\n const currentPackageName = getCurrentPackageName();\n if (currentPackageName) {\n return currentPackageName === '@happyvertical/smrt-core';\n }\n\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return false;\n }\n\n const moduleDir = builtins.path.dirname(\n builtins.url.fileURLToPath(import.meta.url),\n );\n const workspaceRoot = findWorkspaceRootSync(moduleDir);\n if (!workspaceRoot) {\n return false;\n }\n\n const corePackageDir = builtins.path.join(workspaceRoot, 'packages', 'core');\n const relative = builtins.path.relative(corePackageDir, process.cwd());\n return (\n relative === '' ||\n (!relative.startsWith('..') && !builtins.path.isAbsolute(relative))\n );\n}\n\n// ── Node builtins access ─────────────────────────────────────────────────\n\n/**\n * Resolve Node's built-in `fs`/`path`/`url` modules without a static import.\n *\n * `process.getBuiltinModule()` is a Node 22+ API that returns the live\n * built-in without going through the module loader. This avoids Vite's\n * static-import analysis flagging these builtins and simulated\n * \"externalized for browser\" warnings in bundled browser builds.\n */\nexport function getNodeBuiltins() {\n const getBuiltinModule = (\n process as typeof process & {\n getBuiltinModule?: (id: string) => unknown;\n }\n ).getBuiltinModule;\n if (typeof getBuiltinModule !== 'function') {\n return null;\n }\n\n const fs = getBuiltinModule('node:fs') as\n | typeof import('node:fs')\n | undefined;\n const path = getBuiltinModule('node:path') as\n | typeof import('node:path')\n | undefined;\n const url = getBuiltinModule('node:url') as\n | typeof import('node:url')\n | undefined;\n\n if (!fs || !path || !url) {\n return null;\n }\n\n return { fs, path, url };\n}\n\n// ── globalThis cache accessors ───────────────────────────────────────────\n\ntype ManifestGlobals = typeof globalThis & {\n __smrtManifestStatic?: SmartObjectManifest | null;\n __smrtManifestStaticLoadAttempted?: boolean;\n __smrtManifestTest?: SmartObjectManifest | null;\n __smrtManifestLocalTest?: SmartObjectManifest | null;\n __smrtManifestCache?: Map<string, SmartObjectManifest>;\n};\n\nfunction getManifestGlobals(): ManifestGlobals {\n return globalThis as ManifestGlobals;\n}\n\n/**\n * Return the mutable external-manifest cache, creating it if absent.\n *\n * Writers: `registerPackageManifest()` (Release A, each package's shim),\n * `loadExternalManifestSyncWithNode()`, `loadManifestFromPathSyncWithNode()`.\n * Readers: all discovery paths.\n */\nexport function getManifestCache(): Map<string, SmartObjectManifest> {\n const globals = getManifestGlobals();\n if (!globals.__smrtManifestCache) {\n globals.__smrtManifestCache = new Map();\n }\n return globals.__smrtManifestCache;\n}\n\n/** Read-only snapshot of the static manifest, if cached. */\nexport function getStaticManifestCache(): SmartObjectManifest | null {\n return getManifestGlobals().__smrtManifestStatic ?? null;\n}\n\n/** Read-only snapshot of the core test manifest, if cached. */\nexport function getTestManifestCache(): SmartObjectManifest | null {\n return getManifestGlobals().__smrtManifestTest ?? null;\n}\n\n/**\n * Read-only snapshot of the local (domain package) test manifest.\n *\n * Preserves the three-state semantics that `discoverManifestEntry()` relies\n * on: `undefined` means \"never attempted to load\" and triggers the lazy\n * loader; `null` means \"attempted but no local test manifest in this\n * package\"; a manifest object means \"loaded\". Do not collapse `undefined`\n * to `null` here — the async-discovery lazy-load path keys off the\n * distinction (see manifest-loader.ts:`if (getLocalTestManifestCache() === undefined) loadLocalTestManifestSync()`).\n */\nexport function getLocalTestManifestCache():\n | SmartObjectManifest\n | null\n | undefined {\n return getManifestGlobals().__smrtManifestLocalTest;\n}\n\n// ── Manifest shape helpers ───────────────────────────────────────────────\n\nexport function createEmptyStaticManifest(): SmartObjectManifest {\n return {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n packageName: '@happyvertical/smrt-core',\n };\n}\n\n/**\n * Deep-clone a manifest's schema column map so downstream mutators don't\n * corrupt the cached manifest. Foreign-key descriptors get their own copy\n * because manifest-loader.ts and registerFromManifest() both splice them\n * into live schema entries.\n */\nexport function cloneManifestSchemaColumns(\n columns: Record<string, unknown> | undefined,\n): Record<string, ColumnDefinition> {\n return Object.fromEntries(\n Object.entries(columns || {}).map(([columnName, column]) => {\n const clonedColumn = {\n ...(column as ColumnDefinition & Record<string, unknown>),\n } as ColumnDefinition;\n const manifestDefault = (\n clonedColumn as ColumnDefinition & {\n default?: unknown;\n }\n ).default;\n\n if (\n clonedColumn.defaultValue === undefined &&\n manifestDefault !== undefined\n ) {\n clonedColumn.defaultValue = manifestDefault;\n }\n\n if (clonedColumn.foreignKey) {\n clonedColumn.foreignKey = { ...clonedColumn.foreignKey };\n }\n\n return [columnName, clonedColumn];\n }),\n ) as Record<string, ColumnDefinition>;\n}\n\n/**\n * Look up a class definition inside a single manifest.\n *\n * Tries qualified-key, simple-name, and lowercase-name lookups, then falls\n * back to iterating every entry in the manifest comparing lowered names.\n * This is the low-level primitive used by every sync discovery path.\n */\nexport function lookupCachedManifestEntry(\n manifest: SmartObjectManifest | null | undefined,\n className: string,\n): SmartObjectDefinition | undefined {\n if (!manifest?.objects) {\n return undefined;\n }\n\n const requestedQualifiedName = isQualifiedName(className)\n ? className\n : undefined;\n const requestedClassName = requestedQualifiedName\n ? parseQualifiedName(requestedQualifiedName).className\n : className;\n const lowerClassName = requestedClassName.toLowerCase();\n const packageName = manifest.packageName;\n const qualifiedKey =\n requestedQualifiedName ||\n (packageName\n ? createQualifiedName(packageName, requestedClassName)\n : undefined);\n\n return (\n (qualifiedKey ? manifest.objects[qualifiedKey] : undefined) ||\n manifest.objects[requestedClassName] ||\n manifest.objects[lowerClassName] ||\n Object.values(manifest.objects).find((entry) => {\n if (!entry) {\n return false;\n }\n\n if (entry.className?.toLowerCase() === lowerClassName) {\n return true;\n }\n\n return (\n requestedQualifiedName !== undefined &&\n packageName !== undefined &&\n createQualifiedName(packageName, entry.className || '') ===\n requestedQualifiedName\n );\n })\n );\n}\n\n// ── Filesystem helpers ───────────────────────────────────────────────────\n\nexport function findWorkspaceRootSync(startDir: string): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n let currentDir = startDir;\n\n while (true) {\n if (\n builtins.fs.existsSync(\n builtins.path.join(currentDir, 'pnpm-workspace.yaml'),\n )\n ) {\n return currentDir;\n }\n\n const parentDir = builtins.path.dirname(currentDir);\n if (parentDir === currentDir) {\n return null;\n }\n\n currentDir = parentDir;\n }\n}\n\nexport function resolveInstalledPackageJsonPathSync(\n packageName: string,\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n let currentDir = process.cwd();\n\n while (true) {\n const packageJsonPath = builtins.path.join(\n currentDir,\n 'node_modules',\n packageName,\n 'package.json',\n );\n\n if (builtins.fs.existsSync(packageJsonPath)) {\n return packageJsonPath;\n }\n\n const parentDir = builtins.path.dirname(currentDir);\n if (parentDir === currentDir) {\n return null;\n }\n\n currentDir = parentDir;\n }\n}\n\nexport function resolveWorkspacePackageJsonPathSync(\n packageName: string,\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const workspaceRoot = findWorkspaceRootSync(process.cwd());\n if (!workspaceRoot) {\n return null;\n }\n\n const workspacePackagesDir = builtins.path.join(workspaceRoot, 'packages');\n if (!builtins.fs.existsSync(workspacePackagesDir)) {\n return null;\n }\n\n for (const packageDirName of builtins.fs.readdirSync(workspacePackagesDir)) {\n const packageJsonPath = builtins.path.join(\n workspacePackagesDir,\n packageDirName,\n 'package.json',\n );\n if (!builtins.fs.existsSync(packageJsonPath)) {\n continue;\n }\n\n try {\n const packageJson = JSON.parse(\n builtins.fs.readFileSync(packageJsonPath, 'utf-8'),\n ) as { name?: string };\n if (packageJson.name === packageName) {\n return packageJsonPath;\n }\n } catch {\n // Ignore unreadable workspace package metadata and continue scanning.\n }\n }\n\n return null;\n}\n\nexport function resolveWorkspaceSourceManifestPathSync(\n packageDir: string,\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const candidates = [\n builtins.path.join(packageDir, 'src', 'manifest', 'manifest.json'),\n builtins.path.join(packageDir, '.smrt', 'manifest.json'),\n ];\n\n for (const candidate of candidates) {\n if (builtins.fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function resolveManifestExportPathSync(\n packageName: string,\n options: ManifestLoadOptions = {},\n): string | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const shouldWarn = options.warn ?? true;\n const packageJsonPath =\n resolveInstalledPackageJsonPathSync(packageName) ||\n resolveWorkspacePackageJsonPathSync(packageName);\n\n if (!packageJsonPath) {\n return null;\n }\n\n const packageDir = builtins.path.dirname(packageJsonPath);\n const packageJson = JSON.parse(\n builtins.fs.readFileSync(packageJsonPath, 'utf-8'),\n ) as {\n exports?: Record<\n string,\n string | { default?: string; import?: string; require?: string }\n >;\n };\n const manifestExports = ['./manifest.json', './manifest'];\n\n for (const exportKey of manifestExports) {\n const manifestExport = packageJson.exports?.[exportKey];\n if (!manifestExport) {\n continue;\n }\n\n const manifestRelativePath =\n typeof manifestExport === 'string'\n ? manifestExport\n : manifestExport.import ||\n manifestExport.default ||\n manifestExport.require;\n\n if (!manifestRelativePath) {\n recordRegistryDiagnostic(\n 'error',\n 'MANIFEST_EXPORT_INVALID',\n `Package ${packageName} has invalid manifest export configuration for ${exportKey}`,\n { packageName, exportKey },\n );\n if (shouldWarn) {\n logger.warn(\n `Package ${packageName} has invalid manifest export configuration for ${exportKey}`,\n );\n }\n return null;\n }\n\n if (!manifestRelativePath.endsWith('.json')) {\n recordRegistryDiagnostic(\n 'error',\n 'MANIFEST_EXPORT_NOT_JSON',\n `Package ${packageName} must export a JSON manifest for ${exportKey}, received ${manifestRelativePath}`,\n { packageName, exportKey, manifestRelativePath },\n );\n if (shouldWarn) {\n logger.warn(\n `Package ${packageName} must export a JSON manifest for ${exportKey}, received ${manifestRelativePath}`,\n );\n }\n return null;\n }\n\n const manifestPath = builtins.path.join(packageDir, manifestRelativePath);\n if (builtins.fs.existsSync(manifestPath)) {\n return manifestPath;\n }\n\n const workspaceSourceManifest =\n resolveWorkspaceSourceManifestPathSync(packageDir);\n if (workspaceSourceManifest) {\n return workspaceSourceManifest;\n }\n\n recordRegistryDiagnostic(\n 'error',\n 'MANIFEST_EXPORT_NOT_FOUND',\n `Package ${packageName} declares manifest export ${manifestRelativePath}, but no manifest file was found.`,\n { packageName, exportKey, manifestRelativePath },\n );\n if (shouldWarn) {\n logger.warn(\n `Package ${packageName} declares manifest export ${manifestRelativePath}, but no manifest file was found.`,\n );\n }\n return null;\n }\n\n return null;\n}\n\n// ── Manifest reading (seeds caches) ──────────────────────────────────────\n\n/**\n * Load smrt-core's own bundled manifest from disk, caching in\n * `__smrtManifestStatic`. Resolves against this module's own `import.meta.url`\n * so paths work whether the code is running from `dist/` or `src/`.\n */\nexport function loadStaticManifestSyncWithNode(): SmartObjectManifest | null {\n const globals = getManifestGlobals();\n\n if (globals.__smrtManifestStatic !== undefined) {\n return globals.__smrtManifestStatic ?? null;\n }\n\n const builtins = getNodeBuiltins();\n globals.__smrtManifestStaticLoadAttempted = true;\n if (!builtins) {\n const fallbackManifest = createEmptyStaticManifest();\n globals.__smrtManifestStatic = fallbackManifest;\n return fallbackManifest;\n }\n\n // store.ts lives at src/manifest/store.ts (source) → dist/manifest/store.js\n // (compiled). In src, the static manifest is a sibling JSON; in dist, it is\n // the package's dist/manifest.json one level up.\n const candidates = import.meta.url.endsWith('.ts')\n ? [\n new URL('./static-manifest.json', import.meta.url),\n new URL('../../dist/manifest.json', import.meta.url),\n ]\n : [new URL('../manifest.json', import.meta.url)];\n\n for (const candidate of candidates) {\n try {\n if (!builtins.fs.existsSync(candidate)) {\n continue;\n }\n\n const manifest = JSON.parse(\n builtins.fs.readFileSync(candidate, 'utf-8'),\n ) as SmartObjectManifest;\n\n if (!manifest.objects || typeof manifest.objects !== 'object') {\n continue;\n }\n\n const cachedManifest = manifest.packageName\n ? manifest\n : { ...manifest, packageName: '@happyvertical/smrt-core' };\n globals.__smrtManifestStatic = cachedManifest;\n return cachedManifest;\n } catch {\n // Try the next candidate and fall back to the empty manifest if needed.\n }\n }\n\n const fallbackManifest = createEmptyStaticManifest();\n globals.__smrtManifestStatic = fallbackManifest;\n return fallbackManifest;\n}\n\n/**\n * Load a package's published manifest by package name, caching in\n * `__smrtManifestCache` under the package key. Returns null on miss or\n * invalid shape; records a diagnostic instead of throwing.\n */\nexport function loadExternalManifestSyncWithNode(\n packageName: string,\n options: ManifestLoadOptions = {},\n): SmartObjectManifest | null {\n const cache = getManifestCache();\n\n if (cache.has(packageName)) {\n return cache.get(packageName) || null;\n }\n\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n const manifestPath = resolveManifestExportPathSync(packageName, options);\n if (!manifestPath) {\n return null;\n }\n\n try {\n const manifest = JSON.parse(\n builtins.fs.readFileSync(manifestPath, 'utf-8'),\n ) as SmartObjectManifest;\n\n if (!manifest.objects || typeof manifest.objects !== 'object') {\n if (options.warn ?? true) {\n logger.warn(`Invalid manifest structure for package ${packageName}`);\n }\n return null;\n }\n\n const cachedManifest = manifest.packageName\n ? manifest\n : { ...manifest, packageName };\n\n cache.set(packageName, cachedManifest);\n\n return cachedManifest;\n } catch (error) {\n if (options.warn ?? true) {\n logger.warn(\n `Failed to load manifest for package ${packageName}: ${error instanceof Error ? error.message : error}`,\n );\n }\n return null;\n }\n}\n\n/**\n * Load a manifest from an explicit filesystem path, caching in\n * `__smrtManifestCache` under the manifest's packageName (or the path as a\n * fallback key). Used by `loadAllManifests({ manifestPaths })` in registry.ts\n * and by the Release B `ExplicitPathsManifestSource`.\n */\nexport function loadManifestFromPathSyncWithNode(\n manifestPath: string,\n): SmartObjectManifest | null {\n const builtins = getNodeBuiltins();\n if (!builtins) {\n return null;\n }\n\n try {\n const manifest = JSON.parse(\n builtins.fs.readFileSync(manifestPath, 'utf-8'),\n ) as SmartObjectManifest;\n\n if (!manifest.objects || typeof manifest.objects !== 'object') {\n logger.warn(`Invalid manifest structure at ${manifestPath}`);\n return null;\n }\n\n getManifestCache().set(manifest.packageName || manifestPath, manifest);\n\n return manifest;\n } catch (error) {\n logger.warn(\n `Failed to load manifest from path ${manifestPath}: ${error instanceof Error ? error.message : error}`,\n );\n return null;\n }\n}\n\n/**\n * Synchronous lookup across every cached manifest source, in priority order.\n *\n * Matches the historical order from manifest-loader.ts and registry.ts:\n * local test → core test → static (smrt-core bundled) → external cache.\n * The Release B `CompositeManifestSource` wraps the same priority behind a\n * higher-level abstraction.\n */\nexport function discoverCachedManifestSync(\n className: string,\n): SmartObjectDefinition | undefined {\n const manifests: Array<SmartObjectManifest | null | undefined> = [\n getLocalTestManifestCache(),\n getTestManifestCache(),\n loadStaticManifestSyncWithNode(),\n ...getManifestCache().values(),\n ];\n\n for (const manifest of manifests) {\n const entry = lookupCachedManifestEntry(manifest, className);\n if (entry) {\n return entry;\n }\n }\n\n return undefined;\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-core",
5
- "packageVersion": "0.40.69",
5
+ "packageVersion": "0.41.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-core:SmrtClass": {
8
8
  "name": "smrtclass",
@@ -100,12 +100,6 @@
100
100
  }
101
101
  },
102
102
  "indexes": [
103
- {
104
- "name": "smrt_classes_id_idx",
105
- "columns": [
106
- "id"
107
- ]
108
- },
109
103
  {
110
104
  "name": "smrt_classes_slug_context_idx",
111
105
  "columns": [
@@ -113,6 +107,12 @@
113
107
  "context"
114
108
  ],
115
109
  "unique": true
110
+ },
111
+ {
112
+ "name": "smrt_classes_created_at_idx",
113
+ "columns": [
114
+ "created_at"
115
+ ]
116
116
  }
117
117
  ],
118
118
  "version": "234a72b0"
@@ -569,12 +569,6 @@
569
569
  }
570
570
  },
571
571
  "indexes": [
572
- {
573
- "name": "smrt_collections_id_idx",
574
- "columns": [
575
- "id"
576
- ]
577
- },
578
572
  {
579
573
  "name": "smrt_collections_slug_context_idx",
580
574
  "columns": [
@@ -582,6 +576,12 @@
582
576
  "context"
583
577
  ],
584
578
  "unique": true
579
+ },
580
+ {
581
+ "name": "smrt_collections_created_at_idx",
582
+ "columns": [
583
+ "created_at"
584
+ ]
585
585
  }
586
586
  ],
587
587
  "version": "295b0a80"
@@ -1278,12 +1278,6 @@
1278
1278
  }
1279
1279
  },
1280
1280
  "indexes": [
1281
- {
1282
- "name": "smrt_objects_id_idx",
1283
- "columns": [
1284
- "id"
1285
- ]
1286
- },
1287
1281
  {
1288
1282
  "name": "smrt_objects_slug_context_idx",
1289
1283
  "columns": [
@@ -1291,6 +1285,12 @@
1291
1285
  "context"
1292
1286
  ],
1293
1287
  "unique": true
1288
+ },
1289
+ {
1290
+ "name": "smrt_objects_created_at_idx",
1291
+ "columns": [
1292
+ "created_at"
1293
+ ]
1294
1294
  }
1295
1295
  ],
1296
1296
  "version": "204bf3c4"
@@ -1,13 +1,32 @@
1
- import { SchemaChange, SchemaDefinition, SchemaDiff } from '../schema/types.js';
1
+ import { SchemaChange, SchemaDefinition, SchemaDiff, SQLDataType } from '../schema/types.js';
2
2
  import { DatabaseInterface } from './types.js';
3
3
  /**
4
4
  * Options for schema comparison
5
5
  */
6
6
  export interface DiffOptions {
7
- /** Include dropped tables in diff (default: false for safety) */
7
+ /**
8
+ * Emit executable `DROP TABLE` entries (`dropped_tables`) for tables that
9
+ * exist in the database but not in the manifest. Default: false. Orphan
10
+ * tables are always *reported* via `SchemaDiff.orphan_tables` regardless.
11
+ */
8
12
  includeDroppedTables?: boolean;
9
- /** Include dropped columns in diff (default: false for safety) */
13
+ /**
14
+ * Emit executable `drop_column` changes for columns that exist in the
15
+ * database but not in the manifest. Default: false. Orphan columns are
16
+ * always *reported* as `orphan_column` advisories regardless — a NOT NULL
17
+ * orphan without a default is flagged as a warning because every ORM
18
+ * insert (which never supplies the column) will fail on it (#2369).
19
+ */
10
20
  includeDroppedColumns?: boolean;
21
+ /**
22
+ * Execute constraint relaxations instead of only reporting them:
23
+ * `DROP NOT NULL` / `DROP DEFAULT` on live columns that are stricter than
24
+ * the manifest, and `DROP NOT NULL` on orphan NOT NULL columns. Default:
25
+ * false — relaxations are reported as advisories with the suggested SQL.
26
+ * Strengthening repairs (`SET NOT NULL`, `SET DEFAULT`) are always emitted
27
+ * where the engine supports `ALTER COLUMN` (#2369).
28
+ */
29
+ relaxColumns?: boolean;
11
30
  /**
12
31
  * Drop indexes that exist in the database but are absent from the manifest
13
32
  * AND are not functionally equivalent to anything in the manifest. Default:
@@ -15,6 +34,11 @@ export interface DiffOptions {
15
34
  * implicit indexes that PostgreSQL creates from inline UNIQUE constraints
16
35
  * (`*_key`) — those are owned by table-level constraints, not by the
17
36
  * index list, and dropping them here would break the constraint.
37
+ *
38
+ * Independent of this flag, a redundant single-column index over the
39
+ * table's primary key that the manifest no longer declares (the legacy
40
+ * `<table>_id_idx`, #2359) is always dropped: the primary-key constraint
41
+ * keeps serving every lookup, so the drop can only save write cost.
18
42
  */
19
43
  includeDroppedIndexes?: boolean;
20
44
  /** Ignore type mismatches (just log warnings) */
@@ -38,6 +62,31 @@ export interface DiffOptions {
38
62
  legacyTimezone: 'UTC';
39
63
  };
40
64
  }
65
+ /**
66
+ * Name of the unique index that backs a `unique: true` column. Mirrors the
67
+ * `<table>_<column>_key` name PostgreSQL gives the implicit index of an
68
+ * inline column UNIQUE constraint, so the same column ends up with the same
69
+ * index name on every engine and the orphan-index sweep treats it as
70
+ * constraint-owned.
71
+ */
72
+ export declare function uniqueColumnIndexName(tableName: string, columnName: string): string;
73
+ /**
74
+ * True when a change is report-only: it carries an advisory and no
75
+ * executable statement. Such changes never reach the migration stream.
76
+ */
77
+ export declare function isAdvisoryOnlyChange(change: SchemaChange): boolean;
78
+ /**
79
+ * True for an info-level report-only change: listed in `SchemaDiff.changes`
80
+ * for visibility but not counted by `has_changes` (a harmless orphan column,
81
+ * a live default the manifest no longer declares).
82
+ */
83
+ export declare function isInfoOnlyChange(change: SchemaChange): boolean;
84
+ /**
85
+ * True when a change has no executable DDL: `type_mismatch`, an advisory-only
86
+ * change, or a change whose only SQL is an advisory comment (e.g. a SQLite
87
+ * `ALTER COLUMN` the engine cannot perform in place).
88
+ */
89
+ export declare function isManualOrAdvisoryChange(change: SchemaChange): boolean;
41
90
  /**
42
91
  * Normalize a partial-index `WHERE` predicate so semantically-identical
43
92
  * clauses from different sources compare equal (issue #1692).
@@ -67,6 +116,33 @@ export declare function normalizeIndexPredicate(where?: string | null): string;
67
116
  * `pg_indexes.indexdef`. Returns '' for a non-partial index.
68
117
  */
69
118
  export declare function extractIndexPredicate(createIndexSql: string): string;
119
+ /**
120
+ * Canonical form of a column default for drift comparison (#2369).
121
+ *
122
+ * The manifest side is the DDL fragment `formatDefaultValue()` would emit
123
+ * (`'anon'`, `0`, `TRUE`, `'{}'`, `current_timestamp`); the database side is
124
+ * whatever the catalog reports, which each engine renders differently:
125
+ * PostgreSQL appends casts (`'anon'::text`, `'{}'::jsonb`, `'-1'::integer`)
126
+ * and upper-cases `CURRENT_TIMESTAMP`, DuckDB wraps booleans
127
+ * (`CAST('t' AS BOOLEAN)`), SQLite echoes the literal verbatim. Both sides
128
+ * are reduced by the *manifest column type* to a `{kind, key}` pair:
129
+ *
130
+ * - `none` — no default (`undefined`, empty, or the `NULL` keyword)
131
+ * - `now` — the current-timestamp family (`current_timestamp`, `now()`,
132
+ * `transaction_timestamp()`, `datetime('now')`)
133
+ * - `number` — numeric columns, compared by numeric value
134
+ * - `bool` — boolean columns (`true`/`false`/`t`/`f`/`1`/`0`)
135
+ * - `text` — string literal contents (JSON columns compare the parsed
136
+ * value when both sides parse)
137
+ * - `func` — other function calls / keywords, compared lowercase and
138
+ * whitespace-collapsed
139
+ * - `raw` — could not be classified; callers must skip the comparison
140
+ * rather than risk a false positive
141
+ */
142
+ export declare function canonicalizeDefault(fragment: string | undefined, columnType: SQLDataType): {
143
+ kind: 'none' | 'now' | 'number' | 'bool' | 'text' | 'func' | 'raw';
144
+ key: string;
145
+ };
70
146
  /**
71
147
  * SchemaComparer class for comparing manifest schemas to database
72
148
  */
@@ -85,9 +161,88 @@ export declare class SchemaComparer {
85
161
  */
86
162
  compareTable(tableName: string, manifest: SchemaDefinition): Promise<SchemaChange[]>;
87
163
  /**
88
- * Compare columns between manifest and database
164
+ * Compare columns between manifest and database.
165
+ *
166
+ * Three families of drift (#2369):
167
+ *
168
+ * 1. **Missing column** — emit `add_column` with an executable plan for the
169
+ * engine (see {@link generateAddColumnChanges}).
170
+ * 2. **Existing column** — type drift (as before), then nullability and
171
+ * default drift (see {@link compareColumnConstraints}).
172
+ * 3. **Orphan column** (in DB, not in manifest) — always reported. With
173
+ * `includeDroppedColumns` it becomes an executable `drop_column`; a
174
+ * NOT NULL orphan without a default is a `warning` advisory because ORM
175
+ * inserts never supply it and will fail (`23502`); with `relaxColumns`
176
+ * that orphan gets an executable `DROP NOT NULL` where the engine allows.
89
177
  */
90
178
  private compareColumns;
179
+ /**
180
+ * Compare nullability and default between a manifest column and the live
181
+ * column, emitting `alter_column` changes (#2369).
182
+ *
183
+ * Rules:
184
+ * - Primary-key columns are owned by the PK constraint (SQLite reports a
185
+ * `TEXT PRIMARY KEY` as nullable while PostgreSQL/DuckDB imply NOT NULL),
186
+ * so they are skipped entirely.
187
+ * - Strengthening (`SET NOT NULL`, `SET DEFAULT`) is emitted as executable
188
+ * SQL on engines with `ALTER COLUMN` (PostgreSQL/DuckDB). A `SET NOT NULL`
189
+ * is preceded by a backfill of the manifest default when there is one;
190
+ * without a default the live data is probed and, if NULLs exist, the
191
+ * change is reported as manual with the suggested backfill instead of
192
+ * emitting an ALTER the engine would reject mid-batch.
193
+ * - Relaxing (`DROP NOT NULL`, `DROP DEFAULT`) is reported as an advisory
194
+ * unless `relaxColumns` is set, in which case it is executable.
195
+ * - SQLite has no `ALTER COLUMN`; every alteration is reported as manual
196
+ * (advisory-comment SQL) until the table-rebuild path (#2370) lands.
197
+ * - Defaults are compared through {@link canonicalizeDefault}; when either
198
+ * side cannot be canonicalized the comparison is skipped rather than
199
+ * risking a false positive that would churn every run.
200
+ */
201
+ private compareColumnConstraints;
202
+ /**
203
+ * Assemble an `alter_column` change.
204
+ *
205
+ * - `statements: null` means the engine cannot alter the column in place
206
+ * (SQLite): the change is reported as manual with advisory-comment SQL.
207
+ * - `relaxation` marks a weakening change: without `relaxColumns` it is
208
+ * emitted as a report-only advisory (no SQL); with it, the statements are
209
+ * executable.
210
+ */
211
+ private buildAlterColumnChange;
212
+ /**
213
+ * Describe a DB column that has no manifest counterpart. Blocking when it
214
+ * is NOT NULL without a default: ORM inserts never supply it, so every
215
+ * insert fails with a not-null violation while `db:status` would otherwise
216
+ * say the schema is in sync (#2369).
217
+ */
218
+ private describeOrphanColumn;
219
+ /**
220
+ * Remediation hint for a relaxation advisory. On engines with `ALTER
221
+ * COLUMN` the flag executes it; SQLite cannot relax in place, so the hint
222
+ * says so instead of implying the flag will act (#2370).
223
+ */
224
+ private relaxHint;
225
+ private describeDbColumn;
226
+ /**
227
+ * Whether the engine can alter nullability/defaults of an existing column
228
+ * in place. SQLite cannot (`ALTER TABLE ... ALTER COLUMN` does not exist);
229
+ * PostgreSQL, DuckDB and the DuckDB-backed JSON adapter can.
230
+ */
231
+ private supportsAlterColumn;
232
+ private generateSetDefaultSQL;
233
+ /**
234
+ * Live-data probe: does the table hold any row? Used to decide whether an
235
+ * added required column without a default can be enforced NOT NULL right
236
+ * away. Errors (adapter without raw query, etc.) resolve to `true` so the
237
+ * caller takes the conservative path.
238
+ */
239
+ private tableHasRows;
240
+ /**
241
+ * Live-data probe: does the column hold any NULL? Used before emitting a
242
+ * `SET NOT NULL` that has no default to backfill with. Errors resolve to
243
+ * `true` (conservative: report instead of emitting an ALTER that fails).
244
+ */
245
+ private columnHasNulls;
91
246
  private isUuidTextEquivalentColumn;
92
247
  private isStructuralUuidCompatibleColumn;
93
248
  /**
@@ -114,7 +269,10 @@ export declare class SchemaComparer {
114
269
  * caller opts in via `includeDroppedIndexes`, and even then never for
115
270
  * PostgreSQL implicit indexes (`*_pkey`, `*_key`) — those are owned by
116
271
  * table-level constraints and need a separate `DROP CONSTRAINT` path
117
- * that the differ does not emit yet.
272
+ * that the differ does not emit yet. One exception is unconditional: a
273
+ * redundant single-column index over the live primary key (the legacy
274
+ * `<table>_id_idx`, #2359) is dropped even without the opt-in, because
275
+ * the primary-key constraint already serves it.
118
276
  *
119
277
  * 4. **Partial-index predicate drift / collision** — two indexes on the
120
278
  * same column(s) and uniqueness that differ only by their `WHERE`
@@ -244,9 +402,40 @@ export declare class SchemaComparer {
244
402
  private generatePostgresTimestampPreflightSQL;
245
403
  private quoteLiteral;
246
404
  /**
247
- * Generate SQL for adding a column
405
+ * Plan the changes that add a missing column so the emitted DDL is
406
+ * executable on the active engine (#2369).
407
+ *
408
+ * Engines disagree on what `ALTER TABLE ... ADD COLUMN` may carry inline
409
+ * (probed on SQLite 3.4x/3.5x, DuckDB 1.4.x, PostgreSQL 16/17):
410
+ *
411
+ * | clause | SQLite | DuckDB | PostgreSQL |
412
+ * |-----------------|--------------------------------|----------|---------------------|
413
+ * | NOT NULL + DEF | ok | rejected | ok |
414
+ * | NOT NULL, no DEF| ok on empty, rejected populated| rejected | ok empty, rej. pop. |
415
+ * | UNIQUE | rejected | rejected | ok |
416
+ * | DEFAULT | ok | ok | ok |
417
+ * | CHECK | ok | rejected | ok |
418
+ *
419
+ * so the plan is:
420
+ * - `NOT NULL` with a default: inline on SQLite/PostgreSQL (existing rows
421
+ * receive the default); DuckDB adds with `DEFAULT` then a separate
422
+ * `ALTER COLUMN ... SET NOT NULL`.
423
+ * - `NOT NULL` without a default: enforced only when the table is empty
424
+ * (inline on SQLite/PostgreSQL, `SET NOT NULL` step on DuckDB). On a
425
+ * populated table there is nothing to backfill with, so the column is
426
+ * added nullable and a manual `alter_column` (`set_not_null`) is reported
427
+ * with the exact remediation instead of emitting DDL every engine rejects.
428
+ * - `UNIQUE`: inline on PostgreSQL (a real constraint); SQLite/DuckDB get a
429
+ * separate `CREATE UNIQUE INDEX <table>_<col>_key` (the PostgreSQL
430
+ * constraint-index name, so the orphan sweep leaves it alone).
431
+ * - `CHECK`: inline where accepted; DuckDB rejects it in ADD COLUMN, so it
432
+ * is dropped there with a warning (SMRT's generators never emit `check`).
433
+ *
434
+ * The primary `add_column` change carries the whole plan in
435
+ * `sqlStatements` (and the last statement in `sql`) so both the CLI and the
436
+ * orchestrator execute it as one unit.
248
437
  */
249
- private generateAddColumnSQL;
438
+ private generateAddColumnChanges;
250
439
  /**
251
440
  * Generate SQL for dropping a column
252
441
  */
@@ -262,6 +451,11 @@ export declare class SchemaComparer {
262
451
  * reject partial indexes — the predicate is dropped so the emitted DDL stays
263
452
  * executable (a partial index degrades to a full index there). The predicate
264
453
  * is trimmed and a redundant leading `WHERE` stripped for robustness.
454
+ *
455
+ * `IF NOT EXISTS` matches the canonical `generateIndexes()` DDL path and
456
+ * makes a retry after a partially applied batch repair the schema instead of
457
+ * erroring on the indexes that did land (issue #2362). All three engines
458
+ * (SQLite, PostgreSQL, DuckDB) accept the clause.
265
459
  */
266
460
  private generateAddIndexSQL;
267
461
  /**
@@ -285,11 +479,19 @@ export declare class SchemaComparer {
285
479
  */
286
480
  export declare function generateSchemaDiff(db: DatabaseInterface, manifestSchemas: Record<string, SchemaDefinition>, options?: DiffOptions): Promise<SchemaDiff>;
287
481
  /**
288
- * Check if a diff has any actionable changes (excluding type mismatches)
482
+ * Check if a diff has any actionable changes a table to create or drop, or
483
+ * a change with executable DDL. Incompatible type mismatches, report-only
484
+ * advisories (orphan columns/indexes, relaxations the caller has not opted
485
+ * into) and comment-only changes (SQLite in-place limitations, live data
486
+ * blocking a repair) are not actionable; they surface through
487
+ * `unactionableChanges` / the CLI's manual bucket instead.
289
488
  */
290
489
  export declare function hasActionableChanges(diff: SchemaDiff): boolean;
291
490
  /**
292
- * Get SQL statements from a diff for execution
491
+ * Get SQL statements from a diff for execution. Advisory-only changes carry
492
+ * no statements and are skipped; comment-only statements (SQLite in-place
493
+ * limitations) pass through unchanged so callers can filter them the way
494
+ * they already do for `type_upgrade`.
293
495
  */
294
496
  export declare function getSQLFromDiff(diff: SchemaDiff): string[];
295
497
  //# sourceMappingURL=differ.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/migrations/differ.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/migrations/differ.ts"],"names":[],"mappings":"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"}