@happyvertical/smrt-core 0.40.67 → 0.40.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -194,6 +194,15 @@ decorators through `oxc.decorator` instead. Consumers still pinned on vite<8 nee
194
194
  the legacy `esbuild.tsconfigRaw` form with `experimentalDecorators: true,
195
195
  emitDecoratorMetadata: true`.
196
196
 
197
+ For independent CI invocations, both `smrtPlugin()` and `smrtConsumer()` accept
198
+ the same `generationSnapshot: { path, sha256, provenance, sourceRoot }`. The
199
+ schema-v1 snapshot produced by `serializeSmrtGenerationSnapshot()` contains the
200
+ merged project/dependency manifest, portable source paths, and source-file
201
+ digests; each plugin selects its own view. Reuse mode fails closed on
202
+ byte/provenance/path/content drift, skips scans and manifest writes, and still
203
+ generates routes, types, registration, and virtual modules. Omit it for normal
204
+ local development and watch mode.
205
+
197
206
  ## Gotchas
198
207
 
199
208
  - **Filesystem support is a lazy boundary (#1979)**: `SmrtClass` acquires `options.fs` adapters via `createFilesystemAdapter()` (`src/filesystem-loader.ts`), never a static `@happyvertical/files` import — the files SDK statically pulls @aws-sdk/client-s3 and reaches googleapis, and a static edge here would land it in every downstream SSR bundle. Node/tsx/vite-dev runtimes resolve it on first use; fully-bundled deployments import `@happyvertical/smrt-core/filesystem` at startup. Use `importOptionalDependency()` (`src/lazy-external.ts`) for any similar optional heavyweight dependency.
package/README.md CHANGED
@@ -136,6 +136,61 @@ pnpm smrt db:migrate
136
136
  Runtime verifies application tables but does not create them. Rebuild the
137
137
  manifest and rerun the migration after changing persisted object fields.
138
138
 
139
+ #### Reuse a verified generation snapshot in CI
140
+
141
+ Independent Vite invocations can reuse one generation snapshot prepared by an
142
+ earlier job without rescanning or rewriting it. First run one normal generation
143
+ invocation with `smrtPlugin()` and `smrtConsumer()` enabled. After both plugins
144
+ finish, `.smrt/manifest.json` contains the project and dependency views. Wrap
145
+ that aggregate once:
146
+
147
+ ```typescript
148
+ import { readFileSync, writeFileSync } from 'node:fs';
149
+ import {
150
+ serializeSmrtGenerationSnapshot,
151
+ sha256SmrtGenerationSnapshot,
152
+ } from '@happyvertical/smrt-core/vite-plugin';
153
+
154
+ const provenance = process.env.GITHUB_SHA!;
155
+ const sourceRoot = process.env.GITHUB_WORKSPACE ?? process.cwd();
156
+ const manifest = JSON.parse(readFileSync('.smrt/manifest.json', 'utf8'));
157
+ const bytes = serializeSmrtGenerationSnapshot(manifest, provenance, {
158
+ sourceRoot,
159
+ });
160
+ writeFileSync('.ci/smrt-generation-snapshot.json', bytes);
161
+ console.log(sha256SmrtGenerationSnapshot(bytes));
162
+ ```
163
+
164
+ Transport the exact bytes and digest together, then configure the consumers
165
+ with caller-trusted provenance (normally the checked-out commit or tree). Both
166
+ plugins use the same snapshot; each selects its own manifest view. `sourceRoot`
167
+ is the current checkout root, so normalized source paths remain portable across
168
+ workers:
169
+
170
+ ```typescript
171
+ import { smrtConsumer } from '@happyvertical/smrt-core/consumer-plugin';
172
+ import { smrtPlugin } from '@happyvertical/smrt-core/vite-plugin';
173
+
174
+ const provenance = process.env.GITHUB_SHA!;
175
+
176
+ const generationSnapshot = {
177
+ path: '.ci/smrt-generation-snapshot.json',
178
+ sha256: process.env.SMRT_GENERATION_SNAPSHOT_SHA256!,
179
+ provenance,
180
+ sourceRoot: process.env.GITHUB_WORKSPACE ?? process.cwd(),
181
+ };
182
+
183
+ smrtPlugin({ generationSnapshot });
184
+ smrtConsumer({ generationSnapshot });
185
+ ```
186
+
187
+ Both plugins fail closed when the snapshot is missing, malformed, has different
188
+ bytes, declares different provenance, cannot resolve its portable paths, or the
189
+ current source-file digests differ from the prepared inputs. Reuse mode still
190
+ generates routes, types, registration, and virtual modules, but it disables
191
+ source/package scans, watch rescans, and manifest writes. Omit
192
+ `generationSnapshot` for normal local development.
193
+
139
194
  ### Generated SvelteKit routes
140
195
 
141
196
  Enable SvelteKit route generation with `svelteKit: { enabled: true }`. Its
@@ -1,4 +1,6 @@
1
1
  import { Plugin } from 'vite';
2
+ import { SmrtGenerationSnapshotOptions } from '../generation-snapshot.js';
3
+ export { loadVerifiedSmrtGenerationSnapshot, type SerializeSmrtGenerationSnapshotOptions, type SmrtGenerationSnapshotArtifact, type SmrtGenerationSnapshotOptions, type SmrtGenerationSnapshotView, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot, } from '../generation-snapshot.js';
2
4
  export interface SmrtConsumerOptions {
3
5
  /** SMRT packages to scan (e.g., ['@my-org/products', '@my-org/content']) */
4
6
  packages?: string[];
@@ -8,6 +10,12 @@ export interface SmrtConsumerOptions {
8
10
  typesDir?: string;
9
11
  /** Project root path */
10
12
  projectRoot?: string;
13
+ /**
14
+ * Reuse an immutable, verified aggregated manifest instead of discovering
15
+ * packages or writing `.smrt/manifest.json`. Registration and generated
16
+ * types still consume the verified manifest.
17
+ */
18
+ generationSnapshot?: SmrtGenerationSnapshotOptions;
11
19
  /** SvelteKit integration mode */
12
20
  svelteKit?: boolean;
13
21
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAwDnC,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAiBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAuHtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,kCAAkC,EAClC,KAAK,sCAAsC,EAC3C,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AAoDnC,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IACnD,iCAAiC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAiBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAkJtE"}
@@ -1,4 +1,5 @@
1
1
  import "../scanner/types.js";
2
+ import { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot } from "../generation-snapshot.js";
2
3
  import { generateClientModule } from "../vite-plugin/generated-client.js";
3
4
  import { generateDeclarations } from "../prebuild/index.js";
4
5
  import * as fs from "node:fs";
@@ -19,10 +20,14 @@ var VIRTUAL_MODULES = {
19
20
  * Consumer plugin for projects that use SMRT packages
20
21
  */
21
22
  function smrtConsumer(options = {}) {
22
- const { packages = [], generateTypes = true, typesDir = "src/types/smrt-generated", projectRoot = process.cwd(), disableScanning = false, kebabRoutes = false } = options;
23
+ const { packages = [], generateTypes = true, typesDir = "src/types/smrt-generated", projectRoot = process.cwd(), generationSnapshot, disableScanning = false, kebabRoutes = false } = options;
23
24
  let smrtPackages = [];
24
25
  let typeManifest = null;
25
26
  let typesGenerated = false;
27
+ function loadGenerationSnapshot() {
28
+ if (!generationSnapshot) throw new Error("[smrt:consumer] Generation snapshot is not configured");
29
+ return loadVerifiedSmrtGenerationSnapshot(generationSnapshot, projectRoot, "dependencies");
30
+ }
26
31
  return {
27
32
  name: "smrt-consumer",
28
33
  config() {
@@ -30,6 +35,16 @@ function smrtConsumer(options = {}) {
30
35
  },
31
36
  async buildStart() {
32
37
  console.log("[smrt:consumer] Initializing SMRT consumer plugin");
38
+ if (generationSnapshot) {
39
+ typeManifest = loadGenerationSnapshot();
40
+ console.log(`[smrt:consumer] Reusing verified generation snapshot (${generationSnapshot.provenance})`);
41
+ await generateRegistrationFile(typeManifest, projectRoot);
42
+ if (generateTypes && !typesGenerated) {
43
+ await generateProjectTypes(typeManifest, typesDir, projectRoot);
44
+ typesGenerated = true;
45
+ }
46
+ return;
47
+ }
33
48
  if (packages.length === 0 && !disableScanning) smrtPackages = await discoverSmrtPackages(projectRoot);
34
49
  else smrtPackages = packages;
35
50
  if (smrtPackages.length > 0) {
@@ -61,7 +76,7 @@ function smrtConsumer(options = {}) {
61
76
  },
62
77
  async load(id) {
63
78
  const cleanId = id.startsWith("\0") ? id.slice(1) : id;
64
- if (!typeManifest) typeManifest = {
79
+ if (!typeManifest) typeManifest = generationSnapshot ? loadGenerationSnapshot() : {
65
80
  version: "1.0.0",
66
81
  timestamp: 0,
67
82
  objects: {}
@@ -439,6 +454,6 @@ export default manifest;
439
454
  `;
440
455
  }
441
456
  //#endregion
442
- export { smrtConsumer };
457
+ export { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot, smrtConsumer };
443
458
 
444
459
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/consumer-plugin/index.ts"],"sourcesContent":["/**\n * Vite plugin for consuming SMRT packages\n * Solves virtual module resolution in downstream projects\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Plugin } from 'vite';\nimport { generateDeclarations } from '../prebuild/index.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { generateClientModule } from '../vite-plugin/generated-client.js';\n\n/**\n * Loosely-typed view of an object definition as carried by an external\n * package's static manifest. The static manifests are read from JSON at the\n * package boundary, so only the fields this plugin consumes are typed; the\n * index signature preserves any additional fields (e.g. for spreads). This is\n * a structural superset of a manifest `SmartObjectDefinition` plus the\n * consumer-only `hasCollection` marker.\n */\ninterface ConsumerObjectDefinition {\n className?: string;\n packageName?: string;\n packageVersion?: string;\n qualifiedName?: string;\n importPath?: string;\n exportName?: string;\n collectionExportName?: string;\n hasCollection?: boolean;\n collection?: string;\n extends?: string;\n extendsQualified?: string;\n extendsTypeArg?: string;\n [key: string]: unknown;\n}\n\n/**\n * Aggregated manifest assembled by the consumer plugin from one or more\n * external package manifests. Loosely typed because the inputs originate from\n * JSON read at the package boundary.\n */\ninterface ConsumerManifest {\n version: string;\n timestamp: number;\n packageName?: string;\n packageVersion?: string;\n objects: Record<string, ConsumerObjectDefinition>;\n}\n\n/**\n * Minimal structural shape of a parsed `package.json` consumed here (name,\n * version, and the export map used to derive import paths). The index\n * signature keeps the remaining fields accessible.\n */\ninterface ConsumerPackageJson {\n name?: string;\n version?: string;\n main?: string;\n exports?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface SmrtConsumerOptions {\n /** SMRT packages to scan (e.g., ['@my-org/products', '@my-org/content']) */\n packages?: string[];\n /** Generate TypeScript declarations */\n generateTypes?: boolean;\n /** Output directory for generated types */\n typesDir?: string;\n /** Project root path */\n projectRoot?: string;\n /** SvelteKit integration mode */\n svelteKit?: boolean;\n /**\n * Apply kebab-case to generated custom-method URL segments. This must match\n * the producer plugin's `svelteKit.kebabRoutes` setting.\n */\n kebabRoutes?: boolean;\n /** Use static types only (for federation builds) */\n staticTypes?: boolean;\n /** Disable file scanning */\n disableScanning?: boolean;\n}\n\n// Distinct resolved ids per plugin (#1795). smrtPlugin resolves\n// `@happyvertical/smrt-virt-*` to `\\0smrt:*`; if this consumer plugin also\n// resolved its `@smrt/*` specifiers to `\\0smrt:*` the two virtual modules would\n// share a rollup id, and in standalone/federation builds the consumer's\n// fallback `load` would non-deterministically win and shadow smrtPlugin's real\n// module. Namespacing the consumer ids (`\\0smrt-consumer:*`) keeps them\n// separate so each plugin only ever loads its own module.\nconst VIRTUAL_MODULES = {\n '@smrt/routes': 'smrt-consumer:routes',\n '@smrt/client': 'smrt-consumer:client',\n '@smrt/mcp': 'smrt-consumer:mcp',\n '@smrt/types': 'smrt-consumer:types',\n '@smrt/manifest': 'smrt-consumer:manifest',\n};\n\n/**\n * Consumer plugin for projects that use SMRT packages\n */\nexport function smrtConsumer(options: SmrtConsumerOptions = {}): Plugin {\n const {\n packages = [],\n generateTypes = true,\n typesDir = 'src/types/smrt-generated',\n projectRoot = process.cwd(),\n disableScanning = false,\n kebabRoutes = false,\n } = options;\n\n let smrtPackages: string[] = [];\n let typeManifest: ConsumerManifest | null = null;\n let typesGenerated = false;\n\n return {\n name: 'smrt-consumer',\n\n config() {\n return {\n build: {\n rollupOptions: {\n // Runtime registration evaluates provider entry points so their\n // exact constructors can be registered. Leave optional native\n // provider binaries to Node instead of parsing them as JavaScript.\n external: [/\\.node$/],\n },\n },\n };\n },\n\n async buildStart() {\n console.log('[smrt:consumer] Initializing SMRT consumer plugin');\n\n // Discover SMRT packages if not explicitly specified\n if (packages.length === 0 && !disableScanning) {\n smrtPackages = await discoverSmrtPackages(projectRoot);\n } else {\n smrtPackages = packages;\n }\n\n if (smrtPackages.length > 0) {\n console.log(\n `[smrt:consumer] Found SMRT packages: ${smrtPackages.join(', ')}`,\n );\n\n // Aggregate type manifests from discovered packages\n typeManifest = await aggregateTypeManifests(smrtPackages, projectRoot);\n\n // Save aggregated manifest for CLI discovery\n await saveAggregatedManifest(typeManifest, projectRoot);\n\n // Generate registration file for CLI class loading\n await generateRegistrationFile(typeManifest, projectRoot);\n\n // Generate types if requested\n if (generateTypes && !typesGenerated) {\n await generateProjectTypes(typeManifest, typesDir, projectRoot);\n typesGenerated = true;\n }\n } else {\n console.log('[smrt:consumer] No SMRT packages found');\n typeManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n };\n }\n },\n\n resolveId(id, _importer) {\n // Resolve virtual modules to generated type declarations\n if (id in VIRTUAL_MODULES) {\n const typeFileName = getTypeFileName(id);\n const typePath = path.join(projectRoot, typesDir, typeFileName);\n\n // If types file exists, resolve to it\n if (fs.existsSync(typePath)) {\n return typePath;\n }\n\n // Otherwise use virtual module ID for runtime resolution\n return `\\0${VIRTUAL_MODULES[id as keyof typeof VIRTUAL_MODULES]}`;\n }\n return null;\n },\n\n async load(id) {\n // Handle virtual modules if types aren't available\n const cleanId = id.startsWith('\\0') ? id.slice(1) : id;\n\n if (!typeManifest) {\n typeManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n };\n }\n\n switch (cleanId) {\n case 'smrt-consumer:routes':\n return generateFallbackRoutesModule();\n\n case 'smrt-consumer:client':\n return generateFallbackClientModule(typeManifest, { kebabRoutes });\n\n case 'smrt-consumer:mcp':\n return generateFallbackMcpModule();\n\n case 'smrt-consumer:types':\n return generateFallbackTypesModule(typeManifest);\n\n case 'smrt-consumer:manifest':\n return generateFallbackManifestModule(typeManifest);\n\n default:\n return null;\n }\n },\n };\n}\n\n/**\n * Discover SMRT packages from a consumer app's dependencies.\n *\n * Intentional split (#1579): this **consumer-plugin** path is async and\n * resolves SMRT packages from the downstream app's `package.json` dependency\n * names (`@have/`/`smrt` heuristic + `hasSmrtManifest` probe) inside the Vite\n * consumer plugin. It is deliberately separate from the build-time\n * `discoverSmrtPackages()` in `src/manifest/discover-smrt-packages.ts` — a\n * synchronous, lockfile-cached `node_modules` manifest scan used for manifest\n * generation. Different inputs, contexts, and lifecycles, not duplicated logic.\n */\nasync function discoverSmrtPackages(projectRoot: string): Promise<string[]> {\n const packages: string[] = [];\n const nodeModulesPath = path.join(projectRoot, 'node_modules');\n\n if (!fs.existsSync(nodeModulesPath)) {\n return packages;\n }\n\n try {\n // Check package.json for workspace dependencies\n const packageJsonPath = path.join(projectRoot, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.peerDependencies,\n };\n\n // Look for packages that likely contain SMRT objects\n for (const [name, version] of Object.entries(allDeps)) {\n if (\n typeof version === 'string' &&\n (name.includes('smrt') ||\n name.includes('@have/') ||\n (await hasSmrtManifest(nodeModulesPath, name)))\n ) {\n packages.push(name);\n }\n }\n }\n } catch (error) {\n console.warn('[smrt:consumer] Error discovering packages:', error);\n }\n\n return packages;\n}\n\n/**\n * Check if a package has SMRT manifest\n */\nasync function hasSmrtManifest(\n nodeModulesPath: string,\n packageName: string,\n): Promise<boolean> {\n const packagePath = path.join(nodeModulesPath, packageName);\n const manifestPath = path.join(\n packagePath,\n 'dist',\n 'manifest',\n 'static-manifest.js',\n );\n return fs.existsSync(manifestPath);\n}\n\n/**\n * Aggregate type manifests from multiple packages\n */\nasync function aggregateTypeManifests(\n packages: string[],\n projectRoot: string,\n): Promise<ConsumerManifest> {\n const aggregatedManifest: ConsumerManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n };\n\n for (const packageName of packages) {\n try {\n const packageDir = path.join(projectRoot, 'node_modules', packageName);\n\n // Load package.json for version and export information\n const packageJsonPath = path.join(packageDir, 'package.json');\n let packageJson: ConsumerPackageJson;\n try {\n const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');\n packageJson = JSON.parse(packageJsonContent) as ConsumerPackageJson;\n } catch {\n console.warn(\n `[smrt:consumer] Could not read package.json for ${packageName}`,\n );\n continue;\n }\n\n // Try multiple manifest locations\n const manifestCandidates = [\n path.join(packageDir, 'dist', 'manifest', 'static-manifest.js'),\n path.join(packageDir, 'dist', 'manifest.json'),\n path.join(packageDir, 'manifest.json'),\n ];\n\n for (const manifestPath of manifestCandidates) {\n if (fs.existsSync(manifestPath)) {\n // Import or read the manifest\n let manifest: Partial<ConsumerManifest> | undefined;\n if (manifestPath.endsWith('.js')) {\n const manifestModule = await import(manifestPath);\n manifest = manifestModule.staticManifest || manifestModule.default;\n } else {\n const manifestContent = fs.readFileSync(manifestPath, 'utf-8');\n manifest = JSON.parse(manifestContent) as Partial<ConsumerManifest>;\n }\n\n if (manifest?.objects) {\n console.log(\n `[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`,\n );\n\n // ENHANCED: Preserve package metadata for each object\n for (const [objectName, objectDef] of Object.entries(\n manifest.objects,\n )) {\n const def = objectDef;\n\n aggregatedManifest.objects[objectName] = {\n ...def,\n // Ensure package metadata is preserved/set\n packageName:\n def.packageName || manifest.packageName || packageName,\n packageVersion:\n def.packageVersion ||\n manifest.packageVersion ||\n packageJson.version,\n // Add fallback import paths if missing\n importPath: def.importPath || determineImportPath(packageJson),\n exportName: def.exportName || def.className || objectName,\n collectionExportName:\n def.collectionExportName ||\n `${def.className || objectName}Collection`,\n };\n }\n\n break; // Use first found manifest for this package\n }\n }\n }\n } catch (error) {\n console.warn(\n `[smrt:consumer] Error loading manifest from ${packageName}:`,\n error,\n );\n }\n }\n\n return aggregatedManifest;\n}\n\n/**\n * Determine import path from package.json\n */\nfunction determineImportPath(packageJson: ConsumerPackageJson): string {\n const packageName = packageJson.name;\n\n if (!packageName) {\n throw new Error('Package name not found in package.json');\n }\n\n // Strategy 1: Check for specific exports\n if (packageJson.exports) {\n // Check for objects export\n if (packageJson.exports['./objects']) {\n return `${packageName}/objects`;\n }\n\n // Check for main export\n const mainExport = packageJson.exports['.'];\n if (mainExport) {\n // Handle conditional exports\n if (typeof mainExport === 'object' && mainExport !== null) {\n const conditional = mainExport as Record<string, unknown>;\n if (conditional.import) {\n return packageName;\n }\n if (conditional.default) {\n return packageName;\n }\n }\n return packageName;\n }\n }\n\n // Strategy 2: Check main field\n if (packageJson.main) {\n return packageName;\n }\n\n // Strategy 3: Fallback to package name\n return packageName;\n}\n\n/**\n * Save aggregated manifest to .smrt/manifest.json for CLI discovery.\n *\n * Merge-preserving: `smrtPlugin()` writes the project's own scanned objects\n * to the same file (`writeLocalManifest`, issue #963), and both writes happen\n * in parallel `buildStart` hooks — so a plain overwrite here would clobber\n * the local objects whenever this plugin's write lands last (issue #1760\n * review). Local field metadata would then silently vanish from CLI schema\n * commands and from server runtimes that seed `.smrt/manifest.json`, dropping\n * domain columns on write. This function therefore only ADDS/refreshes the\n * external-package entries it owns and preserves everything else already in\n * the file (including the top-level `packageName` the local write sets).\n */\nasync function saveAggregatedManifest(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const manifestPath = path.join(smrtDir, 'manifest.json');\n\n try {\n // Create .smrt directory if it doesn't exist\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Merge with whatever is on disk: existing entries (typically the local\n // project's objects written by smrtPlugin) are preserved; aggregated\n // external entries win for the qualified names this plugin owns.\n let merged: ConsumerManifest = manifest;\n if (fs.existsSync(manifestPath)) {\n try {\n const existing = JSON.parse(\n fs.readFileSync(manifestPath, 'utf-8'),\n ) as Partial<ConsumerManifest>;\n if (existing && typeof existing.objects === 'object') {\n merged = {\n ...existing,\n ...manifest,\n // The aggregated manifest carries no packageName; keep the local\n // project's (used as the manifest cache key at runtime).\n ...(existing.packageName\n ? { packageName: existing.packageName }\n : {}),\n objects: { ...existing.objects, ...manifest.objects },\n };\n }\n } catch {\n // Unreadable/corrupt existing file — fall back to a plain write.\n }\n }\n\n // Write manifest\n fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2), 'utf-8');\n\n console.log(\n `[smrt:consumer] Saved aggregated manifest to .smrt/manifest.json (${Object.keys(merged.objects).length} objects)`,\n );\n } catch (error) {\n console.warn('[smrt:consumer] Failed to save aggregated manifest:', error);\n }\n}\n\n/**\n * Generate registration file for CLI class loading\n *\n * Creates .smrt/register.js with static imports and registrations\n * for all external SMRT objects discovered during build.\n */\nasync function generateRegistrationFile(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const registerPath = path.join(smrtDir, 'register.js');\n\n // Bind every imported symbol to a generated local name. Aggregated manifests\n // may contain same-named exports from different packages (and may list a\n // collection both beside its object and as its own manifest entry), so using\n // provider export names as local bindings can produce invalid duplicate\n // imports in a production consumer bundle.\n const importBindings = new Map<string, string>();\n const importsByPath = new Map<string, Map<string, string>>();\n let nextImportBinding = 0;\n const getImportBinding = (importPath: string, exportName: string): string => {\n const key = `${importPath}\\0${exportName}`;\n const existing = importBindings.get(key);\n if (existing) return existing;\n const binding = `__smrt_consumer_${nextImportBinding++}`;\n importBindings.set(key, binding);\n const specifiers =\n importsByPath.get(importPath) ?? new Map<string, string>();\n specifiers.set(exportName, binding);\n importsByPath.set(importPath, specifiers);\n return binding;\n };\n\n const registrations: string[] = [];\n const registrationManifests: Record<string, ConsumerManifest> = {};\n let importedEntryCount = 0;\n let registeredObjectCount = 0;\n\n const manifestObjects = manifest.objects;\n const manifestObjectLookup = new Map<string, ConsumerObjectDefinition>();\n for (const [key, def] of Object.entries(manifestObjects)) {\n const candidate = def;\n const lookupKeys = [\n key,\n key.includes(':') ? key.split(':').pop() : undefined,\n candidate.qualifiedName,\n candidate.className,\n candidate.exportName,\n ];\n\n for (const lookupKey of lookupKeys) {\n if (lookupKey && !manifestObjectLookup.has(lookupKey)) {\n manifestObjectLookup.set(lookupKey, candidate);\n }\n }\n }\n\n const collectionClassMemo = new WeakMap<object, boolean>();\n\n const isCollectionClass = (\n def: ConsumerObjectDefinition | undefined,\n seen = new Set<string>(),\n ): boolean => {\n if (!def || typeof def !== 'object') {\n return false;\n }\n\n const cached = collectionClassMemo.get(def);\n if (cached !== undefined) {\n return cached;\n }\n\n if (\n def?.extends === 'SmrtCollection' ||\n def?.extendsTypeArg !== undefined\n ) {\n collectionClassMemo.set(def, true);\n return true;\n }\n\n const parentName = def?.extendsQualified || def?.extends;\n if (!parentName || seen.has(parentName)) {\n collectionClassMemo.set(def, false);\n return false;\n }\n seen.add(parentName);\n\n const parentDef = manifestObjectLookup.get(parentName);\n const isCollection = parentDef ? isCollectionClass(parentDef, seen) : false;\n collectionClassMemo.set(def, isCollection);\n\n return isCollection;\n };\n\n for (const [objectName, objectDef] of Object.entries(manifestObjects)) {\n const def = objectDef;\n\n // Skip local objects (they're imported from local entry point)\n if (!def.packageName || def.packageName === manifest.packageName) {\n continue;\n }\n\n const importPath = def.importPath || def.packageName;\n const exportName = def.exportName || def.className || objectName;\n const collectionExportName = def.collectionExportName;\n const hasCollection = def.hasCollection; // Check if collection class actually exists\n const tableName = def.collection || objectName.toLowerCase();\n\n const exportBinding = getImportBinding(importPath, exportName);\n const collectionBinding =\n hasCollection && collectionExportName\n ? getImportBinding(importPath, collectionExportName)\n : undefined;\n importedEntryCount++;\n\n if (isCollectionClass(def)) {\n continue;\n }\n\n const logicalName = def.className || exportName;\n registrationManifests[objectName] = {\n ...manifest,\n packageName: def.packageName,\n packageVersion: def.packageVersion || manifest.packageVersion,\n objects: { [objectName]: def },\n };\n\n // Import evaluation triggers the provider decorator first. The explicit\n // constructor/package/key tuple then promotes that exact constructor with\n // its isolated manifest, which is stable across Rollup name deconfliction.\n registrations.push(\n `if (${exportBinding}) ObjectRegistry.register(${exportBinding}, { name: ${JSON.stringify(logicalName)}, packageName: ${JSON.stringify(def.packageName)}, _manifest: smrtRegistrationManifests[${JSON.stringify(objectName)}], _manifestKey: ${JSON.stringify(objectName)} });`,\n );\n\n // Only register collection if it exists\n if (collectionBinding) {\n registrations.push(\n `if (${collectionBinding}) ObjectRegistry.registerCollection('${tableName}', ${collectionBinding});`,\n );\n }\n\n registeredObjectCount++;\n }\n\n // Skip generation if no external entries\n if (importedEntryCount === 0) {\n console.log('[smrt:consumer] No external entries - skipping register.js');\n return;\n }\n\n const registeredObjectLabel =\n registeredObjectCount === 1 ? 'object' : 'objects';\n const sortedImports = Array.from(importsByPath.entries()).sort(\n ([left], [right]) => left.localeCompare(right),\n );\n const imports = sortedImports.map(\n ([importPath], index) =>\n `import * as __smrt_provider_${index} from '${importPath}';`,\n );\n const importDeclarations = sortedImports.flatMap(([, specifiers], index) =>\n Array.from(specifiers.entries())\n .sort(([left], [right]) => left.localeCompare(right))\n .map(\n ([exportName, binding]) =>\n `const ${binding} = getSmrtExport(__smrt_provider_${index}, ${JSON.stringify(exportName)});`,\n ),\n );\n const registrationManifestLiteral = JSON.stringify(\n JSON.stringify(registrationManifests),\n );\n\n // Generate file content\n const content = `/**\n * Auto-generated by @happyvertical/smrt-core/consumer-plugin\n * DO NOT EDIT - This file is regenerated on every build\n *\n * Registers SMRT objects from external packages for CLI discovery.\n * Generated at: ${new Date().toISOString()}\n */\n\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n${imports.join('\\n')}\n\n/**\n * @param {Record<string, unknown>} provider\n * @param {string} exportName\n * @returns {any}\n */\nconst getSmrtExport = (provider, exportName) =>\n typeof provider[exportName] === 'function' ? provider[exportName] : undefined;\n${importDeclarations.join('\\n')}\n\nconst smrtRegistrationManifests = JSON.parse(${registrationManifestLiteral});\n\n// Register all objects (executed during module evaluation)\n${registrations.join('\\n')}\n\nexport function registerAll() {\n // Objects are already registered during module evaluation\n console.log('[smrt:register] Registered ${registeredObjectCount} external ${registeredObjectLabel}');\n}\n`;\n\n // Create .smrt directory if needed\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Write registration file\n fs.writeFileSync(registerPath, content, 'utf-8');\n\n console.log(\n `[smrt:consumer] Generated .smrt/register.js with ${importedEntryCount} external entries (${registeredObjectCount} registered ${registeredObjectLabel})`,\n );\n}\n\n/**\n * Generate project-specific types\n */\nasync function generateProjectTypes(\n typeManifest: ConsumerManifest,\n typesDir: string,\n projectRoot: string,\n): Promise<void> {\n if (!typeManifest || Object.keys(typeManifest.objects).length === 0) {\n console.log(\n '[smrt:consumer] No SMRT objects found, skipping type generation',\n );\n return;\n }\n\n await generateDeclarations({\n // The aggregated manifest is a runtime SMRT manifest assembled from external\n // package manifests; it is intentionally typed loosely at the JSON boundary,\n // so narrow it to the declaration generator's strict manifest shape here.\n manifest: typeManifest as unknown as SmartObjectManifest,\n outDir: typesDir,\n projectRoot,\n includeVirtualModules: true,\n includeObjectTypes: true,\n });\n\n console.log(\n `[smrt:consumer] Generated types for ${Object.keys(typeManifest.objects).length} objects`,\n );\n}\n\n/**\n * Get type file name for virtual module\n */\nfunction getTypeFileName(virtualModule: string): string {\n const moduleMap: Record<string, string> = {\n '@smrt/routes': 'smrt-routes.d.ts',\n '@smrt/client': 'smrt-client.d.ts',\n '@smrt/mcp': 'smrt-mcp.d.ts',\n '@smrt/types': 'smrt-types.d.ts',\n '@smrt/manifest': 'smrt-manifest.d.ts',\n };\n return moduleMap[virtualModule] || 'smrt-unknown.d.ts';\n}\n\n/**\n * Fallback modules for when types aren't available\n */\nfunction generateFallbackRoutesModule(): string {\n return `\n// Fallback routes module\nexport function setupRoutes(app) {\n console.warn('[smrt:consumer] No routes available - SMRT packages may not be properly configured');\n}\nexport default setupRoutes;\n`;\n}\n\nfunction generateFallbackClientModule(\n manifest: ConsumerManifest,\n options: { kebabRoutes?: boolean } = {},\n): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `\n// Fallback client module\nexport function createClient(basePath = '/api/v1') {\n console.warn('[smrt:consumer] No API client available - SMRT packages may not be properly configured');\n return {};\n}\nexport default createClient;\n`;\n }\n\n return generateClientModule(manifest as unknown as SmartObjectManifest, {\n kebabRoutes: options.kebabRoutes,\n });\n}\n\nfunction generateFallbackMcpModule(): string {\n return `\n// Fallback MCP module\nexport const tools = [];\nexport function createMCPServer() {\n console.warn('[smrt:consumer] No MCP tools available - SMRT packages may not be properly configured');\n return { name: 'smrt-consumer', version: '1.0.0', tools: [] };\n}\nexport default createMCPServer;\n`;\n}\n\nfunction generateFallbackTypesModule(manifest: ConsumerManifest): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `// No types available`;\n }\n\n // Generate basic interfaces\n const interfaces = objects.map(([_name, obj]) => {\n return `export interface ${obj.className}Data {\n id?: string;\n created_at?: string;\n updated_at?: string;\n [key: string]: any;\n}`;\n });\n\n return interfaces.join('\\n\\n');\n}\n\nfunction generateFallbackManifestModule(manifest: ConsumerManifest): string {\n return `\n// Auto-generated manifest from SMRT consumer\nexport const manifest = ${JSON.stringify(manifest, null, 2)};\nexport default manifest;\n`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/consumer-plugin/index.ts"],"sourcesContent":["/**\n * Vite plugin for consuming SMRT packages\n * Solves virtual module resolution in downstream projects\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { Plugin } from 'vite';\nimport {\n loadVerifiedSmrtGenerationSnapshot,\n type SmrtGenerationSnapshotOptions,\n} from '../generation-snapshot.js';\nimport { generateDeclarations } from '../prebuild/index.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { MANIFEST_TIMESTAMP } from '../scanner/types.js';\nimport { generateClientModule } from '../vite-plugin/generated-client.js';\n\nexport {\n loadVerifiedSmrtGenerationSnapshot,\n type SerializeSmrtGenerationSnapshotOptions,\n type SmrtGenerationSnapshotArtifact,\n type SmrtGenerationSnapshotOptions,\n type SmrtGenerationSnapshotView,\n serializeSmrtGenerationSnapshot,\n sha256SmrtGenerationSnapshot,\n} from '../generation-snapshot.js';\n\n/**\n * Loosely-typed view of an object definition as carried by an external\n * package's static manifest. The static manifests are read from JSON at the\n * package boundary, so only the fields this plugin consumes are typed; the\n * index signature preserves any additional fields (e.g. for spreads). This is\n * a structural superset of a manifest `SmartObjectDefinition` plus the\n * consumer-only `hasCollection` marker.\n */\ninterface ConsumerObjectDefinition {\n className?: string;\n packageName?: string;\n packageVersion?: string;\n qualifiedName?: string;\n importPath?: string;\n exportName?: string;\n collectionExportName?: string;\n hasCollection?: boolean;\n collection?: string;\n extends?: string;\n extendsQualified?: string;\n extendsTypeArg?: string;\n [key: string]: unknown;\n}\n\n/**\n * Aggregated manifest assembled by the consumer plugin from one or more\n * external package manifests. Loosely typed because the inputs originate from\n * JSON read at the package boundary.\n */\ninterface ConsumerManifest {\n version: string;\n timestamp: number;\n packageName?: string;\n packageVersion?: string;\n objects: Record<string, ConsumerObjectDefinition>;\n}\n\n/**\n * Minimal structural shape of a parsed `package.json` consumed here (name,\n * version, and the export map used to derive import paths). The index\n * signature keeps the remaining fields accessible.\n */\ninterface ConsumerPackageJson {\n name?: string;\n version?: string;\n main?: string;\n exports?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface SmrtConsumerOptions {\n /** SMRT packages to scan (e.g., ['@my-org/products', '@my-org/content']) */\n packages?: string[];\n /** Generate TypeScript declarations */\n generateTypes?: boolean;\n /** Output directory for generated types */\n typesDir?: string;\n /** Project root path */\n projectRoot?: string;\n /**\n * Reuse an immutable, verified aggregated manifest instead of discovering\n * packages or writing `.smrt/manifest.json`. Registration and generated\n * types still consume the verified manifest.\n */\n generationSnapshot?: SmrtGenerationSnapshotOptions;\n /** SvelteKit integration mode */\n svelteKit?: boolean;\n /**\n * Apply kebab-case to generated custom-method URL segments. This must match\n * the producer plugin's `svelteKit.kebabRoutes` setting.\n */\n kebabRoutes?: boolean;\n /** Use static types only (for federation builds) */\n staticTypes?: boolean;\n /** Disable file scanning */\n disableScanning?: boolean;\n}\n\n// Distinct resolved ids per plugin (#1795). smrtPlugin resolves\n// `@happyvertical/smrt-virt-*` to `\\0smrt:*`; if this consumer plugin also\n// resolved its `@smrt/*` specifiers to `\\0smrt:*` the two virtual modules would\n// share a rollup id, and in standalone/federation builds the consumer's\n// fallback `load` would non-deterministically win and shadow smrtPlugin's real\n// module. Namespacing the consumer ids (`\\0smrt-consumer:*`) keeps them\n// separate so each plugin only ever loads its own module.\nconst VIRTUAL_MODULES = {\n '@smrt/routes': 'smrt-consumer:routes',\n '@smrt/client': 'smrt-consumer:client',\n '@smrt/mcp': 'smrt-consumer:mcp',\n '@smrt/types': 'smrt-consumer:types',\n '@smrt/manifest': 'smrt-consumer:manifest',\n};\n\n/**\n * Consumer plugin for projects that use SMRT packages\n */\nexport function smrtConsumer(options: SmrtConsumerOptions = {}): Plugin {\n const {\n packages = [],\n generateTypes = true,\n typesDir = 'src/types/smrt-generated',\n projectRoot = process.cwd(),\n generationSnapshot,\n disableScanning = false,\n kebabRoutes = false,\n } = options;\n\n let smrtPackages: string[] = [];\n let typeManifest: ConsumerManifest | null = null;\n let typesGenerated = false;\n\n function loadGenerationSnapshot(): ConsumerManifest {\n if (!generationSnapshot) {\n throw new Error('[smrt:consumer] Generation snapshot is not configured');\n }\n return loadVerifiedSmrtGenerationSnapshot<ConsumerManifest>(\n generationSnapshot,\n projectRoot,\n 'dependencies',\n );\n }\n\n return {\n name: 'smrt-consumer',\n\n config() {\n return {\n build: {\n rollupOptions: {\n // Runtime registration evaluates provider entry points so their\n // exact constructors can be registered. Leave optional native\n // provider binaries to Node instead of parsing them as JavaScript.\n external: [/\\.node$/],\n },\n },\n };\n },\n\n async buildStart() {\n console.log('[smrt:consumer] Initializing SMRT consumer plugin');\n\n if (generationSnapshot) {\n typeManifest = loadGenerationSnapshot();\n console.log(\n `[smrt:consumer] Reusing verified generation snapshot (${generationSnapshot.provenance})`,\n );\n await generateRegistrationFile(typeManifest, projectRoot);\n if (generateTypes && !typesGenerated) {\n await generateProjectTypes(typeManifest, typesDir, projectRoot);\n typesGenerated = true;\n }\n return;\n }\n\n // Discover SMRT packages if not explicitly specified\n if (packages.length === 0 && !disableScanning) {\n smrtPackages = await discoverSmrtPackages(projectRoot);\n } else {\n smrtPackages = packages;\n }\n\n if (smrtPackages.length > 0) {\n console.log(\n `[smrt:consumer] Found SMRT packages: ${smrtPackages.join(', ')}`,\n );\n\n // Aggregate type manifests from discovered packages\n typeManifest = await aggregateTypeManifests(smrtPackages, projectRoot);\n\n // Save aggregated manifest for CLI discovery\n await saveAggregatedManifest(typeManifest, projectRoot);\n\n // Generate registration file for CLI class loading\n await generateRegistrationFile(typeManifest, projectRoot);\n\n // Generate types if requested\n if (generateTypes && !typesGenerated) {\n await generateProjectTypes(typeManifest, typesDir, projectRoot);\n typesGenerated = true;\n }\n } else {\n console.log('[smrt:consumer] No SMRT packages found');\n typeManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n };\n }\n },\n\n resolveId(id, _importer) {\n // Resolve virtual modules to generated type declarations\n if (id in VIRTUAL_MODULES) {\n const typeFileName = getTypeFileName(id);\n const typePath = path.join(projectRoot, typesDir, typeFileName);\n\n // If types file exists, resolve to it\n if (fs.existsSync(typePath)) {\n return typePath;\n }\n\n // Otherwise use virtual module ID for runtime resolution\n return `\\0${VIRTUAL_MODULES[id as keyof typeof VIRTUAL_MODULES]}`;\n }\n return null;\n },\n\n async load(id) {\n // Handle virtual modules if types aren't available\n const cleanId = id.startsWith('\\0') ? id.slice(1) : id;\n\n if (!typeManifest) {\n typeManifest = generationSnapshot\n ? loadGenerationSnapshot()\n : {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n };\n }\n\n switch (cleanId) {\n case 'smrt-consumer:routes':\n return generateFallbackRoutesModule();\n\n case 'smrt-consumer:client':\n return generateFallbackClientModule(typeManifest, { kebabRoutes });\n\n case 'smrt-consumer:mcp':\n return generateFallbackMcpModule();\n\n case 'smrt-consumer:types':\n return generateFallbackTypesModule(typeManifest);\n\n case 'smrt-consumer:manifest':\n return generateFallbackManifestModule(typeManifest);\n\n default:\n return null;\n }\n },\n };\n}\n\n/**\n * Discover SMRT packages from a consumer app's dependencies.\n *\n * Intentional split (#1579): this **consumer-plugin** path is async and\n * resolves SMRT packages from the downstream app's `package.json` dependency\n * names (`@have/`/`smrt` heuristic + `hasSmrtManifest` probe) inside the Vite\n * consumer plugin. It is deliberately separate from the build-time\n * `discoverSmrtPackages()` in `src/manifest/discover-smrt-packages.ts` — a\n * synchronous, lockfile-cached `node_modules` manifest scan used for manifest\n * generation. Different inputs, contexts, and lifecycles, not duplicated logic.\n */\nasync function discoverSmrtPackages(projectRoot: string): Promise<string[]> {\n const packages: string[] = [];\n const nodeModulesPath = path.join(projectRoot, 'node_modules');\n\n if (!fs.existsSync(nodeModulesPath)) {\n return packages;\n }\n\n try {\n // Check package.json for workspace dependencies\n const packageJsonPath = path.join(projectRoot, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.peerDependencies,\n };\n\n // Look for packages that likely contain SMRT objects\n for (const [name, version] of Object.entries(allDeps)) {\n if (\n typeof version === 'string' &&\n (name.includes('smrt') ||\n name.includes('@have/') ||\n (await hasSmrtManifest(nodeModulesPath, name)))\n ) {\n packages.push(name);\n }\n }\n }\n } catch (error) {\n console.warn('[smrt:consumer] Error discovering packages:', error);\n }\n\n return packages;\n}\n\n/**\n * Check if a package has SMRT manifest\n */\nasync function hasSmrtManifest(\n nodeModulesPath: string,\n packageName: string,\n): Promise<boolean> {\n const packagePath = path.join(nodeModulesPath, packageName);\n const manifestPath = path.join(\n packagePath,\n 'dist',\n 'manifest',\n 'static-manifest.js',\n );\n return fs.existsSync(manifestPath);\n}\n\n/**\n * Aggregate type manifests from multiple packages\n */\nasync function aggregateTypeManifests(\n packages: string[],\n projectRoot: string,\n): Promise<ConsumerManifest> {\n const aggregatedManifest: ConsumerManifest = {\n version: '1.0.0',\n timestamp: MANIFEST_TIMESTAMP,\n objects: {},\n };\n\n for (const packageName of packages) {\n try {\n const packageDir = path.join(projectRoot, 'node_modules', packageName);\n\n // Load package.json for version and export information\n const packageJsonPath = path.join(packageDir, 'package.json');\n let packageJson: ConsumerPackageJson;\n try {\n const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');\n packageJson = JSON.parse(packageJsonContent) as ConsumerPackageJson;\n } catch {\n console.warn(\n `[smrt:consumer] Could not read package.json for ${packageName}`,\n );\n continue;\n }\n\n // Try multiple manifest locations\n const manifestCandidates = [\n path.join(packageDir, 'dist', 'manifest', 'static-manifest.js'),\n path.join(packageDir, 'dist', 'manifest.json'),\n path.join(packageDir, 'manifest.json'),\n ];\n\n for (const manifestPath of manifestCandidates) {\n if (fs.existsSync(manifestPath)) {\n // Import or read the manifest\n let manifest: Partial<ConsumerManifest> | undefined;\n if (manifestPath.endsWith('.js')) {\n const manifestModule = await import(manifestPath);\n manifest = manifestModule.staticManifest || manifestModule.default;\n } else {\n const manifestContent = fs.readFileSync(manifestPath, 'utf-8');\n manifest = JSON.parse(manifestContent) as Partial<ConsumerManifest>;\n }\n\n if (manifest?.objects) {\n console.log(\n `[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`,\n );\n\n // ENHANCED: Preserve package metadata for each object\n for (const [objectName, objectDef] of Object.entries(\n manifest.objects,\n )) {\n const def = objectDef;\n\n aggregatedManifest.objects[objectName] = {\n ...def,\n // Ensure package metadata is preserved/set\n packageName:\n def.packageName || manifest.packageName || packageName,\n packageVersion:\n def.packageVersion ||\n manifest.packageVersion ||\n packageJson.version,\n // Add fallback import paths if missing\n importPath: def.importPath || determineImportPath(packageJson),\n exportName: def.exportName || def.className || objectName,\n collectionExportName:\n def.collectionExportName ||\n `${def.className || objectName}Collection`,\n };\n }\n\n break; // Use first found manifest for this package\n }\n }\n }\n } catch (error) {\n console.warn(\n `[smrt:consumer] Error loading manifest from ${packageName}:`,\n error,\n );\n }\n }\n\n return aggregatedManifest;\n}\n\n/**\n * Determine import path from package.json\n */\nfunction determineImportPath(packageJson: ConsumerPackageJson): string {\n const packageName = packageJson.name;\n\n if (!packageName) {\n throw new Error('Package name not found in package.json');\n }\n\n // Strategy 1: Check for specific exports\n if (packageJson.exports) {\n // Check for objects export\n if (packageJson.exports['./objects']) {\n return `${packageName}/objects`;\n }\n\n // Check for main export\n const mainExport = packageJson.exports['.'];\n if (mainExport) {\n // Handle conditional exports\n if (typeof mainExport === 'object' && mainExport !== null) {\n const conditional = mainExport as Record<string, unknown>;\n if (conditional.import) {\n return packageName;\n }\n if (conditional.default) {\n return packageName;\n }\n }\n return packageName;\n }\n }\n\n // Strategy 2: Check main field\n if (packageJson.main) {\n return packageName;\n }\n\n // Strategy 3: Fallback to package name\n return packageName;\n}\n\n/**\n * Save aggregated manifest to .smrt/manifest.json for CLI discovery.\n *\n * Merge-preserving: `smrtPlugin()` writes the project's own scanned objects\n * to the same file (`writeLocalManifest`, issue #963), and both writes happen\n * in parallel `buildStart` hooks — so a plain overwrite here would clobber\n * the local objects whenever this plugin's write lands last (issue #1760\n * review). Local field metadata would then silently vanish from CLI schema\n * commands and from server runtimes that seed `.smrt/manifest.json`, dropping\n * domain columns on write. This function therefore only ADDS/refreshes the\n * external-package entries it owns and preserves everything else already in\n * the file (including the top-level `packageName` the local write sets).\n */\nasync function saveAggregatedManifest(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const manifestPath = path.join(smrtDir, 'manifest.json');\n\n try {\n // Create .smrt directory if it doesn't exist\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Merge with whatever is on disk: existing entries (typically the local\n // project's objects written by smrtPlugin) are preserved; aggregated\n // external entries win for the qualified names this plugin owns.\n let merged: ConsumerManifest = manifest;\n if (fs.existsSync(manifestPath)) {\n try {\n const existing = JSON.parse(\n fs.readFileSync(manifestPath, 'utf-8'),\n ) as Partial<ConsumerManifest>;\n if (existing && typeof existing.objects === 'object') {\n merged = {\n ...existing,\n ...manifest,\n // The aggregated manifest carries no packageName; keep the local\n // project's (used as the manifest cache key at runtime).\n ...(existing.packageName\n ? { packageName: existing.packageName }\n : {}),\n objects: { ...existing.objects, ...manifest.objects },\n };\n }\n } catch {\n // Unreadable/corrupt existing file — fall back to a plain write.\n }\n }\n\n // Write manifest\n fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2), 'utf-8');\n\n console.log(\n `[smrt:consumer] Saved aggregated manifest to .smrt/manifest.json (${Object.keys(merged.objects).length} objects)`,\n );\n } catch (error) {\n console.warn('[smrt:consumer] Failed to save aggregated manifest:', error);\n }\n}\n\n/**\n * Generate registration file for CLI class loading\n *\n * Creates .smrt/register.js with static imports and registrations\n * for all external SMRT objects discovered during build.\n */\nasync function generateRegistrationFile(\n manifest: ConsumerManifest,\n projectRoot: string,\n): Promise<void> {\n const smrtDir = path.join(projectRoot, '.smrt');\n const registerPath = path.join(smrtDir, 'register.js');\n\n // Bind every imported symbol to a generated local name. Aggregated manifests\n // may contain same-named exports from different packages (and may list a\n // collection both beside its object and as its own manifest entry), so using\n // provider export names as local bindings can produce invalid duplicate\n // imports in a production consumer bundle.\n const importBindings = new Map<string, string>();\n const importsByPath = new Map<string, Map<string, string>>();\n let nextImportBinding = 0;\n const getImportBinding = (importPath: string, exportName: string): string => {\n const key = `${importPath}\\0${exportName}`;\n const existing = importBindings.get(key);\n if (existing) return existing;\n const binding = `__smrt_consumer_${nextImportBinding++}`;\n importBindings.set(key, binding);\n const specifiers =\n importsByPath.get(importPath) ?? new Map<string, string>();\n specifiers.set(exportName, binding);\n importsByPath.set(importPath, specifiers);\n return binding;\n };\n\n const registrations: string[] = [];\n const registrationManifests: Record<string, ConsumerManifest> = {};\n let importedEntryCount = 0;\n let registeredObjectCount = 0;\n\n const manifestObjects = manifest.objects;\n const manifestObjectLookup = new Map<string, ConsumerObjectDefinition>();\n for (const [key, def] of Object.entries(manifestObjects)) {\n const candidate = def;\n const lookupKeys = [\n key,\n key.includes(':') ? key.split(':').pop() : undefined,\n candidate.qualifiedName,\n candidate.className,\n candidate.exportName,\n ];\n\n for (const lookupKey of lookupKeys) {\n if (lookupKey && !manifestObjectLookup.has(lookupKey)) {\n manifestObjectLookup.set(lookupKey, candidate);\n }\n }\n }\n\n const collectionClassMemo = new WeakMap<object, boolean>();\n\n const isCollectionClass = (\n def: ConsumerObjectDefinition | undefined,\n seen = new Set<string>(),\n ): boolean => {\n if (!def || typeof def !== 'object') {\n return false;\n }\n\n const cached = collectionClassMemo.get(def);\n if (cached !== undefined) {\n return cached;\n }\n\n if (\n def?.extends === 'SmrtCollection' ||\n def?.extendsTypeArg !== undefined\n ) {\n collectionClassMemo.set(def, true);\n return true;\n }\n\n const parentName = def?.extendsQualified || def?.extends;\n if (!parentName || seen.has(parentName)) {\n collectionClassMemo.set(def, false);\n return false;\n }\n seen.add(parentName);\n\n const parentDef = manifestObjectLookup.get(parentName);\n const isCollection = parentDef ? isCollectionClass(parentDef, seen) : false;\n collectionClassMemo.set(def, isCollection);\n\n return isCollection;\n };\n\n for (const [objectName, objectDef] of Object.entries(manifestObjects)) {\n const def = objectDef;\n\n // Skip local objects (they're imported from local entry point)\n if (!def.packageName || def.packageName === manifest.packageName) {\n continue;\n }\n\n const importPath = def.importPath || def.packageName;\n const exportName = def.exportName || def.className || objectName;\n const collectionExportName = def.collectionExportName;\n const hasCollection = def.hasCollection; // Check if collection class actually exists\n const tableName = def.collection || objectName.toLowerCase();\n\n const exportBinding = getImportBinding(importPath, exportName);\n const collectionBinding =\n hasCollection && collectionExportName\n ? getImportBinding(importPath, collectionExportName)\n : undefined;\n importedEntryCount++;\n\n if (isCollectionClass(def)) {\n continue;\n }\n\n const logicalName = def.className || exportName;\n registrationManifests[objectName] = {\n ...manifest,\n packageName: def.packageName,\n packageVersion: def.packageVersion || manifest.packageVersion,\n objects: { [objectName]: def },\n };\n\n // Import evaluation triggers the provider decorator first. The explicit\n // constructor/package/key tuple then promotes that exact constructor with\n // its isolated manifest, which is stable across Rollup name deconfliction.\n registrations.push(\n `if (${exportBinding}) ObjectRegistry.register(${exportBinding}, { name: ${JSON.stringify(logicalName)}, packageName: ${JSON.stringify(def.packageName)}, _manifest: smrtRegistrationManifests[${JSON.stringify(objectName)}], _manifestKey: ${JSON.stringify(objectName)} });`,\n );\n\n // Only register collection if it exists\n if (collectionBinding) {\n registrations.push(\n `if (${collectionBinding}) ObjectRegistry.registerCollection('${tableName}', ${collectionBinding});`,\n );\n }\n\n registeredObjectCount++;\n }\n\n // Skip generation if no external entries\n if (importedEntryCount === 0) {\n console.log('[smrt:consumer] No external entries - skipping register.js');\n return;\n }\n\n const registeredObjectLabel =\n registeredObjectCount === 1 ? 'object' : 'objects';\n const sortedImports = Array.from(importsByPath.entries()).sort(\n ([left], [right]) => left.localeCompare(right),\n );\n const imports = sortedImports.map(\n ([importPath], index) =>\n `import * as __smrt_provider_${index} from '${importPath}';`,\n );\n const importDeclarations = sortedImports.flatMap(([, specifiers], index) =>\n Array.from(specifiers.entries())\n .sort(([left], [right]) => left.localeCompare(right))\n .map(\n ([exportName, binding]) =>\n `const ${binding} = getSmrtExport(__smrt_provider_${index}, ${JSON.stringify(exportName)});`,\n ),\n );\n const registrationManifestLiteral = JSON.stringify(\n JSON.stringify(registrationManifests),\n );\n\n // Generate file content\n const content = `/**\n * Auto-generated by @happyvertical/smrt-core/consumer-plugin\n * DO NOT EDIT - This file is regenerated on every build\n *\n * Registers SMRT objects from external packages for CLI discovery.\n * Generated at: ${new Date().toISOString()}\n */\n\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n${imports.join('\\n')}\n\n/**\n * @param {Record<string, unknown>} provider\n * @param {string} exportName\n * @returns {any}\n */\nconst getSmrtExport = (provider, exportName) =>\n typeof provider[exportName] === 'function' ? provider[exportName] : undefined;\n${importDeclarations.join('\\n')}\n\nconst smrtRegistrationManifests = JSON.parse(${registrationManifestLiteral});\n\n// Register all objects (executed during module evaluation)\n${registrations.join('\\n')}\n\nexport function registerAll() {\n // Objects are already registered during module evaluation\n console.log('[smrt:register] Registered ${registeredObjectCount} external ${registeredObjectLabel}');\n}\n`;\n\n // Create .smrt directory if needed\n if (!fs.existsSync(smrtDir)) {\n fs.mkdirSync(smrtDir, { recursive: true });\n }\n\n // Write registration file\n fs.writeFileSync(registerPath, content, 'utf-8');\n\n console.log(\n `[smrt:consumer] Generated .smrt/register.js with ${importedEntryCount} external entries (${registeredObjectCount} registered ${registeredObjectLabel})`,\n );\n}\n\n/**\n * Generate project-specific types\n */\nasync function generateProjectTypes(\n typeManifest: ConsumerManifest,\n typesDir: string,\n projectRoot: string,\n): Promise<void> {\n if (!typeManifest || Object.keys(typeManifest.objects).length === 0) {\n console.log(\n '[smrt:consumer] No SMRT objects found, skipping type generation',\n );\n return;\n }\n\n await generateDeclarations({\n // The aggregated manifest is a runtime SMRT manifest assembled from external\n // package manifests; it is intentionally typed loosely at the JSON boundary,\n // so narrow it to the declaration generator's strict manifest shape here.\n manifest: typeManifest as unknown as SmartObjectManifest,\n outDir: typesDir,\n projectRoot,\n includeVirtualModules: true,\n includeObjectTypes: true,\n });\n\n console.log(\n `[smrt:consumer] Generated types for ${Object.keys(typeManifest.objects).length} objects`,\n );\n}\n\n/**\n * Get type file name for virtual module\n */\nfunction getTypeFileName(virtualModule: string): string {\n const moduleMap: Record<string, string> = {\n '@smrt/routes': 'smrt-routes.d.ts',\n '@smrt/client': 'smrt-client.d.ts',\n '@smrt/mcp': 'smrt-mcp.d.ts',\n '@smrt/types': 'smrt-types.d.ts',\n '@smrt/manifest': 'smrt-manifest.d.ts',\n };\n return moduleMap[virtualModule] || 'smrt-unknown.d.ts';\n}\n\n/**\n * Fallback modules for when types aren't available\n */\nfunction generateFallbackRoutesModule(): string {\n return `\n// Fallback routes module\nexport function setupRoutes(app) {\n console.warn('[smrt:consumer] No routes available - SMRT packages may not be properly configured');\n}\nexport default setupRoutes;\n`;\n}\n\nfunction generateFallbackClientModule(\n manifest: ConsumerManifest,\n options: { kebabRoutes?: boolean } = {},\n): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `\n// Fallback client module\nexport function createClient(basePath = '/api/v1') {\n console.warn('[smrt:consumer] No API client available - SMRT packages may not be properly configured');\n return {};\n}\nexport default createClient;\n`;\n }\n\n return generateClientModule(manifest as unknown as SmartObjectManifest, {\n kebabRoutes: options.kebabRoutes,\n });\n}\n\nfunction generateFallbackMcpModule(): string {\n return `\n// Fallback MCP module\nexport const tools = [];\nexport function createMCPServer() {\n console.warn('[smrt:consumer] No MCP tools available - SMRT packages may not be properly configured');\n return { name: 'smrt-consumer', version: '1.0.0', tools: [] };\n}\nexport default createMCPServer;\n`;\n}\n\nfunction generateFallbackTypesModule(manifest: ConsumerManifest): string {\n const objects = Object.entries(manifest?.objects || {});\n if (objects.length === 0) {\n return `// No types available`;\n }\n\n // Generate basic interfaces\n const interfaces = objects.map(([_name, obj]) => {\n return `export interface ${obj.className}Data {\n id?: string;\n created_at?: string;\n updated_at?: string;\n [key: string]: any;\n}`;\n });\n\n return interfaces.join('\\n\\n');\n}\n\nfunction generateFallbackManifestModule(manifest: ConsumerManifest): string {\n return `\n// Auto-generated manifest from SMRT consumer\nexport const manifest = ${JSON.stringify(manifest, null, 2)};\nexport default manifest;\n`;\n}\n"],"mappings":"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"}
@@ -1,2 +1,3 @@
1
+ import { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot } from "./generation-snapshot.js";
1
2
  import { smrtConsumer } from "./consumer-plugin/index.js";
2
- export { smrtConsumer };
3
+ export { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot, smrtConsumer };
@@ -30,6 +30,26 @@ export type DatabaseConfig = string | {
30
30
  authToken?: string;
31
31
  [key: string]: unknown;
32
32
  } | DatabaseInterface;
33
+ /**
34
+ * Canonical declaration of the request-scoped database global.
35
+ *
36
+ * The value is installed by whichever package owns request scoping (today
37
+ * `@happyvertical/smrt-users`, via its session permission context) and is read
38
+ * by the SvelteKit runtime config that `smrt-core`'s vite plugin generates into
39
+ * consumer apps. Both sides used to `declare global` this name independently
40
+ * with different types — `DatabaseConfig` here, a package-private
41
+ * `QueryableDatabase` there — and TypeScript requires merged `var` declarations
42
+ * to be *identical*, not merely compatible. Consumers whose program contained
43
+ * both then failed to type-check (#2342).
44
+ *
45
+ * `smrt-core` owns the declaration because it is the base package every other
46
+ * SMRT package and consumer already depends on. Writers should keep assigning a
47
+ * live `DatabaseInterface`; it satisfies `DatabaseConfig` through that arm of
48
+ * the union. Never redeclare this global elsewhere.
49
+ */
50
+ declare global {
51
+ var __smrtGetRequestScopedDatabase: (() => DatabaseConfig | undefined) | undefined;
52
+ }
33
53
  /**
34
54
  * Type guard to check if a value is a DatabaseInterface instance
35
55
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAG3E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IACE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,GACD,iBAAiB,CAAC;AAEtB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,iBAAiB,CAO5B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,iBAAiB,CAAC,CAkC5B"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAG3E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IACE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,GACD,iBAAiB,CAAC;AAEtB;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,8BAA8B,EAC9B,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC,GAClC,SAAS,CAAC;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,iBAAiB,CAO5B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,iBAAiB,CAAC,CAkC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","names":[],"sources":["../src/database.ts"],"sourcesContent":["/**\n * Database configuration and resolution utilities\n *\n * This module provides a unified type for database configuration options\n * and a utility function to resolve any config format to a DatabaseInterface.\n *\n * @module\n */\n\nimport type { DatabaseInterface, SchemasOption } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\n\n/**\n * Unified type for all database configuration formats\n *\n * Supports three formats:\n * - **String**: Connection URL (e.g., 'products.db', ':memory:', 'postgres://...')\n * - **Config object**: Full configuration with type, url, and options\n * - **DatabaseInterface**: Pre-initialized database instance\n *\n * @example\n * ```typescript\n * // String shortcut\n * const config: DatabaseConfig = 'products.db';\n *\n * // Config object\n * const config: DatabaseConfig = {\n * type: 'sqlite',\n * url: 'products.db',\n * authToken: 'token'\n * };\n *\n * // Pre-initialized instance\n * const db = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * const config: DatabaseConfig = db;\n * ```\n */\nexport type DatabaseConfig =\n | string\n | {\n url?: string;\n type?: 'sqlite' | 'postgres' | 'duckdb' | 'json';\n authToken?: string;\n [key: string]: unknown;\n }\n | DatabaseInterface;\n\n/**\n * Type guard to check if a value is a DatabaseInterface instance\n *\n * @param value - Value to check\n * @returns True if value has query() and close() methods\n *\n * @example\n * ```typescript\n * if (isDatabaseInterface(config)) {\n * // config is DatabaseInterface\n * await config.query('SELECT 1');\n * }\n * ```\n */\nexport function isDatabaseInterface(\n value: unknown,\n): value is DatabaseInterface {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'query' in value &&\n typeof (value as { query: unknown }).query === 'function'\n );\n}\n\n/**\n * Options for resolving a database configuration\n */\nexport interface ResolveDatabaseOptions {\n /**\n * Cache key for connection pooling.\n * If provided, the same dbid returns the same cached connection.\n */\n dbid?: string;\n\n /**\n * Optional pre-generated schemas to pass to the database adapter.\n *\n * Intended for explicit tooling and test utilities that bootstrap schema\n * ahead of runtime. Core runtime no longer passes these automatically.\n */\n schemas?: SchemasOption;\n}\n\n/**\n * Resolve any DatabaseConfig format to a DatabaseInterface instance\n *\n * This utility function normalizes the three config formats:\n * 1. **String URL**: Passed to getDatabase() with auto-detected type\n * 2. **Config object**: Passed to getDatabase() directly\n * 3. **DatabaseInterface**: Returned as-is\n *\n * @param config - Database configuration in any supported format\n * @param options - Resolution options (dbid for caching, optional explicit schemas)\n * @returns Promise resolving to a DatabaseInterface instance\n *\n * @example\n * ```typescript\n * // String URL\n * const db = await resolveDatabase('products.db');\n *\n * // Config object\n * const db = await resolveDatabase({\n * type: 'postgres',\n * url: 'postgres://localhost/mydb'\n * });\n *\n * // Pre-initialized instance (returned as-is)\n * const existingDb = await getDatabase({ url: ':memory:' });\n * const db = await resolveDatabase(existingDb);\n * console.log(db === existingDb); // true\n * ```\n */\nexport async function resolveDatabase(\n config: DatabaseConfig,\n options: ResolveDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const { dbid, schemas } = options;\n\n // Already a DatabaseInterface instance - return as-is\n if (isDatabaseInterface(config)) {\n return config;\n }\n\n // String URL shortcut\n if (typeof config === 'string') {\n const isMemoryDb = config === ':memory:';\n return getDatabase({\n url: config,\n schemas,\n ...(isMemoryDb ? {} : { dbid: dbid ?? `smrt:${config}` }),\n });\n }\n\n // Config object\n // Only default to :memory: for SQLite (or unspecified type which defaults to SQLite)\n // Other adapters (json, postgres, duckdb) require explicit URLs\n const canUseMemory = !config.type || config.type === 'sqlite';\n const dbUrl = config.url || (canUseMemory ? ':memory:' : '');\n const isMemoryDb = dbUrl === ':memory:';\n // `config` is the loosely-typed config-object variant of `DatabaseConfig`\n // (it carries an open `[key: string]: unknown` index for adapter-specific\n // options). Cast the merged options to `getDatabase`'s own parameter type at\n // this boundary; the adapter validates the concrete shape at runtime.\n return getDatabase({\n ...config,\n url: dbUrl,\n schemas,\n ...(isMemoryDb ? {} : { dbid: dbid ?? `smrt:${dbUrl}` }),\n } as Parameters<typeof getDatabase>[0]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6DA,SAAgB,oBACd,OAC4B;CAC5B,OACE,UAAU,QACV,OAAO,UAAU,YACjB,WAAW,SACX,OAAQ,MAA6B,UAAU;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,gBACpB,QACA,UAAkC,CAAC,GACP;CAC5B,MAAM,EAAE,MAAM,YAAY;CAG1B,IAAI,oBAAoB,MAAM,GAC5B,OAAO;CAIT,IAAI,OAAO,WAAW,UAEpB,OAAO,YAAY;EACjB,KAAK;EACL;EACA,GAJiB,WAAW,aAIX,CAAC,IAAI,EAAE,MAAM,QAAQ,QAAQ,SAAS;CACzD,CAAC;CAMH,MAAM,eAAe,CAAC,OAAO,QAAQ,OAAO,SAAS;CACrD,MAAM,QAAQ,OAAO,QAAQ,eAAe,aAAa;CACzD,MAAM,aAAa,UAAU;CAK7B,OAAO,YAAY;EACjB,GAAG;EACH,KAAK;EACL;EACA,GAAI,aAAa,CAAC,IAAI,EAAE,MAAM,QAAQ,QAAQ,QAAQ;CACxD,CAAsC;AACxC"}
1
+ {"version":3,"file":"database.js","names":[],"sources":["../src/database.ts"],"sourcesContent":["/**\n * Database configuration and resolution utilities\n *\n * This module provides a unified type for database configuration options\n * and a utility function to resolve any config format to a DatabaseInterface.\n *\n * @module\n */\n\nimport type { DatabaseInterface, SchemasOption } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\n\n/**\n * Unified type for all database configuration formats\n *\n * Supports three formats:\n * - **String**: Connection URL (e.g., 'products.db', ':memory:', 'postgres://...')\n * - **Config object**: Full configuration with type, url, and options\n * - **DatabaseInterface**: Pre-initialized database instance\n *\n * @example\n * ```typescript\n * // String shortcut\n * const config: DatabaseConfig = 'products.db';\n *\n * // Config object\n * const config: DatabaseConfig = {\n * type: 'sqlite',\n * url: 'products.db',\n * authToken: 'token'\n * };\n *\n * // Pre-initialized instance\n * const db = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * const config: DatabaseConfig = db;\n * ```\n */\nexport type DatabaseConfig =\n | string\n | {\n url?: string;\n type?: 'sqlite' | 'postgres' | 'duckdb' | 'json';\n authToken?: string;\n [key: string]: unknown;\n }\n | DatabaseInterface;\n\n/**\n * Canonical declaration of the request-scoped database global.\n *\n * The value is installed by whichever package owns request scoping (today\n * `@happyvertical/smrt-users`, via its session permission context) and is read\n * by the SvelteKit runtime config that `smrt-core`'s vite plugin generates into\n * consumer apps. Both sides used to `declare global` this name independently\n * with different types — `DatabaseConfig` here, a package-private\n * `QueryableDatabase` there — and TypeScript requires merged `var` declarations\n * to be *identical*, not merely compatible. Consumers whose program contained\n * both then failed to type-check (#2342).\n *\n * `smrt-core` owns the declaration because it is the base package every other\n * SMRT package and consumer already depends on. Writers should keep assigning a\n * live `DatabaseInterface`; it satisfies `DatabaseConfig` through that arm of\n * the union. Never redeclare this global elsewhere.\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtGetRequestScopedDatabase:\n | (() => DatabaseConfig | undefined)\n | undefined;\n}\n\n/**\n * Type guard to check if a value is a DatabaseInterface instance\n *\n * @param value - Value to check\n * @returns True if value has query() and close() methods\n *\n * @example\n * ```typescript\n * if (isDatabaseInterface(config)) {\n * // config is DatabaseInterface\n * await config.query('SELECT 1');\n * }\n * ```\n */\nexport function isDatabaseInterface(\n value: unknown,\n): value is DatabaseInterface {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'query' in value &&\n typeof (value as { query: unknown }).query === 'function'\n );\n}\n\n/**\n * Options for resolving a database configuration\n */\nexport interface ResolveDatabaseOptions {\n /**\n * Cache key for connection pooling.\n * If provided, the same dbid returns the same cached connection.\n */\n dbid?: string;\n\n /**\n * Optional pre-generated schemas to pass to the database adapter.\n *\n * Intended for explicit tooling and test utilities that bootstrap schema\n * ahead of runtime. Core runtime no longer passes these automatically.\n */\n schemas?: SchemasOption;\n}\n\n/**\n * Resolve any DatabaseConfig format to a DatabaseInterface instance\n *\n * This utility function normalizes the three config formats:\n * 1. **String URL**: Passed to getDatabase() with auto-detected type\n * 2. **Config object**: Passed to getDatabase() directly\n * 3. **DatabaseInterface**: Returned as-is\n *\n * @param config - Database configuration in any supported format\n * @param options - Resolution options (dbid for caching, optional explicit schemas)\n * @returns Promise resolving to a DatabaseInterface instance\n *\n * @example\n * ```typescript\n * // String URL\n * const db = await resolveDatabase('products.db');\n *\n * // Config object\n * const db = await resolveDatabase({\n * type: 'postgres',\n * url: 'postgres://localhost/mydb'\n * });\n *\n * // Pre-initialized instance (returned as-is)\n * const existingDb = await getDatabase({ url: ':memory:' });\n * const db = await resolveDatabase(existingDb);\n * console.log(db === existingDb); // true\n * ```\n */\nexport async function resolveDatabase(\n config: DatabaseConfig,\n options: ResolveDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const { dbid, schemas } = options;\n\n // Already a DatabaseInterface instance - return as-is\n if (isDatabaseInterface(config)) {\n return config;\n }\n\n // String URL shortcut\n if (typeof config === 'string') {\n const isMemoryDb = config === ':memory:';\n return getDatabase({\n url: config,\n schemas,\n ...(isMemoryDb ? {} : { dbid: dbid ?? `smrt:${config}` }),\n });\n }\n\n // Config object\n // Only default to :memory: for SQLite (or unspecified type which defaults to SQLite)\n // Other adapters (json, postgres, duckdb) require explicit URLs\n const canUseMemory = !config.type || config.type === 'sqlite';\n const dbUrl = config.url || (canUseMemory ? ':memory:' : '');\n const isMemoryDb = dbUrl === ':memory:';\n // `config` is the loosely-typed config-object variant of `DatabaseConfig`\n // (it carries an open `[key: string]: unknown` index for adapter-specific\n // options). Cast the merged options to `getDatabase`'s own parameter type at\n // this boundary; the adapter validates the concrete shape at runtime.\n return getDatabase({\n ...config,\n url: dbUrl,\n schemas,\n ...(isMemoryDb ? {} : { dbid: dbid ?? `smrt:${dbUrl}` }),\n } as Parameters<typeof getDatabase>[0]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqFA,SAAgB,oBACd,OAC4B;CAC5B,OACE,UAAU,QACV,OAAO,UAAU,YACjB,WAAW,SACX,OAAQ,MAA6B,UAAU;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,gBACpB,QACA,UAAkC,CAAC,GACP;CAC5B,MAAM,EAAE,MAAM,YAAY;CAG1B,IAAI,oBAAoB,MAAM,GAC5B,OAAO;CAIT,IAAI,OAAO,WAAW,UAEpB,OAAO,YAAY;EACjB,KAAK;EACL;EACA,GAJiB,WAAW,aAIX,CAAC,IAAI,EAAE,MAAM,QAAQ,QAAQ,SAAS;CACzD,CAAC;CAMH,MAAM,eAAe,CAAC,OAAO,QAAQ,OAAO,SAAS;CACrD,MAAM,QAAQ,OAAO,QAAQ,eAAe,aAAa;CACzD,MAAM,aAAa,UAAU;CAK7B,OAAO,YAAY;EACjB,GAAG;EACH,KAAK;EACL;EACA,GAAI,aAAa,CAAC,IAAI,EAAE,MAAM,QAAQ,QAAQ,QAAQ;CACxD,CAAsC;AACxC"}
@@ -0,0 +1,39 @@
1
+ export type SmrtGenerationSnapshotView = 'all' | 'project' | 'dependencies';
2
+ /**
3
+ * Immutable manifest artifact consumed by SMRT's Vite plugins.
4
+ *
5
+ * `provenance` is caller-defined source identity (normally the exact git tree
6
+ * or commit). Consumers must provide the identity they expect; the loader does
7
+ * not trust the artifact to identify itself.
8
+ */
9
+ export interface SmrtGenerationSnapshotArtifact<TManifest> {
10
+ schemaVersion: 1;
11
+ provenance: string;
12
+ pathMode: 'source-root-relative';
13
+ sourceDigests: Record<string, string>;
14
+ manifest: TManifest;
15
+ }
16
+ /** Fail-closed input used by `smrtPlugin()` and `smrtConsumer()`. */
17
+ export interface SmrtGenerationSnapshotOptions {
18
+ /** Artifact path, absolute or relative to the plugin's project root. */
19
+ path: string;
20
+ /** SHA-256 of the exact artifact bytes, formatted as `sha256:<hex>`. */
21
+ sha256: string;
22
+ /** Expected source identity, matched exactly against artifact provenance. */
23
+ provenance: string;
24
+ /** Current checkout root corresponding to paths normalized by the producer. */
25
+ sourceRoot: string;
26
+ }
27
+ export interface SerializeSmrtGenerationSnapshotOptions {
28
+ /** Checkout/workspace root used to make local source paths portable. */
29
+ sourceRoot: string;
30
+ }
31
+ /** Serialize one deterministic, portable generation snapshot. */
32
+ export declare function serializeSmrtGenerationSnapshot<TManifest>(manifest: TManifest, provenance: string, options: SerializeSmrtGenerationSnapshotOptions): string;
33
+ /** Return the digest format accepted by `SmrtGenerationSnapshotOptions`. */
34
+ export declare function sha256SmrtGenerationSnapshot(contents: string | Uint8Array): string;
35
+ /**
36
+ * Load and verify a generation snapshot without mutating the artifact or project.
37
+ */
38
+ export declare function loadVerifiedSmrtGenerationSnapshot<TManifest>(options: SmrtGenerationSnapshotOptions, projectRoot: string, view?: SmrtGenerationSnapshotView): TManifest;
39
+ //# sourceMappingURL=generation-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generation-snapshot.d.ts","sourceRoot":"","sources":["../src/generation-snapshot.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,0BAA0B,GAAG,KAAK,GAAG,SAAS,GAAG,cAAc,CAAC;AAE5E;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B,CAAC,SAAS;IACvD,aAAa,EAAE,CAAC,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,qEAAqE;AACrE,MAAM,WAAW,6BAA6B;IAC5C,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sCAAsC;IACrD,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;CACpB;AAgMD,iEAAiE;AACjE,wBAAgB,+BAA+B,CAAC,SAAS,EACvD,QAAQ,EAAE,SAAS,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sCAAsC,GAC9C,MAAM,CAmCR;AAED,4EAA4E;AAC5E,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,GAAG,UAAU,GAC5B,MAAM,CAER;AAoBD;;GAEG;AACH,wBAAgB,kCAAkC,CAAC,SAAS,EAC1D,OAAO,EAAE,6BAA6B,EACtC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,0BAAkC,GACvC,SAAS,CAmFX"}
@@ -0,0 +1,148 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
+ //#region src/generation-snapshot.ts
5
+ var SOURCE_ROOT_PREFIX = "@smrt/source-root/";
6
+ function isInsideRoot(root, filePath) {
7
+ const relativePath = relative(root, filePath);
8
+ return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
9
+ }
10
+ function normalizeManifestPaths(manifest, sourceRoot) {
11
+ const clone = structuredClone(manifest);
12
+ if (!isManifest(clone)) return {
13
+ manifest: clone,
14
+ sourceDigests: {}
15
+ };
16
+ const packageName = clone.packageName;
17
+ const sourceDigests = /* @__PURE__ */ new Map();
18
+ for (const definition of Object.values(clone.objects)) {
19
+ if (!definition || typeof definition !== "object") continue;
20
+ const candidate = definition;
21
+ if (typeof candidate.filePath !== "string") continue;
22
+ const isProjectDefinition = candidate.packageName === void 0 || candidate.packageName === packageName;
23
+ if (!isProjectDefinition && !isAbsolute(candidate.filePath)) continue;
24
+ const sourcePath = isAbsolute(candidate.filePath) ? candidate.filePath : resolve(sourceRoot, candidate.filePath);
25
+ if (!isInsideRoot(sourceRoot, sourcePath)) {
26
+ if (isProjectDefinition) throw new Error(`[smrt] Generation snapshot project source path is outside sourceRoot: ${candidate.filePath}`);
27
+ continue;
28
+ }
29
+ const relativePath = relative(sourceRoot, sourcePath).replace(/\\/g, "/");
30
+ sourceDigests.set(relativePath, sha256SmrtGenerationSnapshot(readFileSync(sourcePath)));
31
+ candidate.filePath = `${SOURCE_ROOT_PREFIX}${relativePath}`;
32
+ }
33
+ return {
34
+ manifest: clone,
35
+ sourceDigests: Object.fromEntries([...sourceDigests.entries()].sort(([left], [right]) => left.localeCompare(right)))
36
+ };
37
+ }
38
+ function hydrateManifestPaths(manifest, sourceRoot, sourceDigests) {
39
+ if (!isAbsolute(sourceRoot)) throw new Error("[smrt] Generation snapshot sourceRoot must be absolute");
40
+ const resolvedSourceRoot = resolve(sourceRoot);
41
+ if (!existsSync(resolvedSourceRoot) || !statSync(resolvedSourceRoot).isDirectory()) throw new Error("[smrt] Generation snapshot sourceRoot must be an existing directory");
42
+ const packageName = manifest.packageName;
43
+ const consumedSourceDigests = /* @__PURE__ */ new Set();
44
+ for (const [relativePath, digest] of Object.entries(sourceDigests)) {
45
+ const hydratedPath = resolve(resolvedSourceRoot, relativePath);
46
+ if (!relativePath || isAbsolute(relativePath) || !isInsideRoot(resolvedSourceRoot, hydratedPath)) throw new Error(`[smrt] Generation snapshot contains an invalid source digest path: ${relativePath}`);
47
+ if (!/^sha256:[a-f0-9]{64}$/.test(digest)) throw new Error(`[smrt] Generation snapshot contains an invalid source digest for: ${relativePath}`);
48
+ }
49
+ for (const definition of Object.values(manifest.objects)) {
50
+ if (!definition || typeof definition !== "object") continue;
51
+ const candidate = definition;
52
+ if (typeof candidate.filePath !== "string") continue;
53
+ if (!candidate.filePath.startsWith(SOURCE_ROOT_PREFIX)) {
54
+ if (candidate.packageName === void 0 || candidate.packageName === packageName) throw new Error(`[smrt] Generation snapshot contains a non-portable project source path: ${candidate.filePath}`);
55
+ continue;
56
+ }
57
+ const relativePath = candidate.filePath.slice(18);
58
+ const hydratedPath = resolve(resolvedSourceRoot, relativePath);
59
+ if (!relativePath || !isInsideRoot(resolvedSourceRoot, hydratedPath)) throw new Error(`[smrt] Generation snapshot contains an invalid portable source path: ${candidate.filePath}`);
60
+ if (!existsSync(hydratedPath)) throw new Error(`[smrt] Generation snapshot source path is missing under the current sourceRoot: ${hydratedPath}`);
61
+ const expectedSourceDigest = Object.hasOwn(sourceDigests, relativePath) ? sourceDigests[relativePath] : void 0;
62
+ if (!expectedSourceDigest) throw new Error(`[smrt] Generation snapshot has no source digest for: ${relativePath}`);
63
+ const actualSourceDigest = sha256SmrtGenerationSnapshot(readFileSync(hydratedPath));
64
+ if (actualSourceDigest !== expectedSourceDigest) throw new Error(`[smrt] Generation snapshot source digest mismatch for ${hydratedPath}: expected ${expectedSourceDigest}, received ${actualSourceDigest}`);
65
+ consumedSourceDigests.add(relativePath);
66
+ candidate.filePath = hydratedPath;
67
+ }
68
+ const unreferencedSourceDigests = Object.keys(sourceDigests).filter((relativePath) => !consumedSourceDigests.has(relativePath));
69
+ if (unreferencedSourceDigests.length > 0) throw new Error(`[smrt] Generation snapshot contains unreferenced source digest(s): ${unreferencedSourceDigests.join(", ")}`);
70
+ return manifest;
71
+ }
72
+ function selectManifestView(manifest, view) {
73
+ if (![
74
+ "all",
75
+ "project",
76
+ "dependencies"
77
+ ].includes(view)) throw new Error(`[smrt] Unsupported generation snapshot view: ${view}`);
78
+ if (view === "all") return manifest;
79
+ const typed = manifest;
80
+ if (!typed.packageName) throw new Error(`[smrt] Generation snapshot ${view} view requires a top-level packageName`);
81
+ const objects = Object.fromEntries(Object.entries(typed.objects).filter(([, definition]) => {
82
+ const owner = definition.packageName;
83
+ const isProject = owner === void 0 || owner === typed.packageName;
84
+ return view === "project" ? isProject : !isProject;
85
+ }));
86
+ return {
87
+ ...typed,
88
+ objects
89
+ };
90
+ }
91
+ /** Serialize one deterministic, portable generation snapshot. */
92
+ function serializeSmrtGenerationSnapshot(manifest, provenance, options) {
93
+ if (!provenance.trim()) throw new Error("[smrt] Generation snapshot provenance must not be empty");
94
+ if (!isAbsolute(options.sourceRoot)) throw new Error("[smrt] Generation snapshot sourceRoot must be an absolute path");
95
+ if (!existsSync(options.sourceRoot) || !statSync(options.sourceRoot).isDirectory()) throw new Error("[smrt] Generation snapshot sourceRoot must be an existing directory");
96
+ if (!isManifest(manifest)) throw new Error("[smrt] Generation snapshot requires a valid manifest");
97
+ const { manifest: portableManifest, sourceDigests } = normalizeManifestPaths(manifest, resolve(options.sourceRoot));
98
+ return `${JSON.stringify({
99
+ schemaVersion: 1,
100
+ provenance,
101
+ pathMode: "source-root-relative",
102
+ sourceDigests,
103
+ manifest: portableManifest
104
+ }, null, 2)}\n`;
105
+ }
106
+ /** Return the digest format accepted by `SmrtGenerationSnapshotOptions`. */
107
+ function sha256SmrtGenerationSnapshot(contents) {
108
+ return `sha256:${createHash("sha256").update(contents).digest("hex")}`;
109
+ }
110
+ function isManifest(value) {
111
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
112
+ const candidate = value;
113
+ return typeof candidate.version === "string" && typeof candidate.timestamp === "number" && Boolean(candidate.objects && typeof candidate.objects === "object" && !Array.isArray(candidate.objects));
114
+ }
115
+ /**
116
+ * Load and verify a generation snapshot without mutating the artifact or project.
117
+ */
118
+ function loadVerifiedSmrtGenerationSnapshot(options, projectRoot, view = "all") {
119
+ if (!/^sha256:[a-f0-9]{64}$/.test(options.sha256)) throw new Error("[smrt] Generation snapshot sha256 must use sha256:<64 lowercase hex characters>");
120
+ if (!options.provenance.trim()) throw new Error("[smrt] Expected generation snapshot provenance is required");
121
+ const artifactPath = isAbsolute(options.path) ? options.path : resolve(projectRoot, options.path);
122
+ let contents;
123
+ try {
124
+ contents = readFileSync(artifactPath);
125
+ } catch (error) {
126
+ throw new Error(`[smrt] Unable to read generation snapshot at ${artifactPath}`, { cause: error });
127
+ }
128
+ const actualDigest = sha256SmrtGenerationSnapshot(contents);
129
+ if (actualDigest !== options.sha256) throw new Error(`[smrt] Generation snapshot digest mismatch at ${artifactPath}: expected ${options.sha256}, received ${actualDigest}`);
130
+ let artifact;
131
+ try {
132
+ artifact = JSON.parse(contents.toString("utf8"));
133
+ } catch (error) {
134
+ throw new Error(`[smrt] Generation snapshot at ${artifactPath} is not valid JSON`, { cause: error });
135
+ }
136
+ if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) throw new Error(`[smrt] Invalid generation snapshot at ${artifactPath}`);
137
+ const envelope = artifact;
138
+ if (envelope.schemaVersion !== 1) throw new Error(`[smrt] Unsupported generation snapshot schema at ${artifactPath}`);
139
+ if (envelope.provenance !== options.provenance) throw new Error(`[smrt] Generation snapshot provenance mismatch at ${artifactPath}`);
140
+ if (envelope.pathMode !== "source-root-relative") throw new Error(`[smrt] Unsupported generation snapshot path mode at ${artifactPath}`);
141
+ if (!envelope.sourceDigests || typeof envelope.sourceDigests !== "object" || Array.isArray(envelope.sourceDigests)) throw new Error(`[smrt] Generation snapshot at ${artifactPath} does not contain source digests`);
142
+ if (!isManifest(envelope.manifest)) throw new Error(`[smrt] Generation snapshot at ${artifactPath} does not contain a valid manifest`);
143
+ return selectManifestView(hydrateManifestPaths(envelope.manifest, options.sourceRoot, envelope.sourceDigests), view);
144
+ }
145
+ //#endregion
146
+ export { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot };
147
+
148
+ //# sourceMappingURL=generation-snapshot.js.map