@happyvertical/smrt-core 0.57.16 → 0.57.18

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/README.md CHANGED
@@ -500,6 +500,20 @@ CLI entrypoints should ship two things together:
500
500
  2. The `manifest.json` exports for any installed external `@happyvertical/smrt-*`
501
501
  packages the runtime needs to hydrate at query time.
502
502
 
503
+ For an SSR build, `smrtConsumer()` leaves its consumed providers and their
504
+ transitive server-side `@happyvertical/smrt-*` runtime dependencies external.
505
+ Node therefore resolves native-backed provider dependencies from their
506
+ installed pnpm package context, while core and tenancy retain one server-side
507
+ registry and async context. Mixed packages are split by export: server entries
508
+ stay external, while Svelte and CSS entries remain in Vite's compiler graph.
509
+ The generated consumer registration entrypoint loads transitive-only object
510
+ modules through the configured provider's ESM resolution context. This avoids
511
+ unresolvable app-root imports while preserving the exact model and collection
512
+ constructors used by the provider. The Vite preset retains its narrower
513
+ app-runtime split so SvelteKit control-flow classes keep app identity. This
514
+ applies only to SSR; browser and Svelte component builds retain their normal
515
+ bundled resolution.
516
+
503
517
  `ObjectRegistry.ensureManifestLoaded()` can now auto-load installed external
504
518
  classes by either simple name (`'EventType'`) or qualified name
505
519
  (`'@happyvertical/smrt-events:EventType'`). `SmrtCollection.list()` and
@@ -131,6 +131,9 @@ recorded `packageName` — a plain overwrite in either direction silently drops
131
131
  the other's objects, and the last pass of a multi-environment build is not
132
132
  reliably the aggregating one (#1760, #2925). `dist/manifest.json` stays
133
133
  local-only: a published package must not carry its dependencies' objects.
134
+ Core's canonical generator excludes temporary `__test-*` source roots as well
135
+ as test files and `__tests__`; prepack verifies fresh and reused manifests
136
+ contain no test-artifact marker or fixture source provenance (#4190).
134
137
 
135
138
  The schema-version-1 object projection is additive and high-signal: it retains
136
139
  normalized tenant mode/field, explicit `cti`/`sti` strategy, conflict columns,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAa,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAYnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uCAAuC,CAAC;AAqB/C,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;AAqDnC;;;;GAIG;AACH,MAAM,WAAW,4BACf,SAAQ,OAAO,CACb,IAAI,CACF,gBAAgB,EACd,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,aAAa,GACb,gBAAgB,CACnB,CACF;IACD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,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,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IACnD;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,4BAA4B,CAAC;IACnD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAmXD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAoYtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAa,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAYnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uCAAuC,CAAC;AAqB/C,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;AAsGnC;;;;GAIG;AACH,MAAM,WAAW,4BACf,SAAQ,OAAO,CACb,IAAI,CACF,gBAAgB,EACd,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,aAAa,GACb,gBAAgB,CACnB,CACF;IACD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,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,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IACnD;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,4BAA4B,CAAC;IACnD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAmXD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CA4lBtE"}
@@ -18,6 +18,24 @@ import * as fs$1 from "node:fs";
18
18
  * Vite plugin for consuming SMRT packages
19
19
  * Solves virtual module resolution in downstream projects
20
20
  */
21
+ var AmbiguousProviderGraphError = class extends Error {};
22
+ function packageNameFromImportPath(importPath) {
23
+ const parts = importPath.split("/");
24
+ return importPath.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0] ?? importPath;
25
+ }
26
+ function findInstalledPackageJson(packageName, searchFrom) {
27
+ let currentDir = fs$1.existsSync(searchFrom) ? fs$1.realpathSync(searchFrom) : path$1.resolve(searchFrom);
28
+ while (true) {
29
+ const candidate = path$1.join(currentDir, "node_modules", packageName, "package.json");
30
+ if (fs$1.existsSync(candidate)) return fs$1.realpathSync(candidate);
31
+ const parentDir = path$1.dirname(currentDir);
32
+ if (parentDir === currentDir) return void 0;
33
+ currentDir = parentDir;
34
+ }
35
+ }
36
+ function sameInstalledPackage(left, right) {
37
+ return Boolean(left && right && fs$1.realpathSync(left) === fs$1.realpathSync(right));
38
+ }
21
39
  var VIRTUAL_MODULES = {
22
40
  "@smrt/routes": "smrt-consumer:routes",
23
41
  "@smrt/client": "smrt-consumer:client",
@@ -192,6 +210,8 @@ function smrtConsumer(options = {}) {
192
210
  let typesGenerated = false;
193
211
  let producerApi;
194
212
  let routeLifecycleConfig;
213
+ let isBuild = false;
214
+ let selectiveSsrPackages = /* @__PURE__ */ new Map();
195
215
  function loadGenerationSnapshot() {
196
216
  if (!generationSnapshot) throw new Error("[smrt:consumer] Generation snapshot is not configured");
197
217
  return withHostedStiCollections(loadVerifiedSmrtGenerationSnapshot(generationSnapshot, projectRoot, "dependencies"));
@@ -222,6 +242,101 @@ function smrtConsumer(options = {}) {
222
242
  explicit: packages.length > 0
223
243
  };
224
244
  }
245
+ /**
246
+ * Keep consumed providers and their SMRT runtime graph in Node's installed
247
+ * package context during SSR. Native-backed dependencies (such as sharp)
248
+ * resolve relative to that context, and keeping core/tenancy alongside the
249
+ * provider prevents the server bundle from creating a second registry or
250
+ * AsyncLocalStorage instance.
251
+ */
252
+ function resolveConsumerSsrExternalPackages(providerPackages) {
253
+ const needsViteCompilation = (value) => {
254
+ if (typeof value === "string") return value.endsWith(".svelte") || value.endsWith(".css");
255
+ if (!value || typeof value !== "object") return false;
256
+ return Object.entries(value).some(([condition, target]) => condition === "svelte" || needsViteCompilation(target));
257
+ };
258
+ const external = /* @__PURE__ */ new Set();
259
+ const selective = /* @__PURE__ */ new Map();
260
+ const visited = /* @__PURE__ */ new Set();
261
+ const pending = providerPackages.map((packageName) => ({
262
+ packageName,
263
+ searchFrom: projectRoot
264
+ }));
265
+ while (pending.length > 0) {
266
+ const candidate = pending.pop();
267
+ if (!candidate) continue;
268
+ const { packageName, searchFrom } = candidate;
269
+ const packageJsonPath = findInstalledPackageJson(packageName, searchFrom);
270
+ const visitKey = packageJsonPath ?? `${searchFrom}\0${packageName}`;
271
+ if (visited.has(visitKey)) continue;
272
+ visited.add(visitKey);
273
+ let packageJson;
274
+ if (packageJsonPath) try {
275
+ packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf8"));
276
+ } catch {}
277
+ const needsSvelteCompilation = typeof packageJson?.svelte === "string" || needsViteCompilation(packageJson?.exports);
278
+ const isAppResolvable = sameInstalledPackage(packageJsonPath, findInstalledPackageJson(packageName, projectRoot));
279
+ if (packageName !== "@happyvertical/smrt-app-runtime" && isAppResolvable) if (needsSvelteCompilation && packageJson) selective.set(packageName, {
280
+ packageJson,
281
+ packageRoot: path$1.dirname(packageJsonPath)
282
+ });
283
+ else external.add(packageName);
284
+ if (!packageJsonPath || !packageJson) continue;
285
+ for (const dependencies of [
286
+ packageJson.dependencies,
287
+ packageJson.optionalDependencies,
288
+ packageJson.peerDependencies
289
+ ]) for (const dependency of Object.keys(dependencies ?? {})) if (dependency.startsWith("@happyvertical/smrt-")) pending.push({
290
+ packageName: dependency,
291
+ searchFrom: path$1.dirname(packageJsonPath)
292
+ });
293
+ }
294
+ return {
295
+ external: [...external].sort(),
296
+ selective
297
+ };
298
+ }
299
+ function matchingExportTarget(exports, exportKey) {
300
+ if (!exports) return void 0;
301
+ if (typeof exports === "string") return exportKey === "." ? { target: exports } : void 0;
302
+ if (typeof exports !== "object") return void 0;
303
+ const exportMap = exports;
304
+ if (exportKey in exportMap) return { target: exportMap[exportKey] };
305
+ if (exportKey === "." && !Object.keys(exportMap).some((key) => key.startsWith("."))) return { target: exportMap };
306
+ for (const [pattern, target] of Object.entries(exportMap)) {
307
+ const wildcardIndex = pattern.indexOf("*");
308
+ if (wildcardIndex < 0) continue;
309
+ const prefix = pattern.slice(0, wildcardIndex);
310
+ const suffix = pattern.slice(wildcardIndex + 1);
311
+ if (exportKey.startsWith(prefix) && exportKey.endsWith(suffix)) return {
312
+ target,
313
+ wildcard: exportKey.slice(prefix.length, exportKey.length - suffix.length)
314
+ };
315
+ }
316
+ }
317
+ function svelteConditionTargets(value) {
318
+ if (!value || typeof value !== "object") return [];
319
+ return Object.entries(value).flatMap(([condition, target]) => {
320
+ if (condition === "svelte") {
321
+ if (typeof target === "string") return [target];
322
+ return svelteConditionTargets(target);
323
+ }
324
+ return svelteConditionTargets(target);
325
+ });
326
+ }
327
+ function resolvesToSvelteCondition(packageName, packageJson, packageRoot, id, resolvedId) {
328
+ if (!resolvedId) return false;
329
+ const exportKey = id === packageName ? "." : `./${id.slice(packageName.length + 1)}`;
330
+ const matched = matchingExportTarget(packageJson.exports, exportKey);
331
+ const targets = matched ? svelteConditionTargets(matched.target) : [];
332
+ if (exportKey === "." && typeof packageJson.svelte === "string") targets.push(packageJson.svelte);
333
+ const normalizedResolved = resolvedId.split("?")[0];
334
+ const canonical = (candidate) => fs$1.existsSync(candidate) ? fs$1.realpathSync(candidate) : path$1.resolve(candidate);
335
+ return targets.some((target) => {
336
+ const substituted = matched?.wildcard ? target.replaceAll("*", matched.wildcard) : target;
337
+ return canonical(path$1.resolve(packageRoot, substituted)) === canonical(normalizedResolved);
338
+ });
339
+ }
225
340
  async function generateConfigTypes(manifest) {
226
341
  if (!generateTypes || typesGenerated) return;
227
342
  if (manifest) typeManifest = manifest;
@@ -241,11 +356,14 @@ function smrtConsumer(options = {}) {
241
356
  async handler(userConfig, env) {
242
357
  const routeLifecycle = env ?? userConfig;
243
358
  routeLifecycleConfig = routeLifecycle;
359
+ const consumerPackages = await resolveConsumerPackages();
360
+ isBuild = env?.command === "build";
361
+ const ssrResolution = resolveConsumerSsrExternalPackages(consumerPackages.names);
362
+ selectiveSsrPackages = ssrResolution.selective;
244
363
  const previousConsumerRouteRoots = loadConsumerSvelteKitRouteRoots(projectRoot);
245
364
  if (consumerSvelteKit) {
246
365
  await assertConsumerSvelteKitFormerRouteRootsAreSafe(userConfig, projectRoot, previousConsumerRouteRoots, env);
247
- const routePackages = await resolveConsumerPackages();
248
- const routeManifest = generationSnapshot ? loadGenerationSnapshot() : await aggregateHosted(routePackages.names, projectRoot, { explicit: routePackages.explicit });
366
+ const routeManifest = generationSnapshot ? loadGenerationSnapshot() : await aggregateHosted(consumerPackages.names, projectRoot, { explicit: consumerPackages.explicit });
249
367
  const hostedManifest = selectConsumerRouteManifest(routeManifest, consumerSvelteKit);
250
368
  const routesDir = canonicalConsumerRouteRoot(projectRoot, consumerSvelteKit.routesDir ?? "src/routes/api");
251
369
  const reservedRoutePaths = await activeProducerKnowledgeRoutePaths(userConfig, projectRoot, env);
@@ -288,7 +406,13 @@ function smrtConsumer(options = {}) {
288
406
  if (options.svelteKit && typeManifest) await generateRegistrationFile(typeManifest, projectRoot);
289
407
  if (previousConsumerRouteRoots.length > 0) await reconcileConsumerSvelteKitRouteRoots(routeLifecycle, userConfig, projectRoot, previousConsumerRouteRoots, () => removeConsumerSvelteKitRouteRoots(projectRoot), env);
290
408
  }
291
- return { build: { rollupOptions: { external: [/\.node$/] } } };
409
+ return {
410
+ ssr: {
411
+ external: ssrResolution.external,
412
+ noExternal: [...selectiveSsrPackages.keys()].sort()
413
+ },
414
+ build: { rollupOptions: { external: [/\.node$/] } }
415
+ };
292
416
  }
293
417
  },
294
418
  configResolved(resolvedConfig) {
@@ -328,7 +452,21 @@ function smrtConsumer(options = {}) {
328
452
  };
329
453
  }
330
454
  },
331
- resolveId(id, _importer) {
455
+ async resolveId(id, importer, resolveOptions) {
456
+ if (isBuild && resolveOptions.ssr) for (const [packageName, selectivePackage] of selectiveSsrPackages) {
457
+ if (id !== packageName && !id.startsWith(`${packageName}/`)) continue;
458
+ const resolved = await this.resolve(id, importer, {
459
+ ...resolveOptions,
460
+ skipSelf: true
461
+ });
462
+ const compile = resolved?.id.endsWith(".svelte") || resolved?.id.endsWith(".css");
463
+ const compileSvelteBarrel = resolvesToSvelteCondition(packageName, selectivePackage.packageJson, selectivePackage.packageRoot, id, resolved?.id);
464
+ if (!compile && !compileSvelteBarrel) return {
465
+ id,
466
+ external: true
467
+ };
468
+ break;
469
+ }
332
470
  if (id in VIRTUAL_MODULES) {
333
471
  if (id !== "@smrt/types") return `\0${VIRTUAL_MODULES[id]}`;
334
472
  const typeFileName = getTypeFileName(id);
@@ -467,11 +605,17 @@ async function aggregateTypeManifests(packages, projectRoot, options = {}) {
467
605
  objects: {}
468
606
  };
469
607
  const unresolvedPackages = [];
608
+ const objectSources = /* @__PURE__ */ new Map();
470
609
  for (const packageName of packages) {
471
610
  let loadedManifest = false;
472
611
  try {
473
- const packageDir = path$1.join(projectRoot, "node_modules", packageName);
474
- const packageJsonPath = path$1.join(packageDir, "package.json");
612
+ const packageJsonPath = findInstalledPackageJson(packageName, projectRoot);
613
+ if (!packageJsonPath) {
614
+ console.warn(`[smrt:consumer] Could not read package.json for ${packageName}`);
615
+ unresolvedPackages.push(packageName);
616
+ continue;
617
+ }
618
+ const packageDir = path$1.dirname(packageJsonPath);
475
619
  let packageJson;
476
620
  try {
477
621
  const packageJsonContent = fs$1.readFileSync(packageJsonPath, "utf-8");
@@ -500,11 +644,20 @@ async function aggregateTypeManifests(packages, projectRoot, options = {}) {
500
644
  console.log(`[smrt:consumer] Loaded manifest from ${packageName} (${Object.keys(manifest.objects).length} objects)`);
501
645
  for (const [objectName, objectDef] of Object.entries(manifest.objects)) {
502
646
  const def = objectDef;
647
+ const importPath = def.importPath || determineImportPath(packageJson);
648
+ const importPackageName = importPath.startsWith("@") ? importPath.split("/").slice(0, 2).join("/") : importPath.split("/")[0];
649
+ const resolvedSourcePackageJson = (importPackageName === packageName ? packageJsonPath : void 0) ?? findInstalledPackageJson(importPackageName, packageDir);
650
+ const sourceIdentity = resolvedSourcePackageJson ? fs$1.realpathSync(resolvedSourcePackageJson) : `${packageName}\0${importPackageName}`;
651
+ const priorSource = objectSources.get(objectName);
652
+ if (priorSource && priorSource !== sourceIdentity) throw new AmbiguousProviderGraphError(`[smrt:consumer] ${objectName} is contributed by multiple installed provider graphs; refusing ambiguous constructor identity`);
653
+ if (priorSource) continue;
654
+ objectSources.set(objectName, sourceIdentity);
503
655
  aggregatedManifest.objects[objectName] = {
504
656
  ...def,
505
657
  packageName: def.packageName || manifest.packageName || packageName,
506
658
  packageVersion: def.packageVersion || manifest.packageVersion || packageJson.version,
507
- importPath: def.importPath || determineImportPath(packageJson),
659
+ importPath,
660
+ providerPackageName: packageName,
508
661
  exportName: def.exportName || def.className || objectName,
509
662
  collectionExportName: def.collectionExportName || `${def.className || objectName}Collection`
510
663
  };
@@ -514,6 +667,7 @@ async function aggregateTypeManifests(packages, projectRoot, options = {}) {
514
667
  }
515
668
  }
516
669
  } catch (error) {
670
+ if (error instanceof AmbiguousProviderGraphError) throw error;
517
671
  console.warn(`[smrt:consumer] Error loading manifest from ${packageName}:`, error);
518
672
  }
519
673
  if (!loadedManifest) unresolvedPackages.push(packageName);
@@ -619,6 +773,9 @@ async function generateRegistrationFile(manifest, projectRoot) {
619
773
  const registerPath = path$1.join(smrtDir, "register.js");
620
774
  const importBindings = /* @__PURE__ */ new Map();
621
775
  const importsByPath = /* @__PURE__ */ new Map();
776
+ const transitiveNamespaces = /* @__PURE__ */ new Map();
777
+ const transitiveBindings = /* @__PURE__ */ new Map();
778
+ const transitiveCollectionChecks = [];
622
779
  let nextImportBinding = 0;
623
780
  const getImportBinding = (importPath, exportName) => {
624
781
  const key = `${importPath}\0${exportName}`;
@@ -631,6 +788,37 @@ async function generateRegistrationFile(manifest, projectRoot) {
631
788
  importsByPath.set(importPath, specifiers);
632
789
  return binding;
633
790
  };
791
+ const getTransitiveBinding = (importPath, providerPackageName, exportName) => {
792
+ const namespaceKey = `${providerPackageName}\0${importPath}`;
793
+ let namespace = transitiveNamespaces.get(namespaceKey);
794
+ if (!namespace) {
795
+ namespace = {
796
+ binding: `__smrt_transitive_provider_${transitiveNamespaces.size}`,
797
+ providerPackageName,
798
+ importPath
799
+ };
800
+ transitiveNamespaces.set(namespaceKey, namespace);
801
+ }
802
+ const key = `${namespaceKey}\0${exportName}`;
803
+ const existing = transitiveBindings.get(key);
804
+ if (existing) return existing.binding;
805
+ const binding = `__smrt_consumer_${nextImportBinding++}`;
806
+ transitiveBindings.set(key, {
807
+ binding,
808
+ namespaceBinding: namespace.binding,
809
+ exportName
810
+ });
811
+ return binding;
812
+ };
813
+ const isSameProjectAndProviderImport = (importPath, providerPackageName) => {
814
+ const importPackageName = packageNameFromImportPath(importPath);
815
+ const projectPackageJson = findInstalledPackageJson(importPackageName, projectRoot);
816
+ if (!providerPackageName) return Boolean(projectPackageJson);
817
+ const providerPackageJson = findInstalledPackageJson(providerPackageName, projectRoot);
818
+ if (!providerPackageJson) return false;
819
+ return sameInstalledPackage(projectPackageJson, importPackageName === providerPackageName ? providerPackageJson : findInstalledPackageJson(importPackageName, path$1.dirname(providerPackageJson)));
820
+ };
821
+ for (const packageName of manifest.smrtDependencies ?? []) if (isSameProjectAndProviderImport(packageName, packageName) && !importsByPath.has(packageName)) importsByPath.set(packageName, /* @__PURE__ */ new Map());
634
822
  const registrations = [];
635
823
  const registrationManifests = {};
636
824
  let importedEntryCount = 0;
@@ -676,8 +864,16 @@ async function generateRegistrationFile(manifest, projectRoot) {
676
864
  const collectionExportName = def.collectionExportName;
677
865
  const hasCollection = def.hasCollection;
678
866
  const tableName = def.collection || objectName.toLowerCase();
679
- const exportBinding = getImportBinding(importPath, exportName);
680
- const collectionBinding = hasCollection && collectionExportName ? getImportBinding(importPath, collectionExportName) : void 0;
867
+ const qualifiedName = def.qualifiedName || (objectName.includes(":") ? objectName : `${def.packageName}:${def.className || exportName}`);
868
+ const providerPackageName = def.providerPackageName;
869
+ const appResolvable = isSameProjectAndProviderImport(importPath, providerPackageName);
870
+ if (!appResolvable && !providerPackageName) throw new Error(`[smrt:consumer] ${objectName} has no configured provider provenance for ${importPath}`);
871
+ const exportBinding = appResolvable ? getImportBinding(importPath, exportName) : getTransitiveBinding(importPath, providerPackageName, exportName);
872
+ const collectionBinding = hasCollection && collectionExportName ? appResolvable ? getImportBinding(importPath, collectionExportName) : getTransitiveBinding(importPath, providerPackageName, collectionExportName) : void 0;
873
+ if (!appResolvable && collectionBinding) transitiveCollectionChecks.push({
874
+ collectionBinding,
875
+ itemBinding: exportBinding
876
+ });
681
877
  importedEntryCount++;
682
878
  if (isCollectionClass(def)) continue;
683
879
  const logicalName = def.className || exportName;
@@ -688,7 +884,10 @@ async function generateRegistrationFile(manifest, projectRoot) {
688
884
  objects: { [objectName]: def }
689
885
  };
690
886
  registrations.push(`if (${exportBinding}) ObjectRegistry.register(${exportBinding}, { name: ${JSON.stringify(logicalName)}, packageName: ${JSON.stringify(def.packageName)}, _manifest: smrtRegistrationManifests[${JSON.stringify(objectName)}], _manifestKey: ${JSON.stringify(objectName)} });`);
691
- if (collectionBinding) registrations.push(`if (${collectionBinding}) ObjectRegistry.registerCollection('${tableName}', ${collectionBinding});`);
887
+ if (collectionBinding) registrations.push(`if (${collectionBinding}) {
888
+ ObjectRegistry.registerCollection(${JSON.stringify(qualifiedName)}, ${collectionBinding});
889
+ ObjectRegistry.registerCollection(${JSON.stringify(tableName)}, ${collectionBinding});
890
+ }`);
692
891
  registeredObjectCount++;
693
892
  }
694
893
  if (importedEntryCount === 0) {
@@ -699,6 +898,10 @@ async function generateRegistrationFile(manifest, projectRoot) {
699
898
  const sortedImports = Array.from(importsByPath.entries()).sort(([left], [right]) => left.localeCompare(right));
700
899
  const imports = sortedImports.map(([importPath], index) => `import * as __smrt_provider_${index} from '${importPath}';`);
701
900
  const importDeclarations = sortedImports.flatMap(([, specifiers], index) => Array.from(specifiers.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([exportName, binding]) => `const ${binding} = getSmrtExport(__smrt_provider_${index}, ${JSON.stringify(exportName)});`));
901
+ const transitiveNamespaceDeclarations = Array.from(transitiveNamespaces.values()).sort((left, right) => left.binding.localeCompare(right.binding)).map(({ binding, providerPackageName, importPath }) => `const ${binding} = await importSmrtDependency(${JSON.stringify(importPath)}, ${JSON.stringify(providerPackageName)});`);
902
+ const transitiveDeclarations = Array.from(transitiveBindings.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([, { binding, namespaceBinding, exportName }]) => `const ${binding} = getSmrtExport(${namespaceBinding}, ${JSON.stringify(exportName)});
903
+ if (!${binding}) throw new Error(${JSON.stringify(`[smrt:consumer] Provider-owned module did not export ${exportName}`)});`);
904
+ const transitiveCollectionDeclarations = transitiveCollectionChecks.map(({ collectionBinding, itemBinding }) => `if (${collectionBinding}._itemClass !== ${itemBinding}) throw new Error('[smrt:consumer] Provider-owned collection does not match its item constructor');`);
702
905
  const registrationManifestLiteral = JSON.stringify(JSON.stringify(registrationManifests));
703
906
  const content = `/**
704
907
  * Auto-generated by @happyvertical/smrt-core/consumer-plugin
@@ -709,6 +912,7 @@ async function generateRegistrationFile(manifest, projectRoot) {
709
912
  */
710
913
 
711
914
  import { ObjectRegistry } from '@happyvertical/smrt-core';
915
+ ${transitiveNamespaces.size > 0 ? "import { registerHooks } from 'node:module';" : ""}
712
916
 
713
917
  ${imports.join("\n")}
714
918
 
@@ -719,7 +923,30 @@ ${imports.join("\n")}
719
923
  */
720
924
  const getSmrtExport = (provider, exportName) =>
721
925
  typeof provider[exportName] === 'function' ? provider[exportName] : undefined;
926
+ ${transitiveNamespaces.size > 0 ? `const SMRT_RESOLVER_MODULE = 'data:text/javascript,export default (specifier) => import.meta.resolve(specifier)';
927
+ let smrtResolverToken = 0;
928
+ const importSmrtDependency = async (specifier, providerPackageName) => {
929
+ const parentURL = import.meta.resolve(providerPackageName);
930
+ const token = \`smrt-consumer-resolve-\${smrtResolverToken++}\`;
931
+ const hooks = registerHooks({
932
+ resolve(requested, context, nextResolve) {
933
+ return requested === token
934
+ ? nextResolve(specifier, { ...context, parentURL })
935
+ : nextResolve(requested, context);
936
+ },
937
+ });
938
+ try {
939
+ const resolver = (await import(/* @vite-ignore */ SMRT_RESOLVER_MODULE)).default;
940
+ return import(/* @vite-ignore */ resolver(token));
941
+ } finally {
942
+ hooks.deregister();
943
+ }
944
+ };
945
+ ` : ""}
946
+ ${transitiveNamespaceDeclarations.join("\n")}
722
947
  ${importDeclarations.join("\n")}
948
+ ${transitiveDeclarations.join("\n")}
949
+ ${transitiveCollectionDeclarations.join("\n")}
723
950
 
724
951
  const smrtRegistrationManifests = JSON.parse(${registrationManifestLiteral});
725
952