theokit 0.67.0 → 0.69.0

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 (143) hide show
  1. package/README.md +21 -0
  2. package/dist/{actions-virtual-module-YIWQTI6Q.js → actions-virtual-module-DH3N34I6.js} +6 -6
  3. package/dist/{actions-virtual-module-WCHXKH6R.js → actions-virtual-module-OMIFGXK6.js} +4 -4
  4. package/dist/adapters/agent-mount.d.ts +190 -2
  5. package/dist/adapters/agent-mount.js +18 -3
  6. package/dist/adapters/security-headers.d.ts +17 -1
  7. package/dist/adapters/security-headers.js +7 -0
  8. package/dist/adapters/security-headers.js.map +1 -1
  9. package/dist/{agent-C7F5O5V4.js → agent-7RPGAKTY.js} +3 -3
  10. package/dist/{app-typed-client-2ZEGNFJP.js → app-typed-client-C4LRUSIS.js} +6 -6
  11. package/dist/{app-typed-client-CUM2GZ5Y.js → app-typed-client-R6OUJZEE.js} +4 -4
  12. package/dist/assets-map-BMQASQHW.js +70 -0
  13. package/dist/assets-map-BMQASQHW.js.map +1 -0
  14. package/dist/assets-map-CV2YZ3MY.js +69 -0
  15. package/dist/assets-map-CV2YZ3MY.js.map +1 -0
  16. package/dist/{aws-lambda-R76PWR27.js → aws-lambda-OCJM5YGB.js} +35 -23
  17. package/dist/aws-lambda-OCJM5YGB.js.map +1 -0
  18. package/dist/{build-UNBI6VKC.js → build-CGFKWZ73.js} +13 -8
  19. package/dist/build-CGFKWZ73.js.map +1 -0
  20. package/dist/{bun-NPBIAWSU.js → bun-KRUAWYRW.js} +51 -39
  21. package/dist/bun-KRUAWYRW.js.map +1 -0
  22. package/dist/{chunk-GQNBZDYK.js → chunk-2FHNJ7LI.js} +1 -1
  23. package/dist/{chunk-GQNBZDYK.js.map → chunk-2FHNJ7LI.js.map} +1 -1
  24. package/dist/chunk-3T5XLDOH.js +807 -0
  25. package/dist/chunk-3T5XLDOH.js.map +1 -0
  26. package/dist/{chunk-WAYUHS54.js → chunk-425FJKKO.js} +5 -3
  27. package/dist/chunk-425FJKKO.js.map +1 -0
  28. package/dist/chunk-46FM5AFZ.js +44 -0
  29. package/dist/chunk-46FM5AFZ.js.map +1 -0
  30. package/dist/{chunk-ASBXR57P.js → chunk-4WB62S22.js} +7 -4
  31. package/dist/{chunk-ASBXR57P.js.map → chunk-4WB62S22.js.map} +1 -1
  32. package/dist/chunk-BDPD5HEO.js +554 -0
  33. package/dist/chunk-BDPD5HEO.js.map +1 -0
  34. package/dist/{chunk-UETVDZBE.js → chunk-BINRCILF.js} +4 -1
  35. package/dist/{chunk-UETVDZBE.js.map → chunk-BINRCILF.js.map} +1 -1
  36. package/dist/{chunk-EQCSUC65.js → chunk-BTDTHXUI.js} +2 -2
  37. package/dist/chunk-FFGP2LGQ.js +465 -0
  38. package/dist/chunk-FFGP2LGQ.js.map +1 -0
  39. package/dist/{chunk-PRZCPSUE.js → chunk-I2H6KRCQ.js} +29 -14
  40. package/dist/chunk-I2H6KRCQ.js.map +1 -0
  41. package/dist/{chunk-ENRT6DAU.js → chunk-IDWN2OSG.js} +15 -2
  42. package/dist/chunk-IDWN2OSG.js.map +1 -0
  43. package/dist/{chunk-GAJOZJWD.js → chunk-MOGTMGGA.js} +5 -2
  44. package/dist/chunk-MOGTMGGA.js.map +1 -0
  45. package/dist/{chunk-5VLP3GQ2.js → chunk-MS4A7N77.js} +1 -1
  46. package/dist/chunk-MS4A7N77.js.map +1 -0
  47. package/dist/{chunk-YNK7DW5V.js → chunk-QVXMAHZC.js} +55 -551
  48. package/dist/chunk-QVXMAHZC.js.map +1 -0
  49. package/dist/{chunk-B4Z7KIIU.js → chunk-SJ4AW54X.js} +45 -4
  50. package/dist/chunk-SJ4AW54X.js.map +1 -0
  51. package/dist/{chunk-5AGNZXHJ.js → chunk-SRJBH5E5.js} +16 -679
  52. package/dist/chunk-SRJBH5E5.js.map +1 -0
  53. package/dist/{chunk-GQIATJEG.js → chunk-T2LDLVVV.js} +28 -3
  54. package/dist/chunk-T2LDLVVV.js.map +1 -0
  55. package/dist/{chunk-UKMOQWUC.js → chunk-TJPENLGQ.js} +3 -3
  56. package/dist/chunk-V5SSTT3C.js +52 -0
  57. package/dist/chunk-V5SSTT3C.js.map +1 -0
  58. package/dist/chunk-XG6SQUEP.js +27 -0
  59. package/dist/chunk-XG6SQUEP.js.map +1 -0
  60. package/dist/cli/index.js +6 -6
  61. package/dist/client/index.d.ts +20 -0
  62. package/dist/client/index.js.map +1 -1
  63. package/dist/{cloudflare-2WVDBHCC.js → cloudflare-GK4VD3EU.js} +71 -21
  64. package/dist/cloudflare-GK4VD3EU.js.map +1 -0
  65. package/dist/{route-config-B7FaTSby.d.ts → define-middleware-BOHpPOBs.d.ts} +39 -1
  66. package/dist/{deno-deploy-BXOQ45LD.js → deno-deploy-WMNXRVGL.js} +37 -36
  67. package/dist/deno-deploy-WMNXRVGL.js.map +1 -0
  68. package/dist/{dev-GLUZNBNH.js → dev-72ISY3BP.js} +7 -7
  69. package/dist/{index-BdIaxp0c.d.ts → index-DZ_G5iY2.d.ts} +76 -5
  70. package/dist/index.js +7 -6
  71. package/dist/index.js.map +1 -1
  72. package/dist/{internal-api-3JIWUI3V.js → internal-api-CEZSXGRB.js} +4 -4
  73. package/dist/{internal-api-OK7FS2ZF.js → internal-api-XRIBUACB.js} +8 -7
  74. package/dist/match-DaKsL73w.d.ts +28 -0
  75. package/dist/{mcp-IPDFRK4S.js → mcp-S2AOB3RE.js} +3 -3
  76. package/dist/{match-Dw4CNXJH.d.ts → module-loader-DCip7NoL.d.ts} +1 -28
  77. package/dist/{netlify-LPJAPMQ2.js → netlify-VOL54SC7.js} +23 -20
  78. package/dist/netlify-VOL54SC7.js.map +1 -0
  79. package/dist/{observability-bootstrap-H4NSILXD.js → observability-bootstrap-YQKMEYW7.js} +2 -2
  80. package/dist/{preview-5ONGHDKY.js → preview-WA4HORUQ.js} +3 -3
  81. package/dist/registry-DDBXDHJ2.js +23 -0
  82. package/dist/server/cost/index.d.ts +14 -1
  83. package/dist/server/cost/index.js +1 -1
  84. package/dist/server/define/index.d.ts +2 -28
  85. package/dist/server/define/index.js +2 -3
  86. package/dist/server/http/index.d.ts +4 -3
  87. package/dist/server/http/index.js +13 -8
  88. package/dist/server/index.d.ts +6 -4
  89. package/dist/server/index.js +15 -10
  90. package/dist/server/index.js.map +1 -1
  91. package/dist/server/scan/index.d.ts +3 -2
  92. package/dist/{server-boundary-6PYWFYEL.js → server-boundary-S3G4CSNK.js} +4 -4
  93. package/dist/{server-boundary-CNYNVFT5.js → server-boundary-YN5HAICY.js} +6 -6
  94. package/dist/{start-UYBVPTSV.js → start-ZTMQLNJI.js} +39 -15
  95. package/dist/start-ZTMQLNJI.js.map +1 -0
  96. package/dist/{static-SVDWIOXJ.js → static-7OJNB7D2.js} +4 -4
  97. package/dist/{vercel-YUZADKGV.js → vercel-LZTDSDJF.js} +42 -28
  98. package/dist/vercel-LZTDSDJF.js.map +1 -0
  99. package/dist/vite-plugin/index.js +7 -6
  100. package/dist/{vite-plugin-ILWLTOKK.js → vite-plugin-Z7AJG3M2.js} +7 -7
  101. package/package.json +4 -4
  102. package/dist/aws-lambda-R76PWR27.js.map +0 -1
  103. package/dist/build-UNBI6VKC.js.map +0 -1
  104. package/dist/bun-NPBIAWSU.js.map +0 -1
  105. package/dist/chunk-5AGNZXHJ.js.map +0 -1
  106. package/dist/chunk-5VLP3GQ2.js.map +0 -1
  107. package/dist/chunk-6B3CMPE4.js +0 -192
  108. package/dist/chunk-6B3CMPE4.js.map +0 -1
  109. package/dist/chunk-B4Z7KIIU.js.map +0 -1
  110. package/dist/chunk-D4VP2QPR.js +0 -16
  111. package/dist/chunk-D4VP2QPR.js.map +0 -1
  112. package/dist/chunk-ENRT6DAU.js.map +0 -1
  113. package/dist/chunk-GAJOZJWD.js.map +0 -1
  114. package/dist/chunk-GQIATJEG.js.map +0 -1
  115. package/dist/chunk-O55RLSGG.js +0 -127
  116. package/dist/chunk-O55RLSGG.js.map +0 -1
  117. package/dist/chunk-PRZCPSUE.js.map +0 -1
  118. package/dist/chunk-WAYUHS54.js.map +0 -1
  119. package/dist/chunk-YNK7DW5V.js.map +0 -1
  120. package/dist/cloudflare-2WVDBHCC.js.map +0 -1
  121. package/dist/deno-deploy-BXOQ45LD.js.map +0 -1
  122. package/dist/netlify-LPJAPMQ2.js.map +0 -1
  123. package/dist/registry-E5XEUKG6.js +0 -23
  124. package/dist/start-UYBVPTSV.js.map +0 -1
  125. package/dist/vercel-YUZADKGV.js.map +0 -1
  126. /package/dist/{actions-virtual-module-YIWQTI6Q.js.map → actions-virtual-module-DH3N34I6.js.map} +0 -0
  127. /package/dist/{actions-virtual-module-WCHXKH6R.js.map → actions-virtual-module-OMIFGXK6.js.map} +0 -0
  128. /package/dist/{agent-C7F5O5V4.js.map → agent-7RPGAKTY.js.map} +0 -0
  129. /package/dist/{app-typed-client-2ZEGNFJP.js.map → app-typed-client-C4LRUSIS.js.map} +0 -0
  130. /package/dist/{app-typed-client-CUM2GZ5Y.js.map → app-typed-client-R6OUJZEE.js.map} +0 -0
  131. /package/dist/{chunk-EQCSUC65.js.map → chunk-BTDTHXUI.js.map} +0 -0
  132. /package/dist/{chunk-UKMOQWUC.js.map → chunk-TJPENLGQ.js.map} +0 -0
  133. /package/dist/{dev-GLUZNBNH.js.map → dev-72ISY3BP.js.map} +0 -0
  134. /package/dist/{internal-api-3JIWUI3V.js.map → internal-api-CEZSXGRB.js.map} +0 -0
  135. /package/dist/{internal-api-OK7FS2ZF.js.map → internal-api-XRIBUACB.js.map} +0 -0
  136. /package/dist/{mcp-IPDFRK4S.js.map → mcp-S2AOB3RE.js.map} +0 -0
  137. /package/dist/{observability-bootstrap-H4NSILXD.js.map → observability-bootstrap-YQKMEYW7.js.map} +0 -0
  138. /package/dist/{preview-5ONGHDKY.js.map → preview-WA4HORUQ.js.map} +0 -0
  139. /package/dist/{registry-E5XEUKG6.js.map → registry-DDBXDHJ2.js.map} +0 -0
  140. /package/dist/{server-boundary-6PYWFYEL.js.map → server-boundary-S3G4CSNK.js.map} +0 -0
  141. /package/dist/{server-boundary-CNYNVFT5.js.map → server-boundary-YN5HAICY.js.map} +0 -0
  142. /package/dist/{static-SVDWIOXJ.js.map → static-7OJNB7D2.js.map} +0 -0
  143. /package/dist/{vite-plugin-ILWLTOKK.js.map → vite-plugin-Z7AJG3M2.js.map} +0 -0
package/README.md CHANGED
@@ -38,6 +38,27 @@ Node `>= 22.12.0`. Peers: `react`, `react-dom`, `react-router`, `zod`, and — w
38
38
  | `server/ws/chat.ts` | `ws://…/ws/chat` |
39
39
  | `server/middleware/01-cors.ts` | request middleware, in filename order |
40
40
 
41
+ ### `next` is supported on one of the two paths
42
+
43
+ A middleware receives `(request, context, next)`. What `next` does depends on which runner is
44
+ executing it, and the two differ today:
45
+
46
+ | path | `next` |
47
+ |---|---|
48
+ | `executeWebRequest` — the Web runner | runs the rest of the chain **and the route**, so code after `await next()` observes the route's response |
49
+ | `server/middleware/*.ts` — the file-scan runner | **refuses.** It runs BEFORE routing, so there is no downstream to await |
50
+
51
+ The file-scan path throws `MiddlewareNextUnavailableError`, naming the alternative. It used to
52
+ accept the call and continue silently, which meant the code after `await next()` never ran and
53
+ nothing said so.
54
+
55
+ **This is a real limitation, not a bug being hidden.** Closing it means either folding the
56
+ file-scan path so the chain wraps the route, or retiring this contract for a single one; both are
57
+ large and the choice is open — `docs/adr/0003` § *AMENDED 2026-09-21* records what each costs.
58
+
59
+ Write a `server/middleware/*.ts` middleware that returns a `Response` to answer the request, or
60
+ returns nothing to continue. Use the Web path when you need code to run after the route.
61
+
41
62
  ## Authoring surfaces
42
63
 
43
64
  Every surface is a fluent builder whose `.build()` is a compile error while a required link is
@@ -1,12 +1,12 @@
1
- import "./chunk-WAYUHS54.js";
2
- import "./chunk-GAJOZJWD.js";
1
+ import "./chunk-425FJKKO.js";
2
+ import "./chunk-MOGTMGGA.js";
3
3
  import "./chunk-WSOAN4KN.js";
4
4
  import "./chunk-ENSFCKP6.js";
5
- import "./chunk-5AGNZXHJ.js";
5
+ import "./chunk-SRJBH5E5.js";
6
+ import "./chunk-3T5XLDOH.js";
6
7
  import "./chunk-P6OS7652.js";
7
8
  import "./chunk-ZDUO4IYN.js";
8
- import "./chunk-D4VP2QPR.js";
9
- import "./chunk-ENRT6DAU.js";
9
+ import "./chunk-IDWN2OSG.js";
10
10
  import "./chunk-47FFT43U.js";
11
11
  import "./chunk-NTDN54XO.js";
12
12
  import "./chunk-PVNMLZ5G.js";
@@ -234,4 +234,4 @@ export const actions = manifest
234
234
  export {
235
235
  actionsVirtualModule
236
236
  };
237
- //# sourceMappingURL=actions-virtual-module-YIWQTI6Q.js.map
237
+ //# sourceMappingURL=actions-virtual-module-DH3N34I6.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
- import "./chunk-B4Z7KIIU.js";
4
- import "./chunk-UETVDZBE.js";
3
+ import "./chunk-SJ4AW54X.js";
4
+ import "./chunk-BINRCILF.js";
5
5
  import "./chunk-QD5X56I3.js";
6
6
  import "./chunk-WSMLSIS5.js";
7
- import "./chunk-5VLP3GQ2.js";
7
+ import "./chunk-MS4A7N77.js";
8
8
  import "./chunk-C27VRUVN.js";
9
9
  import "./chunk-7UZZHCRN.js";
10
10
  import "./chunk-BCFA6LHL.js";
@@ -218,4 +218,4 @@ export const actions = manifest
218
218
  export {
219
219
  actionsVirtualModule
220
220
  };
221
- //# sourceMappingURL=actions-virtual-module-WCHXKH6R.js.map
221
+ //# sourceMappingURL=actions-virtual-module-OMIFGXK6.js.map
@@ -1,9 +1,14 @@
1
1
  import { a as RouteSubject, R as RoutePolicy } from '../route-policy-BHXQDzBF.js';
2
2
  import { C as CsrfMode } from '../csrf-Dgot9qSM.js';
3
3
  export { r as resolveProvider } from '../provider-resolver-Cc9C3-Rb.js';
4
+ import { A as AgentNode } from '../agent-scan-BZa-pagG.js';
4
5
  export { s as scanAgents } from '../agent-scan-BZa-pagG.js';
5
- import 'node:http';
6
+ import { IncomingMessage, ServerResponse } from 'node:http';
7
+ import { P as PluginRunner } from '../plugin-runner-BVMOSxvb.js';
8
+ import { L as LoadModule } from '../module-loader-DCip7NoL.js';
6
9
  import '../audit-log-BQWM5YLG.js';
10
+ import '../plugin-types-BUVfUQ0V.js';
11
+ import 'vite';
7
12
 
8
13
  /**
9
14
  * ADR 0001, applied to the agent surface (usetheokit/theokit#365).
@@ -132,4 +137,187 @@ interface MountAgentOptions {
132
137
  */
133
138
  declare function mountAgent(mod: unknown, request: Request, apiKey: string | ApiKeyResolver, { source, csrfMode, projectRoot, agentName, policy, resolveSubject, }?: MountAgentOptions): Promise<Response>;
134
139
 
135
- export { mountAgent };
140
+ /** Dependencies the aux dispatcher needs from its caller (dev or prod). */
141
+ interface AuxRouteDeps {
142
+ /** Discovered agents (from `scanAgents`). */
143
+ agents: readonly AgentNode[];
144
+ /** Load an agent module from its file path (dev: vite loader; prod: dynamic import). */
145
+ loadModule: (filePath: string) => Promise<unknown>;
146
+ /** Absolute base URL (`http(s)://host`) for the agent-card endpoint URLs. */
147
+ baseUrl: string;
148
+ /**
149
+ * M34 (#97) — CSRF enforcement mode for the MCP route. `POST /api/agents/<name>/mcp` drives the
150
+ * agent (spends LLM tokens), so a cross-origin POST MUST be rejected in `'strict'` — parity with
151
+ * the agent-run route (`mount-agent.ts:83-91`). Defaults to `'strict'` (safe-by-default); a caller
152
+ * that already gated upstream may pass `'off'`.
153
+ */
154
+ csrfMode?: CsrfMode;
155
+ /**
156
+ * M39 — lazily resolve the provider apiKey. Required only for the thread
157
+ * follow-up route (which drives the agent); resolved on demand so non-agent
158
+ * aux routes (card, approvals, stream) never need a provider key.
159
+ *
160
+ * theokit#328 — it receives the model the agent declares, so the credential matches the provider
161
+ * the agent asked for. It was called with no argument, before the module was even compiled, so
162
+ * an agent declaring `anthropic/…` was handed whichever key env priority found first.
163
+ */
164
+ resolveApiKey?: ApiKeyResolver;
165
+ /**
166
+ * Who is asking (usetheokit/theokit#365). Invoked ONLY from {@link serveMatchedAuxRoute}, and only
167
+ * when the matched path's agent declares a policy — so the application's `createContext` never
168
+ * runs for a url this dispatcher merely declines. {@link matchAgentAuxRoute} is not given it, for
169
+ * the same reason it is not given the request body: deciding must cost nothing.
170
+ */
171
+ resolveSubject?: AgentSubjectResolver;
172
+ }
173
+ /**
174
+ * One aux route, already decided. Carries what the match resolved so the serve half re-derives
175
+ * nothing: the agent node it looked up, and for MCP the module it had to open in order to answer
176
+ * the opt-in question at all.
177
+ */
178
+ type AgentAuxRoute = {
179
+ readonly kind: 'card';
180
+ readonly agent: AgentNode;
181
+ } | {
182
+ readonly kind: 'approvals';
183
+ readonly agent: AgentNode;
184
+ } | {
185
+ readonly kind: 'run-stream';
186
+ readonly agent: AgentNode;
187
+ readonly runId: string;
188
+ } | {
189
+ readonly kind: 'thread-stream';
190
+ readonly agent: AgentNode;
191
+ readonly sessionId: string;
192
+ } | {
193
+ readonly kind: 'thread-message';
194
+ readonly agent: AgentNode;
195
+ readonly sessionId: string;
196
+ } | {
197
+ readonly kind: 'mcp';
198
+ readonly agent: AgentNode;
199
+ readonly mod: unknown;
200
+ };
201
+ /**
202
+ * Does this dispatcher own `urlPath`? Returns the matched route, or `null` to fall through (not an
203
+ * aux path, wrong method, unknown agent, or an agent that did not opt into MCP).
204
+ *
205
+ * theokit#400 — this function is where the "convert only on a path you are about to answer" rule
206
+ * now lives, and it is enforced by the signature rather than by discipline: it is handed a method
207
+ * and a url and has no request to convert. Converting in order to decide is what drained the Node
208
+ * body stream on every path this dispatcher declined, so an ordinary `POST /api/…` file route then
209
+ * waited forever for an `'end'` that had already fired — no status, no timeout, no response.
210
+ *
211
+ * The one branch that does real work here is MCP, which must open the module to read its
212
+ * `export const mcp` opt-in. That is deliberate: a match that could still fall through would hand
213
+ * the caller a request it had already started a lifecycle for, and the span opened for it would
214
+ * either double-count or never close.
215
+ */
216
+ declare function matchAgentAuxRoute(method: string, urlPath: string, deps: AuxRouteDeps): Promise<AgentAuxRoute | null>;
217
+ /**
218
+ * Answer a route {@link matchAgentAuxRoute} already claimed. Always returns a `Response` — every
219
+ * fall-through was decided upstream, which is what lets a caller open a request span before this
220
+ * runs and be sure something will close it.
221
+ */
222
+ declare function serveMatchedAuxRoute(route: AgentAuxRoute, request: Request, deps: AuxRouteDeps): Promise<Response>;
223
+
224
+ /**
225
+ * Who is asking, on the Node dispatch path that serves the agent endpoints
226
+ * (usetheokit/theokit#365).
227
+ *
228
+ * `executeRoute` answers this with `subjectFromContext(ctx)`, where `ctx` is the run context built
229
+ * from the application's `server/context.ts` plus plugin decorations. The agent branches build no
230
+ * such context — which is why `mountAgent`'s `subject` option had nowhere to come from and every
231
+ * caller left it out. This builds the same context from the same two sources, so the agent surface
232
+ * reads identity from the seam the routes already read it from rather than from a second one.
233
+ *
234
+ * ## Why it returns a resolver instead of a subject
235
+ *
236
+ * `tryServeAgentAux` runs for EVERY url, including the ones it does not own. Resolving eagerly
237
+ * there would execute the application's `createContext` twice on every route request — once in the
238
+ * aux branch that falls through, once in the route executor. So callers get a memoized thunk and
239
+ * invoke it only on a path they are about to answer, which is the same discipline theokit#400
240
+ * imposed on `source.toRequest()` for the same dispatcher.
241
+ *
242
+ * ## What `createContext` can read here, and what it cannot (usetheokit/theokit#415)
243
+ *
244
+ * **Headers and cookies: yes. The request body: no.**
245
+ *
246
+ * This used to state the opposite as a MUST — "invoke it before converting the request to a Web
247
+ * `Request`" — and no caller could honour it. The laziness argued for directly above is what makes
248
+ * it unsatisfiable: the invocation necessarily happens INSIDE the handler, and the handler is
249
+ * entered after `serveThroughPluginLifecycle` has already called `source.toRequest()` at the top of
250
+ * its bracket. `incomingMessageToWebRequest` attaches the Node readable as the request body
251
+ * (`body: webStream, duplex: 'half'`), so by the time an application's `createContext` receives
252
+ * that `IncomingMessage`, the stream is consumed.
253
+ *
254
+ * The contract was written for an EAGER resolver and kept when the resolver became lazy. Both
255
+ * decisions were right on their own; the sentence joining them was not, and it pointed callers at
256
+ * something impossible while implying a capability that does not exist.
257
+ *
258
+ * Practically this costs little — identity is overwhelmingly a header or a cookie, and both survive
259
+ * the conversion untouched. What it costs an application that resolves identity from the BODY is an
260
+ * empty read or a wait for an `'end'` that already fired, which is why saying so plainly matters
261
+ * more than the frequency suggests.
262
+ *
263
+ * Restoring body access would mean resolving eagerly — running the application's `createContext`
264
+ * twice on every route request, which is exactly what the laziness exists to prevent — or buffering
265
+ * every agent request body to replay it, which is a cost paid by every caller for a case almost
266
+ * none has.
267
+ */
268
+
269
+ /** What {@link createAgentSubjectResolver} needs in order to build the run context. */
270
+ interface AgentSubjectSources {
271
+ req: IncomingMessage;
272
+ res: ServerResponse;
273
+ loadModule: LoadModule;
274
+ /** The app's `server/` directory. Absent ⇒ there is no `context.ts` to consult. */
275
+ serverDir: string | undefined;
276
+ pluginRunner: PluginRunner | undefined;
277
+ }
278
+ /**
279
+ * Build a memoized resolver for the caller's identity.
280
+ *
281
+ * Decorations are applied ON TOP of the factory's result, matching `executeRoute`: a plugin
282
+ * decoration wins only where `context.ts` did not set the same key.
283
+ *
284
+ * A `createContext` that throws is not swallowed — an application whose identity resolution is
285
+ * broken must not be treated as an anonymous caller, because that reads as a clean refusal and
286
+ * hides the fault. The throw reaches the branch's own error handler and becomes a 500.
287
+ */
288
+ declare function createAgentSubjectResolver(sources: AgentSubjectSources): () => Promise<RouteSubject | null>;
289
+ /**
290
+ * The app's `createContext`, as a host that is not Node sees it.
291
+ *
292
+ * Deliberately NOT `middleware-runner`'s `ContextFactory`, which types its argument as
293
+ * `IncomingMessage`/`ServerResponse`. Those are exactly right for the dev path and wrong here: on
294
+ * every deploy target the pair is what `createWebShim` synthesises over a Web `Request`. Widening
295
+ * to `unknown` says what is actually true at this boundary rather than casting a lie past the
296
+ * type system — the app's own `createContext` is typed by the app, and a generated fragment is the
297
+ * one caller that cannot promise it Node objects.
298
+ */
299
+ type HostContextFactory = (args: {
300
+ request: unknown;
301
+ response: unknown;
302
+ }) => unknown;
303
+ /**
304
+ * The half that CALLS an already-resolved context factory (B-185, ADR 0014).
305
+ *
306
+ * {@link createAgentSubjectResolver} does two things: it LOCATES `context.ts` on a filesystem —
307
+ * `existsSync` plus `loadModule`, inside `createServerContext` — and then CALLS what it found. A
308
+ * deploy target with no filesystem can do the second and not the first, so the generator bakes the
309
+ * module as a static import (the way routes, agents and plugins already are) and hands the factory
310
+ * here directly.
311
+ *
312
+ * Measured before this existed: passing `serverDir: undefined` to the resolver above takes its
313
+ * `produced = {}` branch, never touches `req`/`res`/`loadModule`, and returns
314
+ * `subjectFromContext({})` — which is null for every caller. Satisfying its types would have
315
+ * satisfied nothing.
316
+ *
317
+ * `request`/`response` are whatever the host has. On every deploy target that is the
318
+ * `ShimRequest`/`ShimResponse` pair `createWebShim` synthesises over a Web `Request`, which is the
319
+ * same contract routes on those targets have crossed since they were baked.
320
+ */
321
+ declare function createSubjectResolverFromFactory(createContext: HostContextFactory | undefined, request: unknown, response: unknown, pluginRunner?: PluginRunner): () => Promise<RouteSubject | null>;
322
+
323
+ export { createAgentSubjectResolver, createSubjectResolverFromFactory, matchAgentAuxRoute, mountAgent, serveMatchedAuxRoute };
@@ -1,8 +1,15 @@
1
+ import {
2
+ createAgentSubjectResolver,
3
+ createSubjectResolverFromFactory,
4
+ matchAgentAuxRoute,
5
+ serveMatchedAuxRoute
6
+ } from "../chunk-BDPD5HEO.js";
1
7
  import {
2
8
  mountAgent
3
- } from "../chunk-GAJOZJWD.js";
9
+ } from "../chunk-MOGTMGGA.js";
10
+ import "../chunk-3T5XLDOH.js";
4
11
  import "../chunk-P6OS7652.js";
5
- import "../chunk-ENRT6DAU.js";
12
+ import "../chunk-IDWN2OSG.js";
6
13
  import "../chunk-PVNMLZ5G.js";
7
14
  import "../chunk-ZDCCILMP.js";
8
15
  import "../chunk-CHTAWXC2.js";
@@ -10,6 +17,10 @@ import "../chunk-AVQU5CQF.js";
10
17
  import {
11
18
  scanAgents
12
19
  } from "../chunk-KDP2HURW.js";
20
+ import "../chunk-V6Y2SMRG.js";
21
+ import "../chunk-ACKBVBUI.js";
22
+ import "../chunk-IZ3TIL6K.js";
23
+ import "../chunk-TQEE36VD.js";
13
24
  import {
14
25
  resolveProvider
15
26
  } from "../chunk-ICXXHV4V.js";
@@ -18,8 +29,12 @@ import "../chunk-SOYOYGWY.js";
18
29
  import "../chunk-ZWN3AODA.js";
19
30
  import "../chunk-XTHN6JBX.js";
20
31
  export {
32
+ createAgentSubjectResolver,
33
+ createSubjectResolverFromFactory,
34
+ matchAgentAuxRoute,
21
35
  mountAgent,
22
36
  resolveProvider,
23
- scanAgents
37
+ scanAgents,
38
+ serveMatchedAuxRoute
24
39
  };
25
40
  //# sourceMappingURL=agent-mount.js.map
@@ -57,6 +57,22 @@ export { b as buildSecurityHeaders } from '../security-headers-vris2MQq.js';
57
57
  * (`cli/commands/start/index.ts`). An app with no security block gets the same
58
58
  * baseline deployed as it gets locally.
59
59
  */
60
+ /**
61
+ * The two declarations every deploy entry emits to carry the security baseline.
62
+ *
63
+ * Four adapters wrote these same two lines — `netlify`, `vercel`, `aws-lambda`, `deno-deploy` —
64
+ * each under a comment naming its own host's static-asset boundary. The COMMENT differs and is
65
+ * meant to; the declarations do not.
66
+ *
67
+ * Extracted after SonarCloud's duplication gate failed a PR at 4.0% against a 3% ceiling and named
68
+ * this as the largest repeated block among the changed files. The gate was measuring something
69
+ * real: a literal renderer and a builder call, copied four times, is four places to update when
70
+ * either changes.
71
+ *
72
+ * Returns the lines rather than a joined string, because every caller splices them into an array
73
+ * of emitted lines.
74
+ */
75
+ declare function securityHeadersDeclarations(config: SecurityHeadersConfig | undefined): string[];
60
76
  declare function renderSecurityHeadersConfigLiteral(headers: SecurityHeadersConfig | undefined): string;
61
77
  /**
62
78
  * Put the headers on a response, without overruling the handler.
@@ -129,4 +145,4 @@ interface DeployedSecurityHeaderLimits {
129
145
  */
130
146
  declare function describeDeployedSecurityHeaders(limits: DeployedSecurityHeaderLimits): string;
131
147
 
132
- export { type DeployedSecurityHeaderLimits, describeDeployedSecurityHeaders, renderSecurityHeadersConfigLiteral, withSecurityHeaders };
148
+ export { type DeployedSecurityHeaderLimits, describeDeployedSecurityHeaders, renderSecurityHeadersConfigLiteral, securityHeadersDeclarations, withSecurityHeaders };
@@ -7,6 +7,12 @@ import {
7
7
  import "../chunk-XTHN6JBX.js";
8
8
 
9
9
  // src/adapters/security-headers.ts
10
+ function securityHeadersDeclarations(config) {
11
+ return [
12
+ `const SECURITY_HEADERS_CONFIG = ${renderSecurityHeadersConfigLiteral(config)}`,
13
+ `const SECURITY_HEADERS = buildSecurityHeaders(SECURITY_HEADERS_CONFIG, { production: true })`
14
+ ];
15
+ }
10
16
  function renderSecurityHeadersConfigLiteral(headers) {
11
17
  return JSON.stringify(headers ?? {});
12
18
  }
@@ -59,6 +65,7 @@ export {
59
65
  describeDeployedSecurityHeaders,
60
66
  generateNonce,
61
67
  renderSecurityHeadersConfigLiteral,
68
+ securityHeadersDeclarations,
62
69
  withSecurityHeaders
63
70
  };
64
71
  //# sourceMappingURL=security-headers.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/security-headers.ts"],"mappings":";;;;;;;;;AA8EO,SAAS,mCACd,SACQ;AACR,SAAO,KAAK,UAAU,WAAW,CAAC,CAAC;AACrC;AAyBO,SAAS,oBAAoB,UAAoB,SAA2C;AACjG,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,CAAC,SAAS,QAAQ,IAAI,GAAG,EAAG,UAAS,QAAQ,IAAI,KAAK,KAAK;AAAA,EACjE;AACA,SAAO;AACT;AAgDO,SAAS,gCAAgC,QAA8C;AAC5F,QAAM,UAAU,qBAAqB,OAAO,mBAAmB,CAAC,GAAG,EAAE,YAAY,KAAK,CAAC;AACvF,QAAM,QAAQ,OAAO,KAAK,OAAO;AACjC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,SAAS,OAAO,MAAM;AAAA,EAC/B;AAEA,QAAM,QAAQ;AAAA,IACZ,iDAA4C,OAAO,MAAM,eAAe,MAAM,KAAK,IAAI,CAAC;AAAA,EAC1F;AACA,QAAM,WAAW,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,yBAAyB,CAAC;AAChF,MAAI,YAAY,CAAC,OAAO,YAAY;AAClC,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,oBAAoB,uBAAuB;AACpD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,oBAAoB,sBAAsB;AACnD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/security-headers.ts"],"mappings":";;;;;;;;;AA6FO,SAAS,4BAA4B,QAAqD;AAC/F,SAAO;AAAA,IACL,mCAAmC,mCAAmC,MAAM,CAAC;AAAA,IAC7E;AAAA,EACF;AACF;AAEO,SAAS,mCACd,SACQ;AACR,SAAO,KAAK,UAAU,WAAW,CAAC,CAAC;AACrC;AAyBO,SAAS,oBAAoB,UAAoB,SAA2C;AACjG,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,CAAC,SAAS,QAAQ,IAAI,GAAG,EAAG,UAAS,QAAQ,IAAI,KAAK,KAAK;AAAA,EACjE;AACA,SAAO;AACT;AAgDO,SAAS,gCAAgC,QAA8C;AAC5F,QAAM,UAAU,qBAAqB,OAAO,mBAAmB,CAAC,GAAG,EAAE,YAAY,KAAK,CAAC;AACvF,QAAM,QAAQ,OAAO,KAAK,OAAO;AACjC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,SAAS,OAAO,MAAM;AAAA,EAC/B;AAEA,QAAM,QAAQ;AAAA,IACZ,iDAA4C,OAAO,MAAM,eAAe,MAAM,KAAK,IAAI,CAAC;AAAA,EAC1F;AACA,QAAM,WAAW,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,yBAAyB,CAAC;AAChF,MAAI,YAAY,CAAC,OAAO,YAAY;AAClC,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,oBAAoB,uBAAuB;AACpD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,oBAAoB,sBAAsB;AACnD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
@@ -3,8 +3,8 @@ import "tsx/esm";
3
3
  import {
4
4
  agentCommand,
5
5
  createAgentSsrLoader
6
- } from "./chunk-UKMOQWUC.js";
7
- import "./chunk-UETVDZBE.js";
6
+ } from "./chunk-TJPENLGQ.js";
7
+ import "./chunk-BINRCILF.js";
8
8
  import "./chunk-5NNFO7R7.js";
9
9
  import "./chunk-Z7CJHDLS.js";
10
10
  import "./chunk-3QPVEJ7D.js";
@@ -12,4 +12,4 @@ export {
12
12
  agentCommand,
13
13
  createAgentSsrLoader
14
14
  };
15
- //# sourceMappingURL=agent-C7F5O5V4.js.map
15
+ //# sourceMappingURL=agent-7RPGAKTY.js.map
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  scanControllerModules
3
- } from "./chunk-WAYUHS54.js";
4
- import "./chunk-GAJOZJWD.js";
3
+ } from "./chunk-425FJKKO.js";
4
+ import "./chunk-MOGTMGGA.js";
5
5
  import "./chunk-WSOAN4KN.js";
6
6
  import "./chunk-ENSFCKP6.js";
7
- import "./chunk-5AGNZXHJ.js";
7
+ import "./chunk-SRJBH5E5.js";
8
+ import "./chunk-3T5XLDOH.js";
8
9
  import "./chunk-P6OS7652.js";
9
10
  import "./chunk-ZDUO4IYN.js";
10
- import "./chunk-D4VP2QPR.js";
11
- import "./chunk-ENRT6DAU.js";
11
+ import "./chunk-IDWN2OSG.js";
12
12
  import "./chunk-47FFT43U.js";
13
13
  import "./chunk-NTDN54XO.js";
14
14
  import "./chunk-PVNMLZ5G.js";
@@ -427,4 +427,4 @@ export {
427
427
  generateClientDts,
428
428
  shouldReemitForFile
429
429
  };
430
- //# sourceMappingURL=app-typed-client-2ZEGNFJP.js.map
430
+ //# sourceMappingURL=app-typed-client-C4LRUSIS.js.map
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
- import "./chunk-B4Z7KIIU.js";
4
- import "./chunk-UETVDZBE.js";
3
+ import "./chunk-SJ4AW54X.js";
4
+ import "./chunk-BINRCILF.js";
5
5
  import {
6
6
  scanControllerModules
7
7
  } from "./chunk-QD5X56I3.js";
8
8
  import "./chunk-WSMLSIS5.js";
9
- import "./chunk-5VLP3GQ2.js";
9
+ import "./chunk-MS4A7N77.js";
10
10
  import "./chunk-C27VRUVN.js";
11
11
  import "./chunk-7UZZHCRN.js";
12
12
  import {
@@ -411,4 +411,4 @@ export {
411
411
  generateClientDts,
412
412
  shouldReemitForFile
413
413
  };
414
- //# sourceMappingURL=app-typed-client-CUM2GZ5Y.js.map
414
+ //# sourceMappingURL=app-typed-client-R6OUJZEE.js.map
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+
4
+ // src/vite-plugin/assets-map.ts
5
+ var PAGE_FILE = /[/\\]page\.(tsx|ts|jsx|js)$/;
6
+ function routePathForPageFile(appDir, moduleId) {
7
+ const normalised = moduleId.replace(/\\/g, "/");
8
+ const root = appDir.replace(/\\/g, "/").replace(/\/$/, "");
9
+ if (!normalised.startsWith(`${root}/`)) return void 0;
10
+ if (!PAGE_FILE.test(normalised)) return void 0;
11
+ const relative = normalised.slice(root.length).replace(PAGE_FILE, "");
12
+ return relative === "" ? "/" : relative;
13
+ }
14
+ function staticClosure(bundle, start) {
15
+ const seen = /* @__PURE__ */ new Set();
16
+ const queue = [start];
17
+ while (queue.length > 0) {
18
+ const name = queue.pop();
19
+ if (name === void 0 || seen.has(name)) continue;
20
+ seen.add(name);
21
+ for (const next of bundle[name]?.imports ?? []) queue.push(next);
22
+ }
23
+ return seen;
24
+ }
25
+ function buildAssetsMap(bundle, appDir) {
26
+ const chunks = Object.entries(bundle).filter(
27
+ (entry) => entry[1]?.type === "chunk"
28
+ );
29
+ const alreadyLoaded = /* @__PURE__ */ new Set();
30
+ for (const [name, chunk] of chunks) {
31
+ if (chunk.isEntry === true) {
32
+ for (const reachable of staticClosure(bundle, name)) alreadyLoaded.add(reachable);
33
+ }
34
+ }
35
+ const map = {};
36
+ for (const [name, chunk] of chunks) {
37
+ const moduleId = chunk.facadeModuleId;
38
+ if (moduleId === void 0 || moduleId === null) continue;
39
+ const route = routePathForPageFile(appDir, moduleId);
40
+ if (route === void 0) continue;
41
+ map[route] = [...staticClosure(bundle, name)].filter((f) => !alreadyLoaded.has(f)).sort((a, b) => a.localeCompare(b));
42
+ }
43
+ return map;
44
+ }
45
+ function assetsMapPlugin(opts) {
46
+ let isSsrPass = false;
47
+ return {
48
+ name: "theokit:assets-map",
49
+ apply: "build",
50
+ configResolved(config) {
51
+ isSsrPass = Boolean(config.build.ssr);
52
+ },
53
+ generateBundle(_options, bundle) {
54
+ if (isSsrPass) return;
55
+ const map = buildAssetsMap(bundle, opts.appDir);
56
+ this.emitFile({
57
+ type: "asset",
58
+ fileName: "assets-map.json",
59
+ source: `${JSON.stringify(map, null, 2)}
60
+ `
61
+ });
62
+ }
63
+ };
64
+ }
65
+ export {
66
+ assetsMapPlugin,
67
+ buildAssetsMap,
68
+ routePathForPageFile
69
+ };
70
+ //# sourceMappingURL=assets-map-BMQASQHW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin/assets-map.ts"],"sourcesContent":["/**\n * Emits `assets-map.json` — the route-to-chunks relation a document needs in order to preload\n * what the current route will import, instead of discovering it one round trip later.\n *\n * Chunk names exist only AFTER bundling, which is why this is a `generateBundle` hook and not a\n * post-build script reading `.vite/manifest.json`: Rollup hands the bundle in as an argument, and\n * the pre-bundle manifests must keep emitting before Vite so a failing build still leaves\n * diagnostics (B-035 ADR-1).\n *\n * `apply: 'build'` is not decoration. The plugin set this joins is shared by `theokit build`,\n * `theo dev` and `theo agent`; removing the plugin from the two serve chains is stronger than\n * relying on the hook never being reached there. The repository already uses the mirror of this in\n * `services-typed-client.ts` (`apply: 'serve'`, \"dev-only; never runs in build\").\n *\n * The SSR pass is skipped by reading the RESOLVED `build.ssr`, never a factory option: the adapter\n * calls the plugin factory twice (`adapters/node.ts:57` client, `:71` ssr) and passes `ssr: true`\n * to BOTH when the project enables SSR, so the option in the signature cannot tell the passes\n * apart. Emitting on the server pass would produce a second, unread `assets-map.json` under\n * `.theokit/server/` — harmless, because `this.emitFile` can only write inside its own build's\n * `outDir`, and deliberately avoided anyway so no future reader picks the wrong one.\n */\nimport type { Plugin } from 'vite'\n\n/** A route path mapped to the chunk file names it needs beyond what the entry already loads. */\nexport type AssetsMap = Record<string, string[]>\n\n/** Rollup's `generateBundle` bundle value, narrowed to the fields this hook reads. */\n/**\n * Exported so a fixture can be typed as one. A test that builds a chunk shape by hand and cannot\n * name the type is a test the compiler checks against `Record<string, unknown>` — which is what\n * silently erased every `fileName` a fixture passed until the root `tsc` reported it.\n */\nexport interface BundleChunk {\n type: string\n isEntry?: boolean\n fileName: string\n facadeModuleId?: string | null\n imports?: string[]\n /**\n * Declared and deliberately NOT walked. A dynamic import is by definition what the browser fetches\n * later; preloading it would defeat the split that made it dynamic.\n *\n * Naming it here is what makes the test asserting that meaningful. The field was absent from this\n * type while a fixture passed it, so the case named \"does not follow dynamic imports\" passed\n * because the walker ignores unknown keys — proving the behaviour by accident rather than by\n * design. The root `tsc` reported it once the fixture helper stopped erasing its overrides.\n */\n dynamicImports?: string[]\n}\n\n/**\n * A bundle as this hook reads it. The value is `| undefined` ON PURPOSE: an `imports` entry may\n * name something the bundle does not contain (an external), and a lookup that cannot miss would\n * make the optional chain in `staticClosure` look redundant to a type-based lint while removing\n * it would crash at runtime. The type is widened to match reality rather than the guard narrowed\n * to match the type.\n */\ntype BundleLike = Record<string, BundleChunk | undefined>\n\nconst PAGE_FILE = /[/\\\\]page\\.(tsx|ts|jsx|js)$/\n\n/**\n * Derive the route path a page file serves. `<appDir>/about/page.tsx` -> `/about`;\n * `<appDir>/page.tsx` -> `/`. Returns undefined when the file is not a page under `appDir`.\n */\nexport function routePathForPageFile(appDir: string, moduleId: string): string | undefined {\n const normalised = moduleId.replace(/\\\\/g, '/')\n const root = appDir.replace(/\\\\/g, '/').replace(/\\/$/, '')\n if (!normalised.startsWith(`${root}/`)) return undefined\n if (!PAGE_FILE.test(normalised)) return undefined\n const relative = normalised.slice(root.length).replace(PAGE_FILE, '')\n return relative === '' ? '/' : relative\n}\n\n/**\n * Every chunk reachable from `start` through STATIC imports, `start` included. Dynamic imports are\n * deliberately excluded: they are what the browser fetches later, by definition, and preloading\n * them would defeat the split that produced them.\n */\nfunction staticClosure(bundle: BundleLike, start: string): Set<string> {\n const seen = new Set<string>()\n const queue = [start]\n while (queue.length > 0) {\n const name = queue.pop()\n if (name === undefined || seen.has(name)) continue\n seen.add(name)\n for (const next of bundle[name]?.imports ?? []) queue.push(next)\n }\n return seen\n}\n\n/**\n * Compute the map from a Rollup bundle. Exported for unit testing without running a build —\n * the hook below is a thin wrapper so this function carries the behaviour.\n */\nexport function buildAssetsMap(bundle: BundleLike, appDir: string): AssetsMap {\n const chunks = Object.entries(bundle).filter(\n (entry): entry is [string, BundleChunk] => entry[1]?.type === 'chunk',\n )\n\n // What the document already loads. A route needing only these needs no preload at all, which is\n // the case an over-eager implementation gets wrong and the empty array records honestly.\n const alreadyLoaded = new Set<string>()\n for (const [name, chunk] of chunks) {\n if (chunk.isEntry === true) {\n for (const reachable of staticClosure(bundle, name)) alreadyLoaded.add(reachable)\n }\n }\n\n const map: AssetsMap = {}\n for (const [name, chunk] of chunks) {\n const moduleId = chunk.facadeModuleId\n if (moduleId === undefined || moduleId === null) continue\n const route = routePathForPageFile(appDir, moduleId)\n if (route === undefined) continue\n map[route] = [...staticClosure(bundle, name)]\n .filter((f) => !alreadyLoaded.has(f))\n .sort((a, b) => a.localeCompare(b))\n }\n return map\n}\n\n/** The plugin. See the module docblock for why `apply` and the SSR skip are what they are. */\nexport function assetsMapPlugin(opts: { appDir: string }): Plugin {\n let isSsrPass = false\n return {\n name: 'theokit:assets-map',\n apply: 'build',\n configResolved(config) {\n // The RESOLVED flag, per build pass — not the factory option, which is `true` in both.\n isSsrPass = Boolean(config.build.ssr)\n },\n generateBundle(_options, bundle) {\n if (isSsrPass) return\n const map = buildAssetsMap(bundle, opts.appDir)\n this.emitFile({\n type: 'asset',\n fileName: 'assets-map.json',\n source: `${JSON.stringify(map, null, 2)}\\n`,\n })\n },\n }\n}\n"],"mappings":";;;;AA2DA,IAAM,YAAY;AAMX,SAAS,qBAAqB,QAAgB,UAAsC;AACzF,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,QAAM,OAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACzD,MAAI,CAAC,WAAW,WAAW,GAAG,IAAI,GAAG,EAAG,QAAO;AAC/C,MAAI,CAAC,UAAU,KAAK,UAAU,EAAG,QAAO;AACxC,QAAM,WAAW,WAAW,MAAM,KAAK,MAAM,EAAE,QAAQ,WAAW,EAAE;AACpE,SAAO,aAAa,KAAK,MAAM;AACjC;AAOA,SAAS,cAAc,QAAoB,OAA4B;AACrE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,KAAK;AACpB,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,OAAO,MAAM,IAAI;AACvB,QAAI,SAAS,UAAa,KAAK,IAAI,IAAI,EAAG;AAC1C,SAAK,IAAI,IAAI;AACb,eAAW,QAAQ,OAAO,IAAI,GAAG,WAAW,CAAC,EAAG,OAAM,KAAK,IAAI;AAAA,EACjE;AACA,SAAO;AACT;AAMO,SAAS,eAAe,QAAoB,QAA2B;AAC5E,QAAM,SAAS,OAAO,QAAQ,MAAM,EAAE;AAAA,IACpC,CAAC,UAA0C,MAAM,CAAC,GAAG,SAAS;AAAA,EAChE;AAIA,QAAM,gBAAgB,oBAAI,IAAY;AACtC,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,QAAI,MAAM,YAAY,MAAM;AAC1B,iBAAW,aAAa,cAAc,QAAQ,IAAI,EAAG,eAAc,IAAI,SAAS;AAAA,IAClF;AAAA,EACF;AAEA,QAAM,MAAiB,CAAC;AACxB,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,UAAM,WAAW,MAAM;AACvB,QAAI,aAAa,UAAa,aAAa,KAAM;AACjD,UAAM,QAAQ,qBAAqB,QAAQ,QAAQ;AACnD,QAAI,UAAU,OAAW;AACzB,QAAI,KAAK,IAAI,CAAC,GAAG,cAAc,QAAQ,IAAI,CAAC,EACzC,OAAO,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC,EACnC,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAkC;AAChE,MAAI,YAAY;AAChB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,eAAe,QAAQ;AAErB,kBAAY,QAAQ,OAAO,MAAM,GAAG;AAAA,IACtC;AAAA,IACA,eAAe,UAAU,QAAQ;AAC/B,UAAI,UAAW;AACf,YAAM,MAAM,eAAe,QAAQ,KAAK,MAAM;AAC9C,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,69 @@
1
+ import "./chunk-XTHN6JBX.js";
2
+
3
+ // src/vite-plugin/assets-map.ts
4
+ var PAGE_FILE = /[/\\]page\.(tsx|ts|jsx|js)$/;
5
+ function routePathForPageFile(appDir, moduleId) {
6
+ const normalised = moduleId.replace(/\\/g, "/");
7
+ const root = appDir.replace(/\\/g, "/").replace(/\/$/, "");
8
+ if (!normalised.startsWith(`${root}/`)) return void 0;
9
+ if (!PAGE_FILE.test(normalised)) return void 0;
10
+ const relative = normalised.slice(root.length).replace(PAGE_FILE, "");
11
+ return relative === "" ? "/" : relative;
12
+ }
13
+ function staticClosure(bundle, start) {
14
+ const seen = /* @__PURE__ */ new Set();
15
+ const queue = [start];
16
+ while (queue.length > 0) {
17
+ const name = queue.pop();
18
+ if (name === void 0 || seen.has(name)) continue;
19
+ seen.add(name);
20
+ for (const next of bundle[name]?.imports ?? []) queue.push(next);
21
+ }
22
+ return seen;
23
+ }
24
+ function buildAssetsMap(bundle, appDir) {
25
+ const chunks = Object.entries(bundle).filter(
26
+ (entry) => entry[1]?.type === "chunk"
27
+ );
28
+ const alreadyLoaded = /* @__PURE__ */ new Set();
29
+ for (const [name, chunk] of chunks) {
30
+ if (chunk.isEntry === true) {
31
+ for (const reachable of staticClosure(bundle, name)) alreadyLoaded.add(reachable);
32
+ }
33
+ }
34
+ const map = {};
35
+ for (const [name, chunk] of chunks) {
36
+ const moduleId = chunk.facadeModuleId;
37
+ if (moduleId === void 0 || moduleId === null) continue;
38
+ const route = routePathForPageFile(appDir, moduleId);
39
+ if (route === void 0) continue;
40
+ map[route] = [...staticClosure(bundle, name)].filter((f) => !alreadyLoaded.has(f)).sort((a, b) => a.localeCompare(b));
41
+ }
42
+ return map;
43
+ }
44
+ function assetsMapPlugin(opts) {
45
+ let isSsrPass = false;
46
+ return {
47
+ name: "theokit:assets-map",
48
+ apply: "build",
49
+ configResolved(config) {
50
+ isSsrPass = Boolean(config.build.ssr);
51
+ },
52
+ generateBundle(_options, bundle) {
53
+ if (isSsrPass) return;
54
+ const map = buildAssetsMap(bundle, opts.appDir);
55
+ this.emitFile({
56
+ type: "asset",
57
+ fileName: "assets-map.json",
58
+ source: `${JSON.stringify(map, null, 2)}
59
+ `
60
+ });
61
+ }
62
+ };
63
+ }
64
+ export {
65
+ assetsMapPlugin,
66
+ buildAssetsMap,
67
+ routePathForPageFile
68
+ };
69
+ //# sourceMappingURL=assets-map-CV2YZ3MY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin/assets-map.ts"],"mappings":";;;AA2DA,IAAM,YAAY;AAMX,SAAS,qBAAqB,QAAgB,UAAsC;AACzF,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,QAAM,OAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACzD,MAAI,CAAC,WAAW,WAAW,GAAG,IAAI,GAAG,EAAG,QAAO;AAC/C,MAAI,CAAC,UAAU,KAAK,UAAU,EAAG,QAAO;AACxC,QAAM,WAAW,WAAW,MAAM,KAAK,MAAM,EAAE,QAAQ,WAAW,EAAE;AACpE,SAAO,aAAa,KAAK,MAAM;AACjC;AAOA,SAAS,cAAc,QAAoB,OAA4B;AACrE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,KAAK;AACpB,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,OAAO,MAAM,IAAI;AACvB,QAAI,SAAS,UAAa,KAAK,IAAI,IAAI,EAAG;AAC1C,SAAK,IAAI,IAAI;AACb,eAAW,QAAQ,OAAO,IAAI,GAAG,WAAW,CAAC,EAAG,OAAM,KAAK,IAAI;AAAA,EACjE;AACA,SAAO;AACT;AAMO,SAAS,eAAe,QAAoB,QAA2B;AAC5E,QAAM,SAAS,OAAO,QAAQ,MAAM,EAAE;AAAA,IACpC,CAAC,UAA0C,MAAM,CAAC,GAAG,SAAS;AAAA,EAChE;AAIA,QAAM,gBAAgB,oBAAI,IAAY;AACtC,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,QAAI,MAAM,YAAY,MAAM;AAC1B,iBAAW,aAAa,cAAc,QAAQ,IAAI,EAAG,eAAc,IAAI,SAAS;AAAA,IAClF;AAAA,EACF;AAEA,QAAM,MAAiB,CAAC;AACxB,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,UAAM,WAAW,MAAM;AACvB,QAAI,aAAa,UAAa,aAAa,KAAM;AACjD,UAAM,QAAQ,qBAAqB,QAAQ,QAAQ;AACnD,QAAI,UAAU,OAAW;AACzB,QAAI,KAAK,IAAI,CAAC,GAAG,cAAc,QAAQ,IAAI,CAAC,EACzC,OAAO,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC,EACnC,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAkC;AAChE,MAAI,YAAY;AAChB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,eAAe,QAAQ;AAErB,kBAAY,QAAQ,OAAO,MAAM,GAAG;AAAA,IACtC;AAAA,IACA,eAAe,UAAU,QAAQ;AAC/B,UAAI,UAAW;AACf,YAAM,MAAM,eAAe,QAAQ,KAAK,MAAM;AAC9C,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}