@happyvertical/smrt-core 0.57.17 → 0.57.19
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 +14 -0
- package/agents/build-knowledge.md +3 -0
- package/agents/cookbook-engine.md +2 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +237 -10
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/cookbook/engine/commands.d.ts.map +1 -1
- package/dist/cookbook/engine/commands.js +7 -2
- package/dist/cookbook/engine/commands.js.map +1 -1
- package/dist/cookbook/engine/document.d.ts.map +1 -1
- package/dist/cookbook/engine/document.js +2 -0
- package/dist/cookbook/engine/document.js.map +1 -1
- package/dist/cookbook/engine/engine.d.ts.map +1 -1
- package/dist/cookbook/engine/engine.js +1 -0
- package/dist/cookbook/engine/engine.js.map +1 -1
- package/dist/cookbook/engine/schemas.d.ts.map +1 -1
- package/dist/cookbook/engine/schemas.js +7 -1
- package/dist/cookbook/engine/schemas.js.map +1 -1
- package/dist/cookbook/engine/types.d.ts +3 -1
- package/dist/cookbook/engine/types.d.ts.map +1 -1
- package/dist/cookbook/engine/types.js.map +1 -1
- package/dist/cookbook/v1.schema.json +1 -0
- package/dist/cookbook/validate.d.ts.map +1 -1
- package/dist/cookbook/validate.js +6 -1
- package/dist/cookbook/validate.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/scripts/generate-manifest.js +1 -0
- package/dist/scripts/prepack.js +4 -0
- package/dist/scripts/verify-production-manifest.js +25 -0
- package/dist/smrt-knowledge.json +8 -7
- package/dist/utils/stack-frames.d.ts.map +1 -1
- package/dist/utils/stack-frames.js +10 -0
- package/dist/utils/stack-frames.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +15 -6
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/package.json +11 -10
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,
|
|
@@ -28,7 +28,8 @@ engine.tools / engine.batchTool // JSON Schema tool definit
|
|
|
28
28
|
- **Commands** (20, `COOKBOOK_COMMANDS_VERSION = 1`): `apply_cookbook` (select),
|
|
29
29
|
`import_cookbook`, `add_cookbook`, `remove_cookbook`, `add_recipes`,
|
|
30
30
|
`remove_recipes`, `add_features`, `remove_features`, `set_settings`,
|
|
31
|
-
`set_policy`, `set_theme`, `reset_theme`, `set_name
|
|
31
|
+
`set_policy`, `set_theme`, `reset_theme`, `set_name` (name, description and `specs`, free-text requirements beyond
|
|
32
|
+
the recipes; internal newlines are kept), `rename_section`,
|
|
32
33
|
`rename_item`, `hide`, `show`, `validate`, `export_cookbook`, `undo`. Navigation
|
|
33
34
|
and focus are not commands: they are an adapter concern.
|
|
34
35
|
- **`set_theme`** takes `preset`, `primary` (a brand colour that replaces the
|
|
@@ -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;
|
|
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
|
|
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 {
|
|
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,
|
|
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
|
|
474
|
-
|
|
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
|
|
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
|
|
680
|
-
const
|
|
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})
|
|
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
|
|