@happyvertical/smrt-core 0.49.5 → 0.49.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +56 -0
  2. package/dist/consumer-plugin/index.d.ts +19 -4
  3. package/dist/consumer-plugin/index.d.ts.map +1 -1
  4. package/dist/consumer-plugin/index.js +214 -6
  5. package/dist/consumer-plugin/index.js.map +1 -1
  6. package/dist/manifest/static-manifest.d.ts.map +1 -1
  7. package/dist/manifest/static-manifest.js +13 -7
  8. package/dist/manifest/static-manifest.js.map +1 -1
  9. package/dist/manifest/store.js +1 -1
  10. package/dist/manifest/store.js.map +1 -1
  11. package/dist/manifest.json +13 -7
  12. package/dist/scanner/manifest-generator.d.ts +39 -0
  13. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  14. package/dist/scanner/manifest-generator.js +79 -12
  15. package/dist/scanner/manifest-generator.js.map +1 -1
  16. package/dist/smrt-knowledge.json +5 -4
  17. package/dist/vite-plugin/changes-route.d.ts.map +1 -1
  18. package/dist/vite-plugin/changes-route.js +4 -3
  19. package/dist/vite-plugin/changes-route.js.map +1 -1
  20. package/dist/vite-plugin/dev-plane-route.d.ts +2 -0
  21. package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
  22. package/dist/vite-plugin/dev-plane-route.js +8 -5
  23. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  24. package/dist/vite-plugin/events-route.d.ts.map +1 -1
  25. package/dist/vite-plugin/events-route.js +4 -3
  26. package/dist/vite-plugin/events-route.js.map +1 -1
  27. package/dist/vite-plugin/index.d.ts.map +1 -1
  28. package/dist/vite-plugin/index.js +25 -40
  29. package/dist/vite-plugin/index.js.map +1 -1
  30. package/dist/vite-plugin/resources-route.d.ts +6 -0
  31. package/dist/vite-plugin/resources-route.d.ts.map +1 -1
  32. package/dist/vite-plugin/resources-route.js +30 -7
  33. package/dist/vite-plugin/resources-route.js.map +1 -1
  34. package/dist/vite-plugin/sveltekit-config-import.d.ts +19 -0
  35. package/dist/vite-plugin/sveltekit-config-import.d.ts.map +1 -0
  36. package/dist/vite-plugin/sveltekit-config-import.js +36 -0
  37. package/dist/vite-plugin/sveltekit-config-import.js.map +1 -0
  38. package/dist/vite-plugin/sveltekit-generator.d.ts +33 -2
  39. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  40. package/dist/vite-plugin/sveltekit-generator.js +181 -65
  41. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  42. package/dist/vite-plugin/sveltekit-path.d.ts +8 -0
  43. package/dist/vite-plugin/sveltekit-path.d.ts.map +1 -0
  44. package/dist/vite-plugin/sveltekit-path.js +25 -0
  45. package/dist/vite-plugin/sveltekit-path.js.map +1 -0
  46. package/dist/vite-plugin/sveltekit-route-coordinator.d.ts +67 -0
  47. package/dist/vite-plugin/sveltekit-route-coordinator.d.ts.map +1 -0
  48. package/dist/vite-plugin/sveltekit-route-coordinator.js +321 -0
  49. package/dist/vite-plugin/sveltekit-route-coordinator.js.map +1 -0
  50. package/dist/vite-plugin/sync-apply-route.d.ts +1 -1
  51. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  52. package/dist/vite-plugin/sync-apply-route.js +4 -3
  53. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  54. package/package.json +6 -5
package/README.md CHANGED
@@ -207,12 +207,68 @@ manifest-derived fields, actions, relationships, WebMCP descriptors, and
207
207
  objects are not included. During declaration generation the matching
208
208
  `smrt-web.d.ts` module is written alongside the other consumer virtual modules.
209
209
 
210
+ For SvelteKit projects, default consumer declaration generation runs during the
211
+ Vite configuration lifecycle as well as the normal build lifecycle. With
212
+ SvelteKit 2.69.3 or later, `svelte-kit sync && svelte-check` loads that Vite
213
+ configuration, so a clean checkout receives the physical `@smrt/manifest`,
214
+ `@smrt/client`, and `@smrt/web` declarations before type checking. Set
215
+ `generateTypes: false` only when another generation step owns those files.
216
+
210
217
  ### Generated SvelteKit routes
211
218
 
212
219
  Enable SvelteKit route generation with `svelteKit: { enabled: true }`. Its
213
220
  default output directory is `src/routes/api`; set `svelteKit.routesDir` when
214
221
  your application uses a different route root.
215
222
 
223
+ A consumer can host selected dependency models with the same generator. This is
224
+ an explicit HTTP boundary that is separate from the broader `packages`
225
+ registration inventory. Name exact provider-qualified object references; a
226
+ simple class name, an unknown object, or an empty list fails the build before
227
+ any generated route is replaced:
228
+
229
+ ```ts
230
+ smrtConsumer({
231
+ packages: runtimeProviderPackages,
232
+ svelteKit: {
233
+ objects: ['@acme/widgets:Widget'],
234
+ routesDir: 'src/routes/api',
235
+ },
236
+ });
237
+ ```
238
+
239
+ `projectRoot` selects one artifact root for consumer route generation,
240
+ registration, manifests, and types; it defaults to the current working
241
+ directory. It remains the consumer artifact root when a Vite config declares a
242
+ different `root`; pass `projectRoot` explicitly when consumer artifacts should
243
+ share that Vite root or belong elsewhere.
244
+
245
+ Only the listed objects are passed to route generation, so another API model in
246
+ the same provider remains absent until it is named. `api: false` and an empty
247
+ `api.include` still suppress handlers, and generated auth, writable-field, and
248
+ tenant safeguards are unchanged. `svelteKit: true` remains a legacy consumer
249
+ integration flag and does not host dependency CRUD routes. Consumer-hosted
250
+ `_changes`, `_events`, and `_resources` routes are disabled unless their
251
+ existing option is explicitly enabled; `sync/apply` is generated when a selected model exposes a mutating API
252
+ action.
253
+
254
+ When `smrtPlugin()` and `smrtConsumer()` generate routes in one SvelteKit
255
+ application, they may use the same canonical `routesDir` and share one
256
+ generated surface, or use disjoint directories. Nested directories are
257
+ rejected before files are changed because a parent generated-root cleanup would
258
+ otherwise own and remove the child surface.
259
+ Canonical ownership resolves symlink aliases through the nearest existing
260
+ directory, so paths that name the same generated directory share one surface.
261
+ When separate artifact roots resolve to that same route directory, their
262
+ resolved object and config outputs must also agree; incompatible settings fail
263
+ before generated handlers or managed ownership records change.
264
+
265
+ The consumer keeps a private managed-root inventory under `.smrt` after a
266
+ successful explicit hosting plan is validated. Changing its `routesDir`,
267
+ setting `svelteKit: false`, returning to legacy `svelteKit: true`, or omitting
268
+ the option reconciles only consumer-managed generated handlers before SvelteKit
269
+ inventories routes. Handwritten handlers and current producer-owned routes are
270
+ preserved.
271
+
216
272
  The plugin records only the concrete `+server.ts` files it generated in a
217
273
  bounded `.gitignore` block. Handwritten handlers below `routesDir` remain
218
274
  visible to Git, including routes that live beside generated resource handlers.
@@ -1,6 +1,15 @@
1
1
  import { Plugin } from 'vite';
2
2
  import { SmrtGenerationSnapshotOptions } from '../generation-snapshot.js';
3
+ import { SvelteKitOptions } from '../vite-plugin/sveltekit-generator.js';
3
4
  export { loadVerifiedSmrtGenerationSnapshot, type SerializeSmrtGenerationSnapshotOptions, type SmrtGenerationSnapshotArtifact, type SmrtGenerationSnapshotOptions, type SmrtGenerationSnapshotView, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot, } from '../generation-snapshot.js';
5
+ /**
6
+ * SvelteKit route-hosting options for dependency models. Unlike `packages`,
7
+ * `objects` is an HTTP exposure boundary: every entry must be an exact,
8
+ * provider-qualified manifest key (for example, `@acme/widgets:Widget`).
9
+ */
10
+ export interface SmrtConsumerSvelteKitOptions extends Partial<Pick<SvelteKitOptions, 'routesDir' | 'configPath' | 'configFileName' | 'kebabRoutes' | 'changesRoute' | 'eventsRoute' | 'resourcesRoute'>> {
11
+ objects: readonly string[];
12
+ }
4
13
  export interface SmrtConsumerOptions {
5
14
  /** SMRT packages to scan (e.g., ['@my-org/products', '@my-org/content']) */
6
15
  packages?: string[];
@@ -8,7 +17,7 @@ export interface SmrtConsumerOptions {
8
17
  generateTypes?: boolean;
9
18
  /** Output directory for generated types */
10
19
  typesDir?: string;
11
- /** Project root path */
20
+ /** Project root path (defaults to the current working directory) */
12
21
  projectRoot?: string;
13
22
  /**
14
23
  * Reuse an immutable, verified aggregated manifest instead of discovering
@@ -16,11 +25,17 @@ export interface SmrtConsumerOptions {
16
25
  * types still consume the verified manifest.
17
26
  */
18
27
  generationSnapshot?: SmrtGenerationSnapshotOptions;
19
- /** SvelteKit integration mode */
20
- svelteKit?: boolean;
28
+ /**
29
+ * Consumer SvelteKit integration. `true` retains the historical compatibility
30
+ * mode and does not generate dependency routes. Route hosting requires an
31
+ * explicit, provider-qualified object allowlist.
32
+ */
33
+ svelteKit?: boolean | SmrtConsumerSvelteKitOptions;
21
34
  /**
22
35
  * Apply kebab-case to generated custom-method URL segments. This must match
23
- * the producer plugin's `svelteKit.kebabRoutes` setting.
36
+ * the producer plugin's `svelteKit.kebabRoutes` setting. When explicit
37
+ * consumer SvelteKit hosting is configured, its `kebabRoutes` value takes
38
+ * precedence, including an explicit `false`.
24
39
  */
25
40
  kebabRoutes?: boolean;
26
41
  /** Use static types only (for federation builds) */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consumer-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAWnC,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;AAkBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAmLtE"}
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;AAWnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uCAAuC,CAAC;AAoB/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,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;AA0VD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAqVtE"}
@@ -1,9 +1,12 @@
1
1
  import "../scanner/types.js";
2
- import { generateWebModule } from "../vite-plugin/web-collections.js";
2
+ import { canonicalSvelteKitPath } from "../vite-plugin/sveltekit-path.js";
3
+ import { clearGeneratedSvelteKitRouteFiles, reconcileSvelteKitRouteGitignore } from "../vite-plugin/sveltekit-generator.js";
4
+ import { generateWebModule, isCollectionManifestClass, resolveCollectionItemObject } from "../vite-plugin/web-collections.js";
3
5
  import { buildDomainKnowledgeManifest } from "../knowledge.js";
4
6
  import { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sha256SmrtGenerationSnapshot } from "../generation-snapshot.js";
5
7
  import { resolveFileKnowledgeConfig } from "../knowledge-config.js";
6
8
  import { generateClientModule } from "../vite-plugin/generated-client.js";
9
+ import { activeProducerKnowledgeRoutePaths, activeSvelteKitRouteParticipants, assertNoSvelteKitRouteRootSymlinkConflict, assertSvelteKitRouteCoordinationComplete, contributeSvelteKitRoutes, expectedSvelteKitRouteOwners, markSvelteKitRouteParticipant, producerKnowledgeRoutePaths, revokeSvelteKitRoutes } from "../vite-plugin/sveltekit-route-coordinator.js";
7
10
  import { generateDeclarations } from "../prebuild/index.js";
8
11
  import { publishArtifactFiles } from "./artifact-publication.js";
9
12
  import * as fs from "node:fs";
@@ -21,25 +24,227 @@ var VIRTUAL_MODULES = {
21
24
  "@smrt/manifest": "smrt-consumer:manifest",
22
25
  "@smrt/web": "smrt-consumer:web"
23
26
  };
27
+ var CONSUMER_SVELTEKIT_ROUTES_ARTIFACT_VERSION = 1;
28
+ var CONSUMER_SVELTEKIT_ROUTES_ARTIFACT = "consumer-sveltekit-routes.json";
29
+ function consumerSvelteKitRoutesArtifactPath(projectRoot) {
30
+ return path.join(projectRoot, ".smrt", CONSUMER_SVELTEKIT_ROUTES_ARTIFACT);
31
+ }
32
+ /** Persist only project-relative consumer route roots, never an output file list. */
33
+ function canonicalConsumerRouteRoot(projectRoot, routesDir) {
34
+ const root = path.resolve(projectRoot);
35
+ const relative = path.relative(root, path.resolve(root, routesDir));
36
+ if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`[smrt:consumer] svelteKit.routesDir must be a project-relative subdirectory (received ${JSON.stringify(routesDir)})`);
37
+ return relative.split(path.sep).join("/");
38
+ }
39
+ function loadConsumerSvelteKitRouteRoots(projectRoot) {
40
+ const artifactPath = consumerSvelteKitRoutesArtifactPath(projectRoot);
41
+ if (!fs.existsSync(artifactPath)) return [];
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(fs.readFileSync(artifactPath, "utf-8"));
45
+ } catch {
46
+ throw new Error(`[smrt:consumer] Cannot read ${CONSUMER_SVELTEKIT_ROUTES_ARTIFACT}; refusing to leave hosted routes unreconciled`);
47
+ }
48
+ if (parsed?.version !== CONSUMER_SVELTEKIT_ROUTES_ARTIFACT_VERSION || !Array.isArray(parsed.routesDir) || parsed.routesDir.some((routesDir) => typeof routesDir !== "string")) throw new Error(`[smrt:consumer] Invalid ${CONSUMER_SVELTEKIT_ROUTES_ARTIFACT}; refusing to leave hosted routes unreconciled`);
49
+ return [...new Set(parsed.routesDir)].map((routesDir) => canonicalConsumerRouteRoot(projectRoot, routesDir));
50
+ }
51
+ function publishConsumerSvelteKitRouteRoots(projectRoot, routesDir) {
52
+ const artifactPath = consumerSvelteKitRoutesArtifactPath(projectRoot);
53
+ fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
54
+ publishArtifactFiles([{
55
+ path: artifactPath,
56
+ content: JSON.stringify({
57
+ version: CONSUMER_SVELTEKIT_ROUTES_ARTIFACT_VERSION,
58
+ routesDir: [...new Set(routesDir)].sort()
59
+ }, null, 2)
60
+ }]);
61
+ }
62
+ function removeConsumerSvelteKitRouteRoots(projectRoot) {
63
+ const artifactPath = consumerSvelteKitRoutesArtifactPath(projectRoot);
64
+ if (fs.existsSync(artifactPath)) fs.unlinkSync(artifactPath);
65
+ }
66
+ /**
67
+ * A hosting-to-hosting move can replace one configured root with another in a
68
+ * fresh lifecycle. Validate every durable former root before the new target
69
+ * journals or clears anything, otherwise a rejected move could alter the
70
+ * prior generated surface before reconciliation notices the conflict.
71
+ */
72
+ async function assertConsumerSvelteKitFormerRouteRootsAreSafe(userConfig, projectRoot, routesDir, env) {
73
+ if (routesDir.length === 0) return;
74
+ const activeRoots = (await activeSvelteKitRouteParticipants(userConfig, projectRoot, env)).map((participant) => participant.routesDir);
75
+ for (const priorRoutesDir of routesDir) assertNoSvelteKitRouteRootSymlinkConflict(canonicalSvelteKitPath(path.resolve(projectRoot, priorRoutesDir)), activeRoots);
76
+ }
77
+ async function reconcileConsumerSvelteKitRouteRoots(lifecycle, userConfig, projectRoot, routesDir, afterReconciled, env) {
78
+ const remaining = new Set(routesDir);
79
+ const reconcileOne = (routesDir) => {
80
+ remaining.delete(routesDir);
81
+ if (remaining.size === 0) afterReconciled();
82
+ };
83
+ if (remaining.size === 0) {
84
+ afterReconciled();
85
+ return;
86
+ }
87
+ for (const routesDir of [...remaining]) {
88
+ const routeRoot = canonicalSvelteKitPath(path.resolve(projectRoot, routesDir));
89
+ const activeParticipants = await activeSvelteKitRouteParticipants(userConfig, projectRoot, env);
90
+ assertNoSvelteKitRouteRootSymlinkConflict(routeRoot, activeParticipants.map((participant) => participant.routesDir));
91
+ if (activeParticipants.find((participant) => participant.owner === "consumer" && (routeRoot === participant.routesDir || routeRoot.startsWith(`${participant.routesDir}${path.sep}`)))) {
92
+ reconcileOne(routesDir);
93
+ continue;
94
+ }
95
+ const containingProducer = activeParticipants.find((participant) => participant.owner === "producer" && (routeRoot === participant.routesDir || routeRoot.startsWith(`${participant.routesDir}${path.sep}`)));
96
+ if (containingProducer) {
97
+ await revokeSvelteKitRoutes(lifecycle, await expectedSvelteKitRouteOwners(userConfig, containingProducer.projectRoot, containingProducer.routesDir, env), containingProducer.projectRoot, containingProducer.routesDir, () => reconcileOne(routesDir));
98
+ continue;
99
+ }
100
+ const owners = await expectedSvelteKitRouteOwners(userConfig, projectRoot, routesDir, env);
101
+ if (owners.includes("producer")) {
102
+ await revokeSvelteKitRoutes(lifecycle, owners, projectRoot, routesDir, () => reconcileOne(routesDir));
103
+ continue;
104
+ }
105
+ clearGeneratedSvelteKitRouteFiles(routeRoot, producerKnowledgeRoutePaths(lifecycle), new Set(activeParticipants.map((participant) => participant.routesDir).filter((activeRoot) => activeRoot !== routeRoot)));
106
+ reconcileSvelteKitRouteGitignore(projectRoot, routesDir);
107
+ reconcileOne(routesDir);
108
+ }
109
+ }
110
+ function consumerRouteOptions(value) {
111
+ if (!value || value === true) return void 0;
112
+ if (!Array.isArray(value.objects) || value.objects.length === 0) throw new Error("[smrt:consumer] svelteKit.objects must list at least one provider-qualified object reference");
113
+ for (const objectRef of value.objects) if (typeof objectRef !== "string" || !objectRef.includes(":")) throw new Error(`[smrt:consumer] svelteKit.objects entries must be provider-qualified (received ${JSON.stringify(objectRef)})`);
114
+ return value;
115
+ }
116
+ function consumerUtilityOption(value) {
117
+ return value?.enabled === true ? value : { enabled: false };
118
+ }
119
+ function consumerObjectRef(manifestKey, objectDef) {
120
+ if (manifestKey.includes(":")) return manifestKey;
121
+ if (objectDef.qualifiedName?.includes(":")) return objectDef.qualifiedName;
122
+ if (objectDef.packageName && objectDef.className) return `${objectDef.packageName}:${objectDef.className}`;
123
+ }
124
+ /**
125
+ * Select exactly the dependency objects that a consumer explicitly hosts.
126
+ * Validation completes before the generator clears its managed files, so an
127
+ * invalid deployment cannot erase a previously generated route surface.
128
+ */
129
+ function selectConsumerRouteManifest(manifest, options) {
130
+ const entriesByRef = /* @__PURE__ */ new Map();
131
+ for (const [manifestKey, objectDef] of Object.entries(manifest.objects)) {
132
+ const objectRef = consumerObjectRef(manifestKey, objectDef);
133
+ if (objectRef) entriesByRef.set(objectRef, [manifestKey, objectDef]);
134
+ }
135
+ const selected = /* @__PURE__ */ new Set();
136
+ const objects = {};
137
+ for (const objectRef of options.objects) {
138
+ const entry = entriesByRef.get(objectRef);
139
+ if (!entry) throw new Error(`[smrt:consumer] svelteKit.objects references unknown dependency object ${JSON.stringify(objectRef)}`);
140
+ if (selected.has(objectRef)) throw new Error(`[smrt:consumer] svelteKit.objects contains duplicate object ${JSON.stringify(objectRef)}`);
141
+ selected.add(objectRef);
142
+ const [, objectDef] = entry;
143
+ objects[objectRef] = {
144
+ ...objectDef,
145
+ qualifiedName: objectRef
146
+ };
147
+ }
148
+ const sourceManifest = manifest;
149
+ for (const [manifestKey, objectDef] of Object.entries(manifest.objects)) {
150
+ const candidate = objectDef;
151
+ if (!isCollectionManifestClass(sourceManifest, candidate)) continue;
152
+ const item = resolveCollectionItemObject(sourceManifest, candidate);
153
+ const itemEntry = item ? Object.entries(manifest.objects).find(([, value]) => value === item) : void 0;
154
+ const itemRef = itemEntry ? consumerObjectRef(itemEntry[0], itemEntry[1]) : void 0;
155
+ if (!itemRef || !selected.has(itemRef)) continue;
156
+ const collectionRef = consumerObjectRef(manifestKey, objectDef);
157
+ if (collectionRef) objects[collectionRef] = {
158
+ ...objectDef,
159
+ qualifiedName: collectionRef
160
+ };
161
+ }
162
+ return {
163
+ ...manifest,
164
+ smrtDependencies: [...new Set(Object.values(manifest.objects).map((objectDef) => objectDef.packageName).filter((packageName) => typeof packageName === "string" && packageName !== manifest.packageName))].sort(),
165
+ objects
166
+ };
167
+ }
24
168
  /**
25
169
  * Consumer plugin for projects that use SMRT packages
26
170
  */
27
171
  function smrtConsumer(options = {}) {
28
172
  const { packages = [], generateTypes = true, typesDir = "src/types/smrt-generated", projectRoot = process.cwd(), generationSnapshot, disableScanning = false, kebabRoutes = false } = options;
173
+ const consumerSvelteKit = consumerRouteOptions(options.svelteKit);
174
+ const effectiveKebabRoutes = consumerSvelteKit?.kebabRoutes ?? kebabRoutes;
29
175
  let smrtPackages = [];
30
176
  let typeManifest = null;
31
177
  let typesGenerated = false;
32
178
  let producerApi;
179
+ let routeLifecycleConfig;
33
180
  function loadGenerationSnapshot() {
34
181
  if (!generationSnapshot) throw new Error("[smrt:consumer] Generation snapshot is not configured");
35
182
  return loadVerifiedSmrtGenerationSnapshot(generationSnapshot, projectRoot, "dependencies");
36
183
  }
37
- return {
184
+ async function generateConfigTypes(manifest) {
185
+ if (!generateTypes || typesGenerated) return;
186
+ typeManifest = manifest ?? (generationSnapshot ? loadGenerationSnapshot() : await aggregateTypeManifests(packages.length === 0 && !disableScanning ? await discoverSmrtPackages(projectRoot) : packages, projectRoot));
187
+ await generateProjectTypes(typeManifest, typesDir, projectRoot);
188
+ typesGenerated = true;
189
+ }
190
+ const plugin = {
38
191
  name: "smrt-consumer",
39
- config() {
40
- return { build: { rollupOptions: { external: [/\.node$/] } } };
192
+ enforce: "pre",
193
+ config: {
194
+ order: "pre",
195
+ async handler(userConfig, env) {
196
+ const routeLifecycle = env ?? userConfig;
197
+ routeLifecycleConfig = routeLifecycle;
198
+ const previousConsumerRouteRoots = loadConsumerSvelteKitRouteRoots(projectRoot);
199
+ if (consumerSvelteKit) {
200
+ await assertConsumerSvelteKitFormerRouteRootsAreSafe(userConfig, projectRoot, previousConsumerRouteRoots, env);
201
+ const routePackages = packages.length === 0 && !disableScanning ? await discoverSmrtPackages(projectRoot) : packages;
202
+ const routeManifest = generationSnapshot ? loadGenerationSnapshot() : await aggregateTypeManifests(routePackages, projectRoot);
203
+ const hostedManifest = selectConsumerRouteManifest(routeManifest, consumerSvelteKit);
204
+ const routesDir = canonicalConsumerRouteRoot(projectRoot, consumerSvelteKit.routesDir ?? "src/routes/api");
205
+ const reservedRoutePaths = await activeProducerKnowledgeRoutePaths(userConfig, projectRoot, env);
206
+ const routeOptions = {
207
+ enabled: true,
208
+ routesDir,
209
+ objectsDir: "src/lib/objects",
210
+ configPath: consumerSvelteKit.configPath ?? "src/lib/server",
211
+ configFileName: consumerSvelteKit.configFileName ?? "smrt.ts",
212
+ kebabRoutes: effectiveKebabRoutes,
213
+ changesRoute: consumerUtilityOption(consumerSvelteKit.changesRoute),
214
+ eventsRoute: consumerUtilityOption(consumerSvelteKit.eventsRoute),
215
+ resourcesRoute: consumerUtilityOption(consumerSvelteKit.resourcesRoute),
216
+ rejectRouteCollisions: true
217
+ };
218
+ let ownershipJournaled = false;
219
+ let reconciliationScheduled = false;
220
+ await contributeSvelteKitRoutes(routeLifecycle, await expectedSvelteKitRouteOwners(userConfig, projectRoot, routeOptions.routesDir, env), projectRoot, {
221
+ owner: "consumer",
222
+ routeManifest: hostedManifest,
223
+ semanticManifest: routeManifest,
224
+ options: routeOptions,
225
+ reservedRoutePaths,
226
+ beforeCleanup: async () => {
227
+ await generateConfigTypes(routeManifest);
228
+ if (ownershipJournaled) return;
229
+ ownershipJournaled = true;
230
+ publishConsumerSvelteKitRouteRoots(projectRoot, [...previousConsumerRouteRoots, routesDir]);
231
+ },
232
+ afterGenerate: async () => {
233
+ if (reconciliationScheduled) return;
234
+ reconciliationScheduled = true;
235
+ const priorRoots = previousConsumerRouteRoots.filter((previousRoot) => previousRoot !== routesDir);
236
+ await reconcileConsumerSvelteKitRouteRoots(routeLifecycle, userConfig, projectRoot, priorRoots, () => publishConsumerSvelteKitRouteRoots(projectRoot, [routesDir]), env);
237
+ }
238
+ });
239
+ } else {
240
+ await generateConfigTypes();
241
+ if (previousConsumerRouteRoots.length > 0) await reconcileConsumerSvelteKitRouteRoots(routeLifecycle, userConfig, projectRoot, previousConsumerRouteRoots, () => removeConsumerSvelteKitRouteRoots(projectRoot), env);
242
+ }
243
+ return { build: { rollupOptions: { external: [/\.node$/] } } };
244
+ }
41
245
  },
42
246
  configResolved(resolvedConfig) {
247
+ if (consumerSvelteKit && routeLifecycleConfig) assertSvelteKitRouteCoordinationComplete(routeLifecycleConfig);
43
248
  producerApi = (resolvedConfig.plugins ?? []).find((plugin) => plugin?.name === "smrt-auto-service")?.api;
44
249
  },
45
250
  async buildStart() {
@@ -94,15 +299,17 @@ function smrtConsumer(options = {}) {
94
299
  };
95
300
  switch (cleanId) {
96
301
  case "smrt-consumer:routes": return generateFallbackRoutesModule();
97
- case "smrt-consumer:client": return generateFallbackClientModule(typeManifest, { kebabRoutes });
302
+ case "smrt-consumer:client": return generateFallbackClientModule(typeManifest, { kebabRoutes: effectiveKebabRoutes });
98
303
  case "smrt-consumer:mcp": return generateFallbackMcpModule();
99
304
  case "smrt-consumer:types": return generateFallbackTypesModule(typeManifest);
100
305
  case "smrt-consumer:manifest": return generateFallbackManifestModule(typeManifest);
101
- case "smrt-consumer:web": return generateWebModule(typeManifest, { kebabRoutes });
306
+ case "smrt-consumer:web": return generateWebModule(typeManifest, { kebabRoutes: effectiveKebabRoutes });
102
307
  default: return null;
103
308
  }
104
309
  }
105
310
  };
311
+ markSvelteKitRouteParticipant(plugin, "consumer", Boolean(consumerSvelteKit), consumerSvelteKit?.routesDir ?? "src/routes/api", void 0, () => projectRoot);
312
+ return plugin;
106
313
  }
107
314
  /**
108
315
  * Discover SMRT packages from a consumer app's dependencies.
@@ -150,6 +357,7 @@ async function aggregateTypeManifests(packages, projectRoot) {
150
357
  const aggregatedManifest = {
151
358
  version: "1.0.0",
152
359
  timestamp: 0,
360
+ smrtDependencies: [...packages],
153
361
  objects: {}
154
362
  };
155
363
  for (const packageName of packages) try {