every-plugin 3.0.0-rc.0 → 3.0.0-rc.10

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 (122) hide show
  1. package/dist/build/rspack/chunk-completeness-plugin.cjs +19 -7
  2. package/dist/build/rspack/chunk-completeness-plugin.cjs.map +1 -1
  3. package/dist/build/rspack/chunk-completeness-plugin.mjs +19 -7
  4. package/dist/build/rspack/chunk-completeness-plugin.mjs.map +1 -1
  5. package/dist/build/ui/generated-config.cjs +4 -0
  6. package/dist/build/ui/generated-config.cjs.map +1 -1
  7. package/dist/build/ui/generated-config.d.cts.map +1 -1
  8. package/dist/build/ui/generated-config.d.mts.map +1 -1
  9. package/dist/build/ui/generated-config.mjs +4 -1
  10. package/dist/build/ui/generated-config.mjs.map +1 -1
  11. package/dist/client.cjs +69 -0
  12. package/dist/client.cjs.map +1 -0
  13. package/dist/client.d.cts +15 -0
  14. package/dist/client.d.cts.map +1 -0
  15. package/dist/client.d.mts +15 -0
  16. package/dist/client.d.mts.map +1 -0
  17. package/dist/client.mjs +68 -0
  18. package/dist/client.mjs.map +1 -0
  19. package/dist/dev/host.cjs +149 -0
  20. package/dist/dev/host.cjs.map +1 -0
  21. package/dist/dev/host.d.cts +32 -0
  22. package/dist/dev/host.d.cts.map +1 -0
  23. package/dist/dev/host.d.mts +32 -0
  24. package/dist/dev/host.d.mts.map +1 -0
  25. package/dist/dev/host.mjs +148 -0
  26. package/dist/dev/host.mjs.map +1 -0
  27. package/dist/dev/serve.cjs +16 -111
  28. package/dist/dev/serve.cjs.map +1 -1
  29. package/dist/dev/serve.d.cts.map +1 -1
  30. package/dist/dev/serve.d.mts.map +1 -1
  31. package/dist/dev/serve.mjs +17 -112
  32. package/dist/dev/serve.mjs.map +1 -1
  33. package/dist/errors.cjs +1 -0
  34. package/dist/errors.cjs.map +1 -1
  35. package/dist/errors.d.cts +2 -2
  36. package/dist/errors.d.mts +2 -2
  37. package/dist/errors.mjs +2 -2
  38. package/dist/errors.mjs.map +1 -1
  39. package/dist/index.cjs +3 -0
  40. package/dist/index.d.cts +5 -4
  41. package/dist/index.d.mts +5 -4
  42. package/dist/index.mjs +3 -2
  43. package/dist/plugin.cjs +1 -0
  44. package/dist/plugin.cjs.map +1 -1
  45. package/dist/plugin.d.cts +23 -3
  46. package/dist/plugin.d.cts.map +1 -1
  47. package/dist/plugin.d.mts +22 -2
  48. package/dist/plugin.d.mts.map +1 -1
  49. package/dist/plugin.mjs +1 -0
  50. package/dist/plugin.mjs.map +1 -1
  51. package/dist/remote-entry.cjs +0 -1
  52. package/dist/remote-entry.cjs.map +1 -1
  53. package/dist/remote-entry.d.cts +1 -2
  54. package/dist/remote-entry.d.cts.map +1 -1
  55. package/dist/remote-entry.d.mts +1 -2
  56. package/dist/remote-entry.d.mts.map +1 -1
  57. package/dist/remote-entry.mjs +1 -1
  58. package/dist/remote-entry.mjs.map +1 -1
  59. package/dist/runtime/errors.cjs +20 -0
  60. package/dist/runtime/errors.cjs.map +1 -1
  61. package/dist/runtime/errors.d.cts +23 -1
  62. package/dist/runtime/errors.d.cts.map +1 -1
  63. package/dist/runtime/errors.d.mts +23 -1
  64. package/dist/runtime/errors.d.mts.map +1 -1
  65. package/dist/runtime/errors.mjs +20 -1
  66. package/dist/runtime/errors.mjs.map +1 -1
  67. package/dist/runtime/mf-config.cjs +1 -1
  68. package/dist/runtime/mf-config.mjs +1 -1
  69. package/dist/runtime/services/module-federation.service.cjs +1 -7
  70. package/dist/runtime/services/module-federation.service.cjs.map +1 -1
  71. package/dist/runtime/services/module-federation.service.mjs +1 -7
  72. package/dist/runtime/services/module-federation.service.mjs.map +1 -1
  73. package/dist/runtime/services/plugin-loader.service.cjs +29 -1
  74. package/dist/runtime/services/plugin-loader.service.cjs.map +1 -1
  75. package/dist/runtime/services/plugin-loader.service.d.cts +2 -2
  76. package/dist/runtime/services/plugin-loader.service.mjs +29 -1
  77. package/dist/runtime/services/plugin-loader.service.mjs.map +1 -1
  78. package/dist/runtime/services/shared-identity.cjs +1 -1
  79. package/dist/runtime/services/shared-identity.cjs.map +1 -1
  80. package/dist/runtime/services/shared-identity.mjs +1 -1
  81. package/dist/runtime/services/shared-identity.mjs.map +1 -1
  82. package/dist/types.d.cts +27 -2
  83. package/dist/types.d.cts.map +1 -1
  84. package/dist/types.d.mts +26 -1
  85. package/dist/types.d.mts.map +1 -1
  86. package/dist/ui/manifest/construct.cjs +69 -18
  87. package/dist/ui/manifest/construct.cjs.map +1 -1
  88. package/dist/ui/manifest/construct.d.cts.map +1 -1
  89. package/dist/ui/manifest/construct.d.mts.map +1 -1
  90. package/dist/ui/manifest/construct.mjs +69 -18
  91. package/dist/ui/manifest/construct.mjs.map +1 -1
  92. package/dist/ui/manifest/generator.cjs +1 -1
  93. package/dist/ui/manifest/generator.mjs +1 -1
  94. package/dist/ui/manifest/index.cjs +1 -1
  95. package/dist/ui/manifest/index.mjs +1 -1
  96. package/dist/version-manifest.cjs +14 -2
  97. package/dist/version-manifest.cjs.map +1 -1
  98. package/dist/version-manifest.d.cts +11 -1
  99. package/dist/version-manifest.d.cts.map +1 -1
  100. package/dist/version-manifest.d.mts +11 -1
  101. package/dist/version-manifest.d.mts.map +1 -1
  102. package/dist/version-manifest.mjs +14 -2
  103. package/dist/version-manifest.mjs.map +1 -1
  104. package/package.json +19 -2
  105. package/skills/plugin-client/SKILL.md +10 -4
  106. package/skills/plugin-testing/SKILL.md +2 -1
  107. package/src/build/rspack/chunk-completeness-plugin.ts +15 -6
  108. package/src/build/ui/generated-config.ts +4 -0
  109. package/src/client.ts +111 -0
  110. package/src/dev/host.ts +235 -0
  111. package/src/dev/serve.ts +22 -140
  112. package/src/errors.ts +2 -1
  113. package/src/index.ts +9 -0
  114. package/src/plugin.ts +26 -1
  115. package/src/remote-entry.ts +1 -2
  116. package/src/runtime/errors.ts +44 -0
  117. package/src/runtime/services/module-federation.service.ts +4 -7
  118. package/src/runtime/services/plugin-loader.service.ts +46 -2
  119. package/src/runtime/services/shared-identity.ts +1 -1
  120. package/src/types.ts +40 -2
  121. package/src/ui/manifest/construct.ts +68 -15
  122. package/src/version-manifest.ts +19 -1
@@ -1,3 +1,4 @@
1
+ import { CORE_UI_PLUGIN_KEY } from "./contract.mjs";
1
2
  import { MOUNT_REGISTRY, resolveMountSegment } from "./mount-registry.mjs";
2
3
  import { digestOf } from "./descriptor.mjs";
3
4
  import { compareNavItems } from "./nav.mjs";
@@ -94,28 +95,61 @@ async function constructTree(input) {
94
95
  const refs = [...input.plugins].sort((a, b) => a.key.localeCompare(b.key));
95
96
  const resolved = [];
96
97
  for (const ref of refs) resolved.push(await input.resolve(ref));
98
+ const ordered = [...resolved.filter((plugin) => plugin.key === "ui"), ...resolved.filter((plugin) => plugin.key !== "ui")];
97
99
  const optionsByPlugin = /* @__PURE__ */ new Map();
100
+ const skippedByPlugin = /* @__PURE__ */ new Map();
98
101
  for (const plugin of resolved) {
102
+ if (!plugin.manifest?.routes) continue;
99
103
  const optionsById = /* @__PURE__ */ new Map();
104
+ const skipped = /* @__PURE__ */ new Set();
100
105
  await Promise.all(plugin.manifest.routes.map(async (record) => {
101
106
  const loader = plugin.routeConfig.routeConfigLoaders[record.id];
102
- if (!loader) throw new Error(`routeConfig for "${plugin.key}" has no loader for route "${record.id}"`);
107
+ if (!loader) {
108
+ console.log(`[Compose] skipping route "${record.id}" in "${plugin.key}" — no routeConfig loader`);
109
+ skipped.add(record.id);
110
+ return;
111
+ }
103
112
  optionsById.set(record.id, await loader());
104
113
  }));
114
+ let changed = true;
115
+ while (changed) {
116
+ changed = false;
117
+ for (const record of plugin.manifest.routes) {
118
+ if (skipped.has(record.id)) continue;
119
+ if (record.parentId && skipped.has(record.parentId)) {
120
+ skipped.add(record.id);
121
+ changed = true;
122
+ }
123
+ }
124
+ }
105
125
  optionsByPlugin.set(plugin.key, optionsById);
126
+ skippedByPlugin.set(plugin.key, skipped);
106
127
  }
107
128
  const declarations = /* @__PURE__ */ new Map();
108
- for (const plugin of resolved) for (const record of plugin.manifest.routes) {
109
- if (!record.mount) continue;
110
- const mountId = record.mount;
111
- if (!MOUNT_REGISTRY[mountId].implemented) throw new Error(`mount "${record.mount}" is declared vocabulary but unimplemented (ADR 0008 §3) — declared by "${plugin.key}"`);
112
- const existing = declarations.get(mountId);
113
- if (existing) throw new Error(`mount "${mountId}" declared by multiple sources: "${existing.key}" (${existing.record.file}) and "${plugin.key}" (${record.file})`);
114
- declarations.set(mountId, {
115
- key: plugin.key,
116
- record,
117
- options: optionsByPlugin.get(plugin.key).get(record.id)
118
- });
129
+ const mountByLayoutId = /* @__PURE__ */ new Map();
130
+ for (const plugin of ordered) {
131
+ if (!plugin.manifest?.routes) continue;
132
+ for (const record of plugin.manifest.routes) {
133
+ if (!record.mount) continue;
134
+ const mountId = record.mount;
135
+ if (!MOUNT_REGISTRY[mountId].implemented) throw new Error(`mount "${record.mount}" is declared vocabulary but unimplemented (ADR 0008 §3) — declared by "${plugin.key}"`);
136
+ const existing = declarations.get(mountId);
137
+ if (existing) {
138
+ if (existing.key === "ui" && plugin.key !== "ui") {
139
+ console.log(`[Compose] mount declaration "${existing.key}:${existing.record.id}" (${mountId}) shadows "${plugin.key}:${record.id}" — core ui wins; shadowed declaration skipped`);
140
+ mountByLayoutId.set(record.id, mountId);
141
+ continue;
142
+ }
143
+ throw new Error(`mount "${mountId}" declared by multiple sources: "${existing.key}" (${existing.record.file}) and "${plugin.key}" (${record.file})`);
144
+ }
145
+ if (skippedByPlugin.get(plugin.key).has(record.id)) continue;
146
+ declarations.set(mountId, {
147
+ key: plugin.key,
148
+ record,
149
+ options: optionsByPlugin.get(plugin.key).get(record.id)
150
+ });
151
+ mountByLayoutId.set(record.id, mountId);
152
+ }
119
153
  }
120
154
  const rootRoute = toAnyRoute(createRootRoute({
121
155
  ...input.rootOptions ? rootAuthoredOptions(input.rootOptions) : {},
@@ -138,8 +172,6 @@ async function constructTree(input) {
138
172
  });
139
173
  mountRoutes.set(mountId, toAnyRoute(route));
140
174
  }
141
- const mountByLayoutId = /* @__PURE__ */ new Map();
142
- for (const [mountId, declaration] of declarations) mountByLayoutId.set(declaration.record.id, mountId);
143
175
  const nav = [];
144
176
  const mountCounts = {};
145
177
  const childrenByParent = /* @__PURE__ */ new Map();
@@ -150,7 +182,8 @@ async function constructTree(input) {
150
182
  list.push(child);
151
183
  childrenByParent.set(parent, list);
152
184
  };
153
- for (const plugin of resolved) {
185
+ for (const plugin of ordered) {
186
+ if (!plugin.manifest?.routes) continue;
154
187
  const optionsById = optionsByPlugin.get(plugin.key);
155
188
  const fullPathById = /* @__PURE__ */ new Map();
156
189
  const mountById = /* @__PURE__ */ new Map();
@@ -184,13 +217,24 @@ async function constructTree(input) {
184
217
  }
185
218
  throw new Error(`route "${record.id}" in "${plugin.key}" must live under a mount`);
186
219
  };
187
- const pending = plugin.manifest.routes.filter((record) => !record.mount);
220
+ const pending = plugin.manifest.routes.filter((record) => !record.mount && !skippedByPlugin.get(plugin.key).has(record.id));
221
+ const shadowedIds = /* @__PURE__ */ new Set();
188
222
  let progressed = true;
189
223
  while (pending.length > 0 && progressed) {
190
224
  progressed = false;
191
225
  for (let i = 0; i < pending.length; i += 1) {
192
226
  const record = pending[i];
193
- if (record.parentId && !byId.has(record.parentId) && !mountByLayoutId.has(record.parentId)) continue;
227
+ const parentId = record.parentId;
228
+ if (parentId) {
229
+ if (shadowedIds.has(parentId)) {
230
+ shadowedIds.add(record.id);
231
+ pending.splice(i, 1);
232
+ i -= 1;
233
+ progressed = true;
234
+ continue;
235
+ }
236
+ if (!byId.has(parentId) && !mountByLayoutId.has(parentId)) continue;
237
+ }
194
238
  pending.splice(i, 1);
195
239
  progressed = true;
196
240
  const options = optionsById.get(record.id);
@@ -200,7 +244,14 @@ async function constructTree(input) {
200
244
  if (!isLayoutRoute) {
201
245
  const siblings = claimedPathsByParent.get(parent.route) ?? /* @__PURE__ */ new Map();
202
246
  const claimant = siblings.get(routePath);
203
- if (claimant) throw new Error(`path collision: "${plugin.key}" route "${record.id}" declares "${routePath}" which is already claimed by "${claimant}" under the same parent`);
247
+ if (claimant) {
248
+ if (plugin.key !== "ui" && claimant.startsWith(`${"ui"}:`)) {
249
+ console.log(`[Compose] route claim "${claimant}" (${routePath}) shadows "${plugin.key}:${record.id}" — core ui wins; shadowed route skipped`);
250
+ shadowedIds.add(record.id);
251
+ break;
252
+ }
253
+ throw new Error(`path collision: "${plugin.key}" route "${record.id}" declares "${routePath}" which is already claimed by "${claimant}" under the same parent`);
254
+ }
204
255
  siblings.set(routePath, `${plugin.key}:${record.id}`);
205
256
  claimedPathsByParent.set(parent.route, siblings);
206
257
  }
@@ -1 +1 @@
1
- {"version":3,"file":"construct.mjs","names":[],"sources":["../../../src/ui/manifest/construct.ts"],"sourcesContent":["import type { AnyRoute } from \"@tanstack/react-router\";\nimport { createRootRoute, createRoute, Outlet, redirect } from \"@tanstack/react-router\";\nimport { digestOf } from \"./descriptor\";\nimport type { PluginManifest, RouteRecord } from \"./manifest-schema\";\nimport { MOUNT_REGISTRY, type MountDef, type MountId, resolveMountSegment } from \"./mount-registry\";\nimport { compareNavItems, type NavDeclaration, type NavItem, type NavManifest } from \"./nav\";\nimport { ROUTE_OPTION_KEYS, type RouteConfigModule, type RouteOptionsBundle } from \"./route-config\";\n\n/**\n * Host-side route-graph construction (ADR 0008 §1): manifests + route\n * configs in → host-built tree out. The host constructs ITS OWN Route\n * objects from validated manifest data via public `createRoute` APIs; no\n * foreign route object is ever mutated. Gates are attached HOST-side from\n * the mount registry — plugins cannot ship a route that escapes its\n * mount's gate because plugins never construct route objects.\n *\n * Mounts are the ONLY cross-source parenting points: a root-level\n * pathless layout file (`_public.tsx`) declares the mount for the whole\n * composition (exactly one declaring source per mount); every other\n * source's routes live under the same `_mount/` directory prefix and\n * parent onto the shared mount route. This module executes inside the\n * core ui's module graph (production MF expose `./compose`, dev source\n * import) so constructed routes are minted by the same react/router\n * instance that renders them.\n */\n\n/**\n * TanStack's Route generics are intentionally invariant — routes built from\n * manifest data can't satisfy them structurally. One named boundary for\n * handing a constructed route to the tree-building APIs.\n */\nfunction toAnyRoute(route: unknown): AnyRoute {\n return route as AnyRoute;\n}\n\nexport interface GateUser {\n id: string;\n name?: string;\n /** Better Auth user role — \"admin\" gates the admin mount. */\n role?: string;\n}\n\nexport interface HostContext {\n user?: GateUser;\n session?: { user?: GateUser };\n}\n\ntype GateFn = (args: {\n context: HostContext;\n location: { pathname: string; searchStr: string };\n}) => void;\n\n/**\n * Gate redirects are absolute: gates attach to root-level mount routes, and\n * relatives would silently shift if a mount ever moved deeper in the tree.\n * The login surface is plugin-owned, never core-known.\n */\nconst LOGIN_TO: string = \"/login\";\nconst HOME_TO: string = \"/\";\n\n/**\n * Gate implementations by mount gate kind. Only `session` and `admin` are\n * implemented (ADR 0008 §3); unimplemented mounts are rejected during mount\n * declaration before any gate lookup, and gates are optional by type — a\n * mount without an entry (or `none`) renders ungated.\n */\nconst GATES: Partial<Record<MountDef[\"gate\"], GateFn>> = {\n session: ({ context, location }) => {\n const user = userOf(context);\n if (!user) {\n throw redirect({\n to: LOGIN_TO,\n search: { redirect: location.pathname + location.searchStr } as never,\n });\n }\n },\n admin: ({ context }) => {\n const user = userOf(context);\n if (!user) throw redirect({ to: LOGIN_TO });\n if (user.role !== \"admin\") throw redirect({ to: HOME_TO });\n },\n};\n\nconst userOf = (context: HostContext | undefined): GateUser | undefined =>\n context?.user ?? context?.session?.user;\n\nexport interface ConstructPluginRef {\n /** composition identity — plugin key; participates in the digest */\n key: string;\n /** deployment identity — the MF container name loadRemote targets */\n mfName: string;\n}\n\nexport interface ResolvedPlugin {\n key: string;\n manifest: PluginManifest;\n routeConfig: RouteConfigModule;\n}\n\nexport interface ConstructInput {\n name: string;\n plugins: ReadonlyArray<ConstructPluginRef>;\n resolve: (ref: ConstructPluginRef) => Promise<ResolvedPlugin>;\n /** core ui `__root` options — component/error/notFound + head baseline */\n rootOptions?: RouteOptionsBundle;\n}\n\nexport interface ConstructedTree {\n rootRoute: AnyRoute;\n routeTree: AnyRoute;\n digest: string;\n manifests: PluginManifest[];\n nav: NavManifest;\n /** constructed route count per mount id */\n mountCounts: Record<string, number>;\n}\n\ninterface MountDeclaration {\n key: string;\n record: RouteRecord;\n options: RouteOptionsBundle;\n}\n\nfunction joinPath(a: string | undefined, b: string | undefined): string {\n const left = a && a !== \"/\" ? a.replace(/\\/+$/, \"\") : \"\";\n const right = b && b !== \"/\" ? b.replace(/^\\/+/, \"\") : \"\";\n if (!left) return right ? `/${right}` : \"/\";\n if (!right) return left;\n return `${left}/${right}`;\n}\n\nconst navOf = (options: RouteOptionsBundle): NavDeclaration | undefined => {\n const nav = options.staticData?.nav;\n if (nav && typeof nav === \"object\" && typeof (nav as NavDeclaration).label === \"string\") {\n return nav as NavDeclaration;\n }\n return undefined;\n};\n\n/** Layout routes (mount or pathless) pass their authored options through,\n * like every other route kind. On mount routes the registry gate composes\n * BEFORE the declaration's own beforeLoad so a source can never escape its\n * mount's gate (ADR 0008 §3), while the declaration's return value still\n * flows to children as route context. */\nfunction layoutBeforeLoad(\n gate: GateFn | undefined,\n declared: RouteOptionsBundle[\"beforeLoad\"],\n): RouteOptionsBundle[\"beforeLoad\"] {\n if (!gate) return declared;\n return (args) => {\n gate(args);\n return declared?.(args);\n };\n}\n\nconst AUTHORED_OPTION_KEYS = ROUTE_OPTION_KEYS.filter(\n (key) => key !== \"beforeLoad\" && key !== \"component\",\n);\n\nfunction authoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const authored: Partial<Record<keyof RouteOptionsBundle, unknown>> = {};\n for (const key of AUTHORED_OPTION_KEYS) {\n if (options[key] !== undefined) authored[key] = options[key];\n }\n return authored as Partial<RouteOptionsBundle>;\n}\n\nfunction rootAuthoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const { params: _params, ...authored } = authoredOptions(options);\n return authored;\n}\n\nexport async function constructTree(input: ConstructInput): Promise<ConstructedTree> {\n const refs = [...input.plugins].sort((a, b) => a.key.localeCompare(b.key));\n const resolved: ResolvedPlugin[] = [];\n for (const ref of refs) {\n resolved.push(await input.resolve(ref));\n }\n\n const optionsByPlugin = new Map<string, Map<string, RouteOptionsBundle>>();\n for (const plugin of resolved) {\n const optionsById = new Map<string, RouteOptionsBundle>();\n await Promise.all(\n plugin.manifest.routes.map(async (record) => {\n const loader = plugin.routeConfig.routeConfigLoaders[record.id];\n if (!loader) {\n throw new Error(`routeConfig for \"${plugin.key}\" has no loader for route \"${record.id}\"`);\n }\n optionsById.set(record.id, await loader());\n }),\n );\n optionsByPlugin.set(plugin.key, optionsById);\n }\n\n const declarations = new Map<MountId, MountDeclaration>();\n for (const plugin of resolved) {\n for (const record of plugin.manifest.routes) {\n if (!record.mount) continue;\n const mountId = record.mount as MountId;\n const def = MOUNT_REGISTRY[mountId];\n if (!def.implemented) {\n throw new Error(\n `mount \"${record.mount}\" is declared vocabulary but unimplemented (ADR 0008 §3) — declared by \"${plugin.key}\"`,\n );\n }\n const existing = declarations.get(mountId);\n if (existing) {\n throw new Error(\n `mount \"${mountId}\" declared by multiple sources: \"${existing.key}\" (${existing.record.file}) and \"${plugin.key}\" (${record.file})`,\n );\n }\n declarations.set(mountId, {\n key: plugin.key,\n record,\n options: optionsByPlugin.get(plugin.key)!.get(record.id)!,\n });\n }\n }\n\n const rootRoute = toAnyRoute(\n createRootRoute({\n ...(input.rootOptions ? rootAuthoredOptions(input.rootOptions) : {}),\n component: input.rootOptions?.component ?? Outlet,\n ...(input.rootOptions?.beforeLoad ? { beforeLoad: input.rootOptions.beforeLoad } : {}),\n }),\n );\n\n const mountRoutes = new Map<MountId, AnyRoute>();\n for (const mountId of Object.keys(MOUNT_REGISTRY) as MountId[]) {\n const declaration = declarations.get(mountId);\n if (!declaration) continue;\n const def = MOUNT_REGISTRY[mountId]!;\n const gate = GATES[def.gate];\n const options = declaration.options;\n const route = createRoute({\n id: `__mount_${mountId}`,\n getParentRoute: () => rootRoute,\n ...(gate || options.beforeLoad\n ? { beforeLoad: layoutBeforeLoad(gate, options.beforeLoad) }\n : {}),\n ...authoredOptions(options),\n component: options.component ?? Outlet,\n });\n mountRoutes.set(mountId, toAnyRoute(route));\n }\n\n const mountByLayoutId = new Map<string, MountId>();\n for (const [mountId, declaration] of declarations) {\n mountByLayoutId.set(declaration.record.id, mountId);\n }\n\n const nav: NavItem[] = [];\n const mountCounts: Record<string, number> = {};\n const childrenByParent = new Map<AnyRoute, AnyRoute[]>();\n /** cross-plugin path-collision guard: claimed paths per parent route */\n const claimedPathsByParent = new Map<AnyRoute, Map<string, string>>();\n const attach = (parent: AnyRoute, child: AnyRoute) => {\n const list = childrenByParent.get(parent) ?? [];\n list.push(child);\n childrenByParent.set(parent, list);\n };\n\n for (const plugin of resolved) {\n const optionsById = optionsByPlugin.get(plugin.key)!;\n const fullPathById = new Map<string, string>();\n const mountById = new Map<string, MountId>();\n const byId = new Map<string, { record: RouteRecord; route: AnyRoute }>();\n\n const parentFor = (\n record: RouteRecord,\n ): { route: AnyRoute; fullPath: string; mount: MountId } => {\n if (record.parentId) {\n const local = byId.get(record.parentId);\n if (local) {\n return {\n route: local.route,\n fullPath: fullPathById.get(record.parentId)!,\n mount: mountById.get(record.parentId)!,\n };\n }\n const mount = mountByLayoutId.get(record.parentId);\n if (mount) return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n throw new Error(\n `unresolvable parent \"${record.parentId}\" for \"${record.id}\" in \"${plugin.key}\" — mounts are the only cross-source parenting points`,\n );\n }\n const firstSegment = record.id.split(\"/\")[0] ?? \"\";\n if (firstSegment.startsWith(\"_\")) {\n const mount = resolveMountSegment(firstSegment.slice(1));\n if (mount && mountRoutes.has(mount))\n return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n if (mount) {\n throw new Error(\n `mount \"${mount}\" is not declared by any source (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(\n `unknown mount \"_${firstSegment.slice(1)}\" (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(`route \"${record.id}\" in \"${plugin.key}\" must live under a mount`);\n };\n\n const pending = plugin.manifest.routes.filter((record) => !record.mount);\n let progressed = true;\n while (pending.length > 0 && progressed) {\n progressed = false;\n for (let i = 0; i < pending.length; i += 1) {\n const record = pending[i]!;\n if (\n record.parentId &&\n !byId.has(record.parentId) &&\n !mountByLayoutId.has(record.parentId)\n ) {\n continue;\n }\n pending.splice(i, 1);\n progressed = true;\n\n const options = optionsById.get(record.id)!;\n const parent = parentFor(record);\n const routePath = record.path;\n const isLayoutRoute = record.type === \"layout\" || routePath === undefined;\n if (!isLayoutRoute) {\n const siblings = claimedPathsByParent.get(parent.route) ?? new Map<string, string>();\n const claimant = siblings.get(routePath);\n if (claimant) {\n throw new Error(\n `path collision: \"${plugin.key}\" route \"${record.id}\" declares \"${routePath}\" which is already claimed by \"${claimant}\" under the same parent`,\n );\n }\n siblings.set(routePath, `${plugin.key}:${record.id}`);\n claimedPathsByParent.set(parent.route, siblings);\n }\n const fullPath = joinPath(parent.fullPath, routePath);\n let route: AnyRoute;\n if (isLayoutRoute) {\n route = createRoute({\n id: `${plugin.key}__${record.id}`,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n } else {\n route = createRoute({\n path: routePath,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n }\n\n byId.set(record.id, { record, route: toAnyRoute(route) });\n fullPathById.set(record.id, fullPath);\n mountById.set(record.id, parent.mount);\n attach(parent.route, toAnyRoute(route));\n mountCounts[parent.mount] = (mountCounts[parent.mount] ?? 0) + 1;\n\n const navDeclaration = navOf(options);\n if (navDeclaration && navDeclaration.hidden !== true) {\n nav.push({\n id: `${plugin.key}:${record.id}`,\n label: navDeclaration.label,\n ...(typeof navDeclaration.icon === \"string\" ? { icon: navDeclaration.icon } : {}),\n ...(typeof navDeclaration.group === \"string\" ? { group: navDeclaration.group } : {}),\n ...(typeof navDeclaration.order === \"number\" ? { order: navDeclaration.order } : {}),\n to: navDeclaration.to ?? fullPath,\n plugin: plugin.key,\n mount: parent.mount,\n });\n }\n break;\n }\n }\n if (pending.length > 0) {\n throw new Error(\n `unresolvable parentage in \"${plugin.key}\": ${pending.map((record) => record.id).join(\", \")}`,\n );\n }\n }\n\n for (const [parent, children] of childrenByParent) {\n (parent as { addChildren: (children: AnyRoute[]) => void }).addChildren(children);\n }\n rootRoute.addChildren([...mountRoutes.values()] as AnyRoute[]);\n nav.sort(compareNavItems);\n\n const digest = await digestOf({\n plugins: refs.map((ref) => ({ key: ref.key, mfName: ref.mfName })),\n manifests: resolved.map((plugin) => plugin.manifest),\n });\n\n return {\n rootRoute,\n routeTree: rootRoute,\n digest,\n manifests: resolved.map((plugin) => plugin.manifest),\n nav: { items: nav },\n mountCounts,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"construct.mjs","names":[],"sources":["../../../src/ui/manifest/construct.ts"],"sourcesContent":["import type { AnyRoute } from \"@tanstack/react-router\";\nimport { createRootRoute, createRoute, Outlet, redirect } from \"@tanstack/react-router\";\nimport { CORE_UI_PLUGIN_KEY } from \"./contract\";\nimport { digestOf } from \"./descriptor\";\nimport type { PluginManifest, RouteRecord } from \"./manifest-schema\";\nimport { MOUNT_REGISTRY, type MountDef, type MountId, resolveMountSegment } from \"./mount-registry\";\nimport { compareNavItems, type NavDeclaration, type NavItem, type NavManifest } from \"./nav\";\nimport { ROUTE_OPTION_KEYS, type RouteConfigModule, type RouteOptionsBundle } from \"./route-config\";\n\n/**\n * Host-side route-graph construction (ADR 0008 §1): manifests + route\n * configs in → host-built tree out. The host constructs ITS OWN Route\n * objects from validated manifest data via public `createRoute` APIs; no\n * foreign route object is ever mutated. Gates are attached HOST-side from\n * the mount registry — plugins cannot ship a route that escapes its\n * mount's gate because plugins never construct route objects.\n *\n * Mounts are the ONLY cross-source parenting points: a root-level\n * pathless layout file (`_public.tsx`) declares the mount for the whole\n * composition (exactly one declaring source per mount); every other\n * source's routes live under the same `_mount/` directory prefix and\n * parent onto the shared mount route. This module executes inside the\n * core ui's module graph (production MF expose `./compose`, dev source\n * import) so constructed routes are minted by the same react/router\n * instance that renders them.\n */\n\n/**\n * TanStack's Route generics are intentionally invariant — routes built from\n * manifest data can't satisfy them structurally. One named boundary for\n * handing a constructed route to the tree-building APIs.\n */\nfunction toAnyRoute(route: unknown): AnyRoute {\n return route as AnyRoute;\n}\n\nexport interface GateUser {\n id: string;\n name?: string;\n /** Better Auth user role — \"admin\" gates the admin mount. */\n role?: string;\n}\n\nexport interface HostContext {\n user?: GateUser;\n session?: { user?: GateUser };\n}\n\ntype GateFn = (args: {\n context: HostContext;\n location: { pathname: string; searchStr: string };\n}) => void;\n\n/**\n * Gate redirects are absolute: gates attach to root-level mount routes, and\n * relatives would silently shift if a mount ever moved deeper in the tree.\n * The login surface is plugin-owned, never core-known.\n */\nconst LOGIN_TO: string = \"/login\";\nconst HOME_TO: string = \"/\";\n\n/**\n * Gate implementations by mount gate kind. Only `session` and `admin` are\n * implemented (ADR 0008 §3); unimplemented mounts are rejected during mount\n * declaration before any gate lookup, and gates are optional by type — a\n * mount without an entry (or `none`) renders ungated.\n */\nconst GATES: Partial<Record<MountDef[\"gate\"], GateFn>> = {\n session: ({ context, location }) => {\n const user = userOf(context);\n if (!user) {\n throw redirect({\n to: LOGIN_TO,\n search: { redirect: location.pathname + location.searchStr } as never,\n });\n }\n },\n admin: ({ context }) => {\n const user = userOf(context);\n if (!user) throw redirect({ to: LOGIN_TO });\n if (user.role !== \"admin\") throw redirect({ to: HOME_TO });\n },\n};\n\nconst userOf = (context: HostContext | undefined): GateUser | undefined =>\n context?.user ?? context?.session?.user;\n\nexport interface ConstructPluginRef {\n /** composition identity — plugin key; participates in the digest */\n key: string;\n /** deployment identity — the MF container name loadRemote targets */\n mfName: string;\n}\n\nexport interface ResolvedPlugin {\n key: string;\n manifest: PluginManifest;\n routeConfig: RouteConfigModule;\n}\n\nexport interface ConstructInput {\n name: string;\n plugins: ReadonlyArray<ConstructPluginRef>;\n resolve: (ref: ConstructPluginRef) => Promise<ResolvedPlugin>;\n /** core ui `__root` options — component/error/notFound + head baseline */\n rootOptions?: RouteOptionsBundle;\n}\n\nexport interface ConstructedTree {\n rootRoute: AnyRoute;\n routeTree: AnyRoute;\n digest: string;\n manifests: PluginManifest[];\n nav: NavManifest;\n /** constructed route count per mount id */\n mountCounts: Record<string, number>;\n}\n\ninterface MountDeclaration {\n key: string;\n record: RouteRecord;\n options: RouteOptionsBundle;\n}\n\nfunction joinPath(a: string | undefined, b: string | undefined): string {\n const left = a && a !== \"/\" ? a.replace(/\\/+$/, \"\") : \"\";\n const right = b && b !== \"/\" ? b.replace(/^\\/+/, \"\") : \"\";\n if (!left) return right ? `/${right}` : \"/\";\n if (!right) return left;\n return `${left}/${right}`;\n}\n\nconst navOf = (options: RouteOptionsBundle): NavDeclaration | undefined => {\n const nav = options.staticData?.nav;\n if (nav && typeof nav === \"object\" && typeof (nav as NavDeclaration).label === \"string\") {\n return nav as NavDeclaration;\n }\n return undefined;\n};\n\n/** Layout routes (mount or pathless) pass their authored options through,\n * like every other route kind. On mount routes the registry gate composes\n * BEFORE the declaration's own beforeLoad so a source can never escape its\n * mount's gate (ADR 0008 §3), while the declaration's return value still\n * flows to children as route context. */\nfunction layoutBeforeLoad(\n gate: GateFn | undefined,\n declared: RouteOptionsBundle[\"beforeLoad\"],\n): RouteOptionsBundle[\"beforeLoad\"] {\n if (!gate) return declared;\n return (args) => {\n gate(args);\n return declared?.(args);\n };\n}\n\nconst AUTHORED_OPTION_KEYS = ROUTE_OPTION_KEYS.filter(\n (key) => key !== \"beforeLoad\" && key !== \"component\",\n);\n\nfunction authoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const authored: Partial<Record<keyof RouteOptionsBundle, unknown>> = {};\n for (const key of AUTHORED_OPTION_KEYS) {\n if (options[key] !== undefined) authored[key] = options[key];\n }\n return authored as Partial<RouteOptionsBundle>;\n}\n\nfunction rootAuthoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const { params: _params, ...authored } = authoredOptions(options);\n return authored;\n}\n\nexport async function constructTree(input: ConstructInput): Promise<ConstructedTree> {\n const refs = [...input.plugins].sort((a, b) => a.key.localeCompare(b.key));\n const resolved: ResolvedPlugin[] = [];\n for (const ref of refs) {\n resolved.push(await input.resolve(ref));\n }\n\n const ordered = [\n ...resolved.filter((plugin) => plugin.key === CORE_UI_PLUGIN_KEY),\n ...resolved.filter((plugin) => plugin.key !== CORE_UI_PLUGIN_KEY),\n ];\n\n const optionsByPlugin = new Map<string, Map<string, RouteOptionsBundle>>();\n const skippedByPlugin = new Map<string, Set<string>>();\n for (const plugin of resolved) {\n // A plugin with no manifest at all contributes no routes — skip it rather\n // than crashing SSR composition (same contract as the missing-loader skip\n // below).\n if (!plugin.manifest?.routes) continue;\n const optionsById = new Map<string, RouteOptionsBundle>();\n const skipped = new Set<string>();\n await Promise.all(\n plugin.manifest.routes.map(async (record) => {\n const loader = plugin.routeConfig.routeConfigLoaders[record.id];\n if (!loader) {\n console.log(\n `[Compose] skipping route \"${record.id}\" in \"${plugin.key}\" — no routeConfig loader`,\n );\n skipped.add(record.id);\n return;\n }\n optionsById.set(record.id, await loader());\n }),\n );\n // cascade: any route whose parent is skipped is also skipped\n let changed = true;\n while (changed) {\n changed = false;\n for (const record of plugin.manifest.routes) {\n if (skipped.has(record.id)) continue;\n if (record.parentId && skipped.has(record.parentId)) {\n skipped.add(record.id);\n changed = true;\n }\n }\n }\n optionsByPlugin.set(plugin.key, optionsById);\n skippedByPlugin.set(plugin.key, skipped);\n }\n\n const declarations = new Map<MountId, MountDeclaration>();\n const mountByLayoutId = new Map<string, MountId>();\n for (const plugin of ordered) {\n if (!plugin.manifest?.routes) continue;\n for (const record of plugin.manifest.routes) {\n if (!record.mount) continue;\n const mountId = record.mount as MountId;\n const def = MOUNT_REGISTRY[mountId];\n if (!def.implemented) {\n throw new Error(\n `mount \"${record.mount}\" is declared vocabulary but unimplemented (ADR 0008 §3) — declared by \"${plugin.key}\"`,\n );\n }\n const existing = declarations.get(mountId);\n if (existing) {\n if (existing.key === CORE_UI_PLUGIN_KEY && plugin.key !== CORE_UI_PLUGIN_KEY) {\n console.log(\n `[Compose] mount declaration \"${existing.key}:${existing.record.id}\" (${mountId}) shadows \"${plugin.key}:${record.id}\" — core ui wins; shadowed declaration skipped`,\n );\n mountByLayoutId.set(record.id, mountId);\n continue;\n }\n throw new Error(\n `mount \"${mountId}\" declared by multiple sources: \"${existing.key}\" (${existing.record.file}) and \"${plugin.key}\" (${record.file})`,\n );\n }\n if (skippedByPlugin.get(plugin.key)!.has(record.id)) continue;\n declarations.set(mountId, {\n key: plugin.key,\n record,\n options: optionsByPlugin.get(plugin.key)!.get(record.id)!,\n });\n mountByLayoutId.set(record.id, mountId);\n }\n }\n\n const rootRoute = toAnyRoute(\n createRootRoute({\n ...(input.rootOptions ? rootAuthoredOptions(input.rootOptions) : {}),\n component: input.rootOptions?.component ?? Outlet,\n ...(input.rootOptions?.beforeLoad ? { beforeLoad: input.rootOptions.beforeLoad } : {}),\n }),\n );\n\n const mountRoutes = new Map<MountId, AnyRoute>();\n for (const mountId of Object.keys(MOUNT_REGISTRY) as MountId[]) {\n const declaration = declarations.get(mountId);\n if (!declaration) continue;\n const def = MOUNT_REGISTRY[mountId]!;\n const gate = GATES[def.gate];\n const options = declaration.options;\n const route = createRoute({\n id: `__mount_${mountId}`,\n getParentRoute: () => rootRoute,\n ...(gate || options.beforeLoad\n ? { beforeLoad: layoutBeforeLoad(gate, options.beforeLoad) }\n : {}),\n ...authoredOptions(options),\n component: options.component ?? Outlet,\n });\n mountRoutes.set(mountId, toAnyRoute(route));\n }\n\n const nav: NavItem[] = [];\n const mountCounts: Record<string, number> = {};\n const childrenByParent = new Map<AnyRoute, AnyRoute[]>();\n /** cross-plugin path-collision guard: claimed paths per parent route */\n const claimedPathsByParent = new Map<AnyRoute, Map<string, string>>();\n const attach = (parent: AnyRoute, child: AnyRoute) => {\n const list = childrenByParent.get(parent) ?? [];\n list.push(child);\n childrenByParent.set(parent, list);\n };\n\n for (const plugin of ordered) {\n if (!plugin.manifest?.routes) continue;\n const optionsById = optionsByPlugin.get(plugin.key)!;\n const fullPathById = new Map<string, string>();\n const mountById = new Map<string, MountId>();\n const byId = new Map<string, { record: RouteRecord; route: AnyRoute }>();\n\n const parentFor = (\n record: RouteRecord,\n ): { route: AnyRoute; fullPath: string; mount: MountId } => {\n if (record.parentId) {\n const local = byId.get(record.parentId);\n if (local) {\n return {\n route: local.route,\n fullPath: fullPathById.get(record.parentId)!,\n mount: mountById.get(record.parentId)!,\n };\n }\n const mount = mountByLayoutId.get(record.parentId);\n if (mount) return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n throw new Error(\n `unresolvable parent \"${record.parentId}\" for \"${record.id}\" in \"${plugin.key}\" — mounts are the only cross-source parenting points`,\n );\n }\n const firstSegment = record.id.split(\"/\")[0] ?? \"\";\n if (firstSegment.startsWith(\"_\")) {\n const mount = resolveMountSegment(firstSegment.slice(1));\n if (mount && mountRoutes.has(mount))\n return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n if (mount) {\n throw new Error(\n `mount \"${mount}\" is not declared by any source (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(\n `unknown mount \"_${firstSegment.slice(1)}\" (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(`route \"${record.id}\" in \"${plugin.key}\" must live under a mount`);\n };\n\n const pending = plugin.manifest.routes.filter(\n (record) => !record.mount && !skippedByPlugin.get(plugin.key)!.has(record.id),\n );\n const shadowedIds = new Set<string>();\n let progressed = true;\n while (pending.length > 0 && progressed) {\n progressed = false;\n for (let i = 0; i < pending.length; i += 1) {\n const record = pending[i]!;\n const parentId = record.parentId;\n if (parentId) {\n if (shadowedIds.has(parentId)) {\n shadowedIds.add(record.id);\n pending.splice(i, 1);\n i -= 1;\n progressed = true;\n continue;\n }\n if (!byId.has(parentId) && !mountByLayoutId.has(parentId)) continue;\n }\n pending.splice(i, 1);\n progressed = true;\n\n const options = optionsById.get(record.id)!;\n const parent = parentFor(record);\n const routePath = record.path;\n const isLayoutRoute = record.type === \"layout\" || routePath === undefined;\n if (!isLayoutRoute) {\n const siblings = claimedPathsByParent.get(parent.route) ?? new Map<string, string>();\n const claimant = siblings.get(routePath);\n if (claimant) {\n if (\n plugin.key !== CORE_UI_PLUGIN_KEY &&\n claimant.startsWith(`${CORE_UI_PLUGIN_KEY}:`)\n ) {\n console.log(\n `[Compose] route claim \"${claimant}\" (${routePath}) shadows \"${plugin.key}:${record.id}\" — core ui wins; shadowed route skipped`,\n );\n shadowedIds.add(record.id);\n break;\n }\n throw new Error(\n `path collision: \"${plugin.key}\" route \"${record.id}\" declares \"${routePath}\" which is already claimed by \"${claimant}\" under the same parent`,\n );\n }\n siblings.set(routePath, `${plugin.key}:${record.id}`);\n claimedPathsByParent.set(parent.route, siblings);\n }\n const fullPath = joinPath(parent.fullPath, routePath);\n let route: AnyRoute;\n if (isLayoutRoute) {\n route = createRoute({\n id: `${plugin.key}__${record.id}`,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n } else {\n route = createRoute({\n path: routePath,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n }\n\n byId.set(record.id, { record, route: toAnyRoute(route) });\n fullPathById.set(record.id, fullPath);\n mountById.set(record.id, parent.mount);\n attach(parent.route, toAnyRoute(route));\n mountCounts[parent.mount] = (mountCounts[parent.mount] ?? 0) + 1;\n\n const navDeclaration = navOf(options);\n if (navDeclaration && navDeclaration.hidden !== true) {\n nav.push({\n id: `${plugin.key}:${record.id}`,\n label: navDeclaration.label,\n ...(typeof navDeclaration.icon === \"string\" ? { icon: navDeclaration.icon } : {}),\n ...(typeof navDeclaration.group === \"string\" ? { group: navDeclaration.group } : {}),\n ...(typeof navDeclaration.order === \"number\" ? { order: navDeclaration.order } : {}),\n to: navDeclaration.to ?? fullPath,\n plugin: plugin.key,\n mount: parent.mount,\n });\n }\n break;\n }\n }\n if (pending.length > 0) {\n throw new Error(\n `unresolvable parentage in \"${plugin.key}\": ${pending.map((record) => record.id).join(\", \")}`,\n );\n }\n }\n\n for (const [parent, children] of childrenByParent) {\n (parent as { addChildren: (children: AnyRoute[]) => void }).addChildren(children);\n }\n rootRoute.addChildren([...mountRoutes.values()] as AnyRoute[]);\n nav.sort(compareNavItems);\n\n const digest = await digestOf({\n plugins: refs.map((ref) => ({ key: ref.key, mfName: ref.mfName })),\n manifests: resolved.map((plugin) => plugin.manifest),\n });\n\n return {\n rootRoute,\n routeTree: rootRoute,\n digest,\n manifests: resolved.map((plugin) => plugin.manifest),\n nav: { items: nav },\n mountCounts,\n };\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
3
+ const require_ui_manifest_contract = require('./contract.cjs');
3
4
  const require_mount_registry = require('./mount-registry.cjs');
4
5
  const require_route_config = require('./route-config.cjs');
5
- const require_ui_manifest_contract = require('./contract.cjs');
6
6
  const require_manifest_schema = require('./manifest-schema.cjs');
7
7
  require('./index.cjs');
8
8
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
+ import { MANIFEST_FILENAME, ROUTE_CONFIG_FILENAME } from "./contract.mjs";
1
2
  import { MOUNT_REGISTRY, resolveMountSegment } from "./mount-registry.mjs";
2
3
  import { ROUTE_OPTION_KEYS } from "./route-config.mjs";
3
- import { MANIFEST_FILENAME, ROUTE_CONFIG_FILENAME } from "./contract.mjs";
4
4
  import { MANIFEST_VERSION, PluginManifestSchema } from "./manifest-schema.mjs";
5
5
  import "./index.mjs";
6
6
  import path from "node:path";
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_identity = require('../../identity.cjs');
3
+ const require_ui_manifest_contract = require('./contract.cjs');
3
4
  const require_mount_registry = require('./mount-registry.cjs');
4
5
  const require_descriptor = require('./descriptor.cjs');
5
6
  const require_nav = require('./nav.cjs');
6
7
  const require_construct = require('./construct.cjs');
7
- const require_ui_manifest_contract = require('./contract.cjs');
8
8
  const require_manifest_schema = require('./manifest-schema.cjs');
9
9
 
10
10
  exports.App = require_descriptor.App;
@@ -1,9 +1,9 @@
1
1
  import { containerName } from "../../identity.mjs";
2
+ import { CORE_UI_PLUGIN_KEY, MANIFEST_FILENAME, PLUGIN_UI_SHARED_EXPOSES, ROUTE_CONFIG_FILENAME, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME } from "./contract.mjs";
2
3
  import { MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, resolveMountSegment } from "./mount-registry.mjs";
3
4
  import { App, Plugin, digestOf, resolveApp } from "./descriptor.mjs";
4
5
  import { compareNavItems } from "./nav.mjs";
5
6
  import { constructTree } from "./construct.mjs";
6
- import { CORE_UI_PLUGIN_KEY, MANIFEST_FILENAME, PLUGIN_UI_SHARED_EXPOSES, ROUTE_CONFIG_FILENAME, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME } from "./contract.mjs";
7
7
  import { ComposePayloadSchema, MANIFEST_VERSION, ManifestSchema, PluginManifestSchema, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, parsePluginManifest } from "./manifest-schema.mjs";
8
8
 
9
9
  export { App, CORE_UI_PLUGIN_KEY, ComposePayloadSchema, MANIFEST_FILENAME, MANIFEST_VERSION, MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, ManifestSchema, PLUGIN_UI_SHARED_EXPOSES, Plugin, PluginManifestSchema, ROUTE_CONFIG_FILENAME, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME, compareNavItems, constructTree, digestOf, parsePluginManifest, resolveApp, resolveMountSegment, containerName as sanitizeContainerName };
@@ -36,7 +36,19 @@ const WorkspaceVersionManifestSchema = zod.z.object({
36
36
  * it and skip re-uploading unchanged files; readers that predate the field
37
37
  * ignore it (zod strips unknown keys). Participates in the version hash.
38
38
  */
39
- files: zod.z.record(zod.z.string(), SRI).optional()
39
+ files: zod.z.record(zod.z.string(), SRI).optional(),
40
+ /**
41
+ * Source archive of the workspace subtree (object path → served-object SRI)
42
+ * published beside the dists when the runtime opts in via
43
+ * `publish.sourcePublish` — lets `bos import` dereference a published
44
+ * config slot to the exact source bytes that build came from, with no
45
+ * GitHub dependency. Readers that predate the field ignore it (zod strips
46
+ * unknown keys). Participates in the version hash.
47
+ */
48
+ source: zod.z.object({
49
+ file: zod.z.string().min(1),
50
+ integrity: SRI
51
+ }).optional()
40
52
  });
41
53
  /**
42
54
  * Compose the manifest with its `version` derived from the canonical content
@@ -46,7 +58,7 @@ const WorkspaceVersionManifestSchema = zod.z.object({
46
58
  * coordinate → a new version (and a new file).
47
59
  */
48
60
  function composeVersionManifest(input) {
49
- const { builtAt, ...hashedInputs } = input;
61
+ const { builtAt: _builtAt, ...hashedInputs } = input;
50
62
  const version = (0, node_crypto.createHash)("sha256").update(stableStringify(hashedInputs)).digest("hex").slice(0, 16);
51
63
  return WorkspaceVersionManifestSchema.parse({
52
64
  ...input,
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.cjs","names":["z","createHash"],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,MAAMA,MAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiCA,MAAE,OAAO;CACrD,SAASA,MAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAASA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAKA,MACF,OAAO;EACN,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiBA,MACd,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAGA,MAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAOA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;AAC5C,CAAC;;;;;;;;AAyBD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,GAAG,iBAAiB;CACrC,MAAM,cAAUC,wBAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
1
+ {"version":3,"file":"version-manifest.cjs","names":["z","createHash"],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n /**\n * Source archive of the workspace subtree (object path → served-object SRI)\n * published beside the dists when the runtime opts in via\n * `publish.sourcePublish` — lets `bos import` dereference a published\n * config slot to the exact source bytes that build came from, with no\n * GitHub dependency. Readers that predate the field ignore it (zod strips\n * unknown keys). Participates in the version hash.\n */\n source: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\n/** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */\nexport type SourceRef = NonNullable<WorkspaceVersionManifest[\"source\"]>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n source?: { file: string; integrity: string };\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,MAAM,MAAMA,MAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiCA,MAAE,OAAO;CACrD,SAASA,MAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAASA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQA,MAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAKA,MACF,OAAO;EACN,OAAOA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiBA,MACd,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQA,MAAE,OAAOA,MAAE,OAAO,GAAGA,MAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAOA,MAAE,OAAOA,MAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;;;;;;;;CAS1C,QAAQA,MACL,OAAO;EACN,MAAMA,MAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;AACd,CAAC;;;;;;;;AA6BD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,cAAUC,wBAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
@@ -17,8 +17,14 @@ declare const WorkspaceVersionManifestSchema: z.ZodObject<{
17
17
  assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
18
18
  shared: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
19
19
  files: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
20
+ source: z.ZodOptional<z.ZodObject<{
21
+ file: z.ZodString;
22
+ integrity: z.ZodString;
23
+ }, z.core.$strip>>;
20
24
  }, z.core.$strip>;
21
25
  type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;
26
+ /** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */
27
+ type SourceRef = NonNullable<WorkspaceVersionManifest["source"]>;
22
28
  interface VersionManifestInput {
23
29
  /** Omit: a per-build timestamp makes the same version id map to different
24
30
  * manifest bytes per machine, breaking the pin's SRI against the image's
@@ -38,6 +44,10 @@ interface VersionManifestInput {
38
44
  assets?: Record<string, string>;
39
45
  shared?: Record<string, string>;
40
46
  files?: Record<string, string>;
47
+ source?: {
48
+ file: string;
49
+ integrity: string;
50
+ };
41
51
  }
42
52
  /**
43
53
  * Compose the manifest with its `version` derived from the canonical content
@@ -48,5 +58,5 @@ interface VersionManifestInput {
48
58
  */
49
59
  declare function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest;
50
60
  //#endregion
51
- export { VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest };
61
+ export { SourceRef, VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest };
52
62
  //# sourceMappingURL=version-manifest.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.d.cts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;cAca,gCAA8B,EAAA;;;;;;;;;;;;;;;;;GA6BzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;UAErC;;;;EAIf;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;;;;;;;;;iBAUM,uBAAuB,OAAO,uBAAuB"}
1
+ {"version":3,"file":"version-manifest.d.cts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;cAca,gCAA8B,EAAA;;;;;;;;;;;;;;;;;;;;;GA2CzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;;KAG1C,YAAY,YAAY;UAEnB;;;;EAIf;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;EACR;IAAW;IAAc;;;;;;;;;;iBAUX,uBAAuB,OAAO,uBAAuB"}
@@ -17,8 +17,14 @@ declare const WorkspaceVersionManifestSchema: z.ZodObject<{
17
17
  assets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
18
18
  shared: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
19
19
  files: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
20
+ source: z.ZodOptional<z.ZodObject<{
21
+ file: z.ZodString;
22
+ integrity: z.ZodString;
23
+ }, z.core.$strip>>;
20
24
  }, z.core.$strip>;
21
25
  type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;
26
+ /** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */
27
+ type SourceRef = NonNullable<WorkspaceVersionManifest["source"]>;
22
28
  interface VersionManifestInput {
23
29
  /** Omit: a per-build timestamp makes the same version id map to different
24
30
  * manifest bytes per machine, breaking the pin's SRI against the image's
@@ -38,6 +44,10 @@ interface VersionManifestInput {
38
44
  assets?: Record<string, string>;
39
45
  shared?: Record<string, string>;
40
46
  files?: Record<string, string>;
47
+ source?: {
48
+ file: string;
49
+ integrity: string;
50
+ };
41
51
  }
42
52
  /**
43
53
  * Compose the manifest with its `version` derived from the canonical content
@@ -48,5 +58,5 @@ interface VersionManifestInput {
48
58
  */
49
59
  declare function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest;
50
60
  //#endregion
51
- export { VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest };
61
+ export { SourceRef, VersionManifestInput, WorkspaceVersionManifest, WorkspaceVersionManifestSchema, composeVersionManifest };
52
62
  //# sourceMappingURL=version-manifest.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.d.mts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;cAca,gCAA8B,EAAA;;;;;;;;;;;;;;;;;GA6BzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;UAErC;;;;EAIf;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;;;;;;;;;iBAUM,uBAAuB,OAAO,uBAAuB"}
1
+ {"version":3,"file":"version-manifest.d.mts","names":[],"sources":["../src/version-manifest.ts"],"mappings":";;cAca,gCAA8B,EAAA;;;;;;;;;;;;;;;;;;;;;GA2CzC,EAAA,KAAA;KACU,2BAA2B,EAAE,aAAa;;KAG1C,YAAY,YAAY;UAEnB;;;;EAIf;EACA;EACA;EACA;EACA;IAAQ;IAAe;;EACvB;IAAoB;IAAc;;EAClC,SAAS;EACT,SAAS;EACT,QAAQ;EACR;IAAW;IAAc;;;;;;;;;;iBAUX,uBAAuB,OAAO,uBAAuB"}
@@ -35,7 +35,19 @@ const WorkspaceVersionManifestSchema = z.object({
35
35
  * it and skip re-uploading unchanged files; readers that predate the field
36
36
  * ignore it (zod strips unknown keys). Participates in the version hash.
37
37
  */
38
- files: z.record(z.string(), SRI).optional()
38
+ files: z.record(z.string(), SRI).optional(),
39
+ /**
40
+ * Source archive of the workspace subtree (object path → served-object SRI)
41
+ * published beside the dists when the runtime opts in via
42
+ * `publish.sourcePublish` — lets `bos import` dereference a published
43
+ * config slot to the exact source bytes that build came from, with no
44
+ * GitHub dependency. Readers that predate the field ignore it (zod strips
45
+ * unknown keys). Participates in the version hash.
46
+ */
47
+ source: z.object({
48
+ file: z.string().min(1),
49
+ integrity: SRI
50
+ }).optional()
39
51
  });
40
52
  /**
41
53
  * Compose the manifest with its `version` derived from the canonical content
@@ -45,7 +57,7 @@ const WorkspaceVersionManifestSchema = z.object({
45
57
  * coordinate → a new version (and a new file).
46
58
  */
47
59
  function composeVersionManifest(input) {
48
- const { builtAt, ...hashedInputs } = input;
60
+ const { builtAt: _builtAt, ...hashedInputs } = input;
49
61
  const version = createHash("sha256").update(stableStringify(hashedInputs)).digest("hex").slice(0, 16);
50
62
  return WorkspaceVersionManifestSchema.parse({
51
63
  ...input,
@@ -1 +1 @@
1
- {"version":3,"file":"version-manifest.mjs","names":[],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiC,EAAE,OAAO;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAK,EACF,OAAO;EACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiB,EACd,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;AAC5C,CAAC;;;;;;;;AAyBD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,GAAG,iBAAiB;CACrC,MAAM,UAAU,WAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
1
+ {"version":3,"file":"version-manifest.mjs","names":[],"sources":["../src/version-manifest.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * WorkspaceVersionManifest — the immutable per-deploy document at\n * `bundles/<account>/<gateway>/<workspace>/versions/<version>.json`\n * (ADR 0020, as amended; atomic-deploys MAP decision 1). The published\n * config's slot pins this document via its `pin` (`manifest` + the\n * manifest's own SRI); every artifact coordinate lives here, so the config\n * stops growing with artifact kinds and old versions stay fully servable.\n */\n\nconst SRI = z.string().regex(/^sha384-[A-Za-z0-9+/=]+$/);\n\nexport const WorkspaceVersionManifestSchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n builtAt: z.string().optional(),\n gitSha: z.string().optional(),\n entry: z.string().min(1),\n entryIntegrity: SRI,\n ssr: z\n .object({\n entry: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n browserManifest: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n /** extra assets (css, icons) — logical name → served-object SRI */\n assets: z.record(z.string(), SRI).optional(),\n /** shared-dependency versions this build was built against */\n shared: z.record(z.string(), z.string()).optional(),\n /**\n * Full per-file digest map of the dist (object path → served-object SRI).\n * Written by `every-plugin`-based deploys so the next deploy can diff against\n * it and skip re-uploading unchanged files; readers that predate the field\n * ignore it (zod strips unknown keys). Participates in the version hash.\n */\n files: z.record(z.string(), SRI).optional(),\n /**\n * Source archive of the workspace subtree (object path → served-object SRI)\n * published beside the dists when the runtime opts in via\n * `publish.sourcePublish` — lets `bos import` dereference a published\n * config slot to the exact source bytes that build came from, with no\n * GitHub dependency. Readers that predate the field ignore it (zod strips\n * unknown keys). Participates in the version hash.\n */\n source: z\n .object({\n file: z.string().min(1),\n integrity: SRI,\n })\n .optional(),\n});\nexport type WorkspaceVersionManifest = z.infer<typeof WorkspaceVersionManifestSchema>;\n\n/** A published-artifact coordinate: object path within the bundle namespace + its served SRI. */\nexport type SourceRef = NonNullable<WorkspaceVersionManifest[\"source\"]>;\n\nexport interface VersionManifestInput {\n /** Omit: a per-build timestamp makes the same version id map to different\n * manifest bytes per machine, breaking the pin's SRI against the image's\n * staged copy and the CDN's immutability for republished versions. */\n builtAt?: string;\n gitSha?: string;\n entry: string;\n entryIntegrity: string;\n ssr?: { entry: string; integrity: string };\n browserManifest?: { file: string; integrity: string };\n assets?: Record<string, string>;\n shared?: Record<string, string>;\n files?: Record<string, string>;\n source?: { file: string; integrity: string };\n}\n\n/**\n * Compose the manifest with its `version` derived from the canonical content\n * of the artifact coordinates alone (key-sorted, `builtAt` excluded) —\n * identical bytes → identical version id regardless of when they were built,\n * so republishing unchanged content is a pointer no-op. Any changed\n * coordinate → a new version (and a new file).\n */\nexport function composeVersionManifest(input: VersionManifestInput): WorkspaceVersionManifest {\n const { builtAt: _builtAt, ...hashedInputs } = input;\n const version = createHash(\"sha256\")\n .update(stableStringify(hashedInputs))\n .digest(\"hex\")\n .slice(0, 16);\n return WorkspaceVersionManifestSchema.parse({ ...input, version });\n}\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .sort()\n .join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAM,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,0BAA0B;AAEvD,MAAa,iCAAiC,EAAE,OAAO;CACrD,SAAS,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB;CAC1C,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,gBAAgB;CAChB,KAAK,EACF,OAAO;EACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACvB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;CACZ,iBAAiB,EACd,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;;CAEZ,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;CAE3C,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;CAOlD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,SAAS;;;;;;;;;CAS1C,QAAQ,EACL,OAAO;EACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EACtB,WAAW;CACb,CAAC,CAAC,CACD,SAAS;AACd,CAAC;;;;;;;;AA6BD,SAAgB,uBAAuB,OAAuD;CAC5F,MAAM,EAAE,SAAS,UAAU,GAAG,iBAAiB;CAC/C,MAAM,UAAU,WAAW,QAAQ,CAAC,CACjC,OAAO,gBAAgB,YAAY,CAAC,CAAC,CACrC,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,EAAE;CACd,OAAO,+BAA+B,MAAM;EAAE,GAAG;EAAO;CAAQ,CAAC;AACnE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAC1E,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,IAAI,OAAO,QAAQ,KAAK,CAAC,CAC7B,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC,CAClC,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAC7D,KAAK,CAAC,CACN,KAAK,GAAG,EAAE;CAEf,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "every-plugin",
3
- "version": "3.0.0-rc.0",
3
+ "version": "3.0.0-rc.10",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,6 +18,14 @@
18
18
  "import": "./dist/index.mjs",
19
19
  "require": "./dist/index.cjs"
20
20
  },
21
+ "./client": {
22
+ "bun": {
23
+ "types": "./src/client.ts",
24
+ "import": "./src/client.ts"
25
+ },
26
+ "types": "./dist/client.d.mts",
27
+ "import": "./dist/client.mjs"
28
+ },
21
29
  "./identity": {
22
30
  "bun": {
23
31
  "types": "./src/identity.ts",
@@ -144,6 +152,14 @@
144
152
  "import": "./dist/build/ui/index.mjs",
145
153
  "require": "./dist/build/ui/index.cjs"
146
154
  },
155
+ "./dev-host": {
156
+ "bun": {
157
+ "types": "./src/dev/host.ts",
158
+ "import": "./src/dev/host.ts"
159
+ },
160
+ "types": "./dist/dev/host.d.mts",
161
+ "import": "./dist/dev/host.mjs"
162
+ },
147
163
  "./dev-serve": {
148
164
  "bun": {
149
165
  "types": "./src/dev/serve.ts",
@@ -222,7 +238,8 @@
222
238
  "@rsbuild/plugin-react": "2.1.0",
223
239
  "@tanstack/router-generator": "1.167.33",
224
240
  "@tanstack/router-plugin": "1.168.35",
225
- "sirv": "3.0.2"
241
+ "sirv": "3.0.2",
242
+ "sonner": ">=2.0.0"
226
243
  },
227
244
  "keywords": [
228
245
  "plugin",
@@ -60,7 +60,8 @@ import { RPCLink } from "@orpc/client/fetch";
60
60
  const HOST = "https://dev.everything.dev";
61
61
 
62
62
  const link = new RPCLink({
63
- url: `${HOST}/api/rpc`,
63
+ origin: HOST,
64
+ url: "/api/rpc",
64
65
  fetch: globalThis.fetch,
65
66
  headers: {
66
67
  "x-api-key": process.env.EVERYTHING_DEV_API_KEY!,
@@ -70,6 +71,8 @@ const link = new RPCLink({
70
71
  const client = createORPCClient(link);
71
72
  ```
72
73
 
74
+ `RPCLink` takes `origin` plus a relative `url` — its `url` option is typed as `StandardUrl` (`/${string}`), so a concatenated absolute URL like `url: \`${HOST}/api/rpc\`` fails typechecking at the link boundary.
75
+
73
76
  ### URL Structure
74
77
 
75
78
  oRPC's RPC protocol encodes the procedure path in the URL segment after the RPC base:
@@ -114,7 +117,8 @@ Two credential mechanisms:
114
117
 
115
118
  ```ts
116
119
  const link = new RPCLink({
117
- url: `${HOST}/api/rpc`,
120
+ origin: HOST,
121
+ url: "/api/rpc",
118
122
  fetch: globalThis.fetch,
119
123
  headers: { "x-api-key": "edk_xxxxxxxxxxxxxxx" },
120
124
  });
@@ -124,7 +128,8 @@ const link = new RPCLink({
124
128
 
125
129
  ```ts
126
130
  const link = new RPCLink({
127
- url: `${HOST}/api/rpc`,
131
+ origin: HOST,
132
+ url: "/api/rpc",
128
133
  fetch: (url, options) => fetch(url, { ...options, credentials: "include" }),
129
134
  });
130
135
  ```
@@ -268,7 +273,8 @@ Pass the key in the `x-api-key` header:
268
273
 
269
274
  ```ts
270
275
  const link = new RPCLink({
271
- url: `${HOST}/api/rpc`,
276
+ origin: HOST,
277
+ url: "/api/rpc",
272
278
  fetch: globalThis.fetch,
273
279
  headers: { "x-api-key": "edk_xxxxxxxxxxxxxxx" },
274
280
  });
@@ -225,12 +225,13 @@ export default defineConfig({
225
225
  - Forgetting `vite-tsconfig-paths` plugin — package subpath imports like `every-plugin/errors` won't resolve
226
226
  - Omitting the `registry` object when calling `createPluginRuntime(...)` — the runtime requires explicit plugin entries
227
227
  - Testing only happy paths — always test error channels (Effect failures, ORPCError throws)
228
+ - Asserting errors by message from a client call — oRPC v2 does not transport undefined-error messages, so message-based assertions are brittle. Match on `error.code` instead: `await expect(client.getById({ id: "x" })).rejects.toMatchObject({ code: "NOT_FOUND" })`.
228
229
 
229
230
  ## The tasks you will actually be given
230
231
 
231
232
  **"Add an integration test for a new route"** — in `plugins/<your-plugin>/tests/integration/plugin.test.ts`, follow the `_template` pattern (`plugins/_template/tests/setup.ts`): call `await getPluginClient({ userId: "user123" })` — it boots `createPluginRuntime` with the registry keyed by the package.json `name`, config from `plugin.dev.ts`, and an HTTP server that maps `x-test-user`/`x-test-session` headers to context. Then drive the route through the client and assert the response. Run with `cd plugins/<your-plugin> && pnpm test` (script `vitest run`, `testTimeout: 30000` in `vitest.config.ts`).
232
233
 
233
- **"Prove a service method fails with the right ORPC code"** — follow `plugins/_template/tests/unit/things-service.test.ts`: build a fresh layer with `DatabaseLive('pglite:<mkdtemp dir>')` and `Layer.succeed(PluginIdTag, "<pluginId>")`, run the failing effect through `Effect.runPromiseExit` + `Cause.squash`, then `expect(error).toBeInstanceOf(ORPCError)` and `expect(error.code).toBe("CONFLICT")` (or `NOT_FOUND`). Clean the temp dir in `afterEach`.
234
+ **"Prove a service method fails with the right ORPC code"** — follow `plugins/_template/tests/unit/things-service.test.ts`: build a fresh layer with `DatabaseLive({ pluginId: "<pluginId>", secrets: { <SLUG>_DATABASE_URL: "pglite:<mkdtemp dir>" } })` (pluginId is a value on the initialize input since ADR 0033 — no `PluginIdTag` provision needed), run the failing effect through `Effect.runPromiseExit` + `Cause.squash`, then `expect(error).toBeInstanceOf(ORPCError)` and `expect(error.code).toBe("CONFLICT")` (or `NOT_FOUND`). Clean the temp dir in `afterEach`. For a pure unit seam, provide a fake with `Layer.succeed(DatabaseTag, fakeDb)` — the plugin-owned tag is the swap seam.
234
235
 
235
236
  **"A test fails at `usePlugin` with a validation error"** — the config comes from `plugin.dev.ts` via `tests/setup.ts` (`TEST_CONFIG`). The runtime validates `variables` and `secrets` against the plugin's zod schemas (`validate-config` / `validate-secrets` → `PluginRuntimeError` with a `zodError` cause). Fix the config in `plugin.dev.ts`, not the test.
236
237