theokit 0.71.0 → 0.72.1

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 (139) hide show
  1. package/dist/{actions-virtual-module-2SM6YMTM.js → actions-virtual-module-EAKYEMGF.js} +4 -4
  2. package/dist/{actions-virtual-module-5LRBP6YK.js → actions-virtual-module-OHENNMQG.js} +6 -6
  3. package/dist/adapters/agent-mount.js +3 -3
  4. package/dist/{agent-4FXJXZFD.js → agent-VJTZ4DQ7.js} +2 -2
  5. package/dist/{app-typed-client-LIIPHLO6.js → app-typed-client-ILLQYTXP.js} +6 -6
  6. package/dist/{app-typed-client-JFPV4YLS.js → app-typed-client-LHH77QDB.js} +4 -4
  7. package/dist/{aws-lambda-MUGK4WNZ.js → aws-lambda-7AZNJGWU.js} +6 -4
  8. package/dist/{aws-lambda-MUGK4WNZ.js.map → aws-lambda-7AZNJGWU.js.map} +1 -1
  9. package/dist/{build-KCIH3HI6.js → build-23QF5FNQ.js} +7 -7
  10. package/dist/build-23QF5FNQ.js.map +1 -0
  11. package/dist/{bun-JWZ2UOLJ.js → bun-NKVHOFSJ.js} +6 -4
  12. package/dist/{bun-JWZ2UOLJ.js.map → bun-NKVHOFSJ.js.map} +1 -1
  13. package/dist/{chunk-KOG5K26V.js → chunk-2Z7TANA4.js} +2 -2
  14. package/dist/{chunk-V6VARIR3.js → chunk-4EQEJ4PH.js} +9 -9
  15. package/dist/{chunk-3NAKOWTX.js → chunk-6U67SO5U.js} +3 -3
  16. package/dist/{chunk-IGCOTLQI.js → chunk-C5CAL3DX.js} +136 -5
  17. package/dist/{chunk-IGCOTLQI.js.map → chunk-C5CAL3DX.js.map} +1 -1
  18. package/dist/{chunk-YWWMYEAC.js → chunk-CLSYYO5F.js} +2 -2
  19. package/dist/{chunk-OSBGZYXZ.js → chunk-D2R5SJEL.js} +16 -2
  20. package/dist/chunk-D2R5SJEL.js.map +1 -0
  21. package/dist/{chunk-JMN4COMK.js → chunk-D7STTNQN.js} +3 -3
  22. package/dist/{chunk-DSPEN7ZJ.js → chunk-DKX7LF5Q.js} +4 -4
  23. package/dist/{chunk-5XXXJ6DT.js → chunk-E2RC367T.js} +47 -8
  24. package/dist/chunk-E2RC367T.js.map +1 -0
  25. package/dist/{chunk-3DGIANAN.js → chunk-FJEKQ4FT.js} +1 -1
  26. package/dist/{chunk-3DGIANAN.js.map → chunk-FJEKQ4FT.js.map} +1 -1
  27. package/dist/{chunk-KI4DOTN4.js → chunk-G7U7FQVC.js} +3 -1
  28. package/dist/chunk-G7U7FQVC.js.map +1 -0
  29. package/dist/{chunk-6AGXDYVE.js → chunk-IB5VIXLR.js} +2 -2
  30. package/dist/{chunk-Q4NSUGMS.js → chunk-JJXBRY2U.js} +74 -6
  31. package/dist/chunk-JJXBRY2U.js.map +1 -0
  32. package/dist/{chunk-PGDBSK2L.js → chunk-L6UHMWXU.js} +2 -2
  33. package/dist/{chunk-MS4A7N77.js → chunk-PEL3MJLM.js} +2 -2
  34. package/dist/{chunk-TNI4MARH.js → chunk-SFGGRC35.js} +23 -6
  35. package/dist/chunk-SFGGRC35.js.map +1 -0
  36. package/dist/{chunk-4D4YVXHR.js → chunk-SYIQV4UH.js} +29 -2
  37. package/dist/{chunk-4D4YVXHR.js.map → chunk-SYIQV4UH.js.map} +1 -1
  38. package/dist/{chunk-FFS7IRZF.js → chunk-SZWGXHY3.js} +2 -2
  39. package/dist/{chunk-SX7ZAHNE.js → chunk-WBB3MEEN.js} +16 -2
  40. package/dist/chunk-WBB3MEEN.js.map +1 -0
  41. package/dist/{chunk-TMQ52AZR.js → chunk-X2LFNDJQ.js} +2 -2
  42. package/dist/{chunk-FWEXQ2BR.js → chunk-XLSJWKSP.js} +11 -11
  43. package/dist/{chunk-NZDGV4YJ.js → chunk-YCF3N4JS.js} +2 -2
  44. package/dist/cli/index.js +9 -9
  45. package/dist/{cloudflare-XO43ZIQF.js → cloudflare-4M4XVNUH.js} +5 -3
  46. package/dist/{cloudflare-XO43ZIQF.js.map → cloudflare-4M4XVNUH.js.map} +1 -1
  47. package/dist/{config-support-O4REYSK5.js → config-support-SB5NDQZB.js} +11 -5
  48. package/dist/{config-support-O4REYSK5.js.map → config-support-SB5NDQZB.js.map} +1 -1
  49. package/dist/{deno-deploy-EUQDK2RC.js → deno-deploy-IVACZ62D.js} +11 -5
  50. package/dist/deno-deploy-IVACZ62D.js.map +1 -0
  51. package/dist/{dev-CD4YU5FN.js → dev-JDJWMRLH.js} +9 -9
  52. package/dist/{index-inRdhi59.d.ts → index-CmkWlIDO.d.ts} +3 -1
  53. package/dist/index.d.ts +12 -2
  54. package/dist/index.js +7 -7
  55. package/dist/{info-2H7I55YN.js → info-SUX7QVA3.js} +4 -4
  56. package/dist/{internal-api-TMLSNYMP.js → internal-api-6XNLCNI5.js} +6 -6
  57. package/dist/{internal-api-WAKX6AZW.js → internal-api-VDYOXOT7.js} +4 -4
  58. package/dist/{load-config-VMKOV44Y.js → load-config-52H3EKAA.js} +4 -4
  59. package/dist/{mcp-HS26ZCU4.js → mcp-WJNCTM7M.js} +3 -3
  60. package/dist/{netlify-LEYEQZK6.js → netlify-3HEWV375.js} +6 -4
  61. package/dist/{netlify-LEYEQZK6.js.map → netlify-3HEWV375.js.map} +1 -1
  62. package/dist/{node-OSVPYQ3D.js → node-QUGTW6XV.js} +2 -2
  63. package/dist/{observability-bootstrap-YQKMEYW7.js → observability-bootstrap-FP6LMMJE.js} +3 -3
  64. package/dist/{openapi-N4CXRUNT.js → openapi-Q53YOBIU.js} +4 -4
  65. package/dist/{preview-YDJSRJ2K.js → preview-BARW75PN.js} +3 -3
  66. package/dist/{rate-limit-BdNDZ3vt.d.ts → rate-limit-fULgvzCz.d.ts} +11 -1
  67. package/dist/{rate-limit-store-BEJnhWdw.d.ts → rate-limit-store-n1wD4Yk4.d.ts} +48 -1
  68. package/dist/registry-SDBODRU6.js +23 -0
  69. package/dist/{routes-ECCDRHXZ.js → routes-FDF4SLOB.js} +4 -4
  70. package/dist/{schema-ND7GHPQK.js → schema-LHNWUR4V.js} +3 -3
  71. package/dist/{schemas-QCMSKQDK.js → schemas-2R2J64ZH.js} +2 -2
  72. package/dist/server/auth/index.d.ts +1 -1
  73. package/dist/server/http/index.d.ts +1 -1
  74. package/dist/server/http/index.js +5 -3
  75. package/dist/server/index.d.ts +4 -4
  76. package/dist/server/index.js +15 -7
  77. package/dist/server/index.js.map +1 -1
  78. package/dist/server/rate-limit/index.d.ts +96 -14
  79. package/dist/server/rate-limit/index.js +10 -4
  80. package/dist/server/storage/index.js +1 -1
  81. package/dist/{server-boundary-RX2AFU5U.js → server-boundary-AO6EELRF.js} +4 -4
  82. package/dist/{server-boundary-NZPP33G6.js → server-boundary-E5GFVPLB.js} +6 -6
  83. package/dist/{start-TYXS5I4M.js → start-4JTYXQQJ.js} +14 -11
  84. package/dist/start-4JTYXQQJ.js.map +1 -0
  85. package/dist/{static-7OJNB7D2.js → static-NQPLPAB6.js} +6 -2
  86. package/dist/{static-7OJNB7D2.js.map → static-NQPLPAB6.js.map} +1 -1
  87. package/dist/{theo-cloud-3ET4G5T6.js → theo-cloud-HMFNZ3FS.js} +3 -1
  88. package/dist/{theo-cloud-3ET4G5T6.js.map → theo-cloud-HMFNZ3FS.js.map} +1 -1
  89. package/dist/{vercel-HTAWCNAG.js → vercel-5PMYEEKF.js} +6 -4
  90. package/dist/{vercel-HTAWCNAG.js.map → vercel-5PMYEEKF.js.map} +1 -1
  91. package/dist/vite-plugin/index.d.ts +2 -2
  92. package/dist/vite-plugin/index.js +7 -7
  93. package/dist/{vite-plugin-DDILUFYY.js → vite-plugin-3ON45MQE.js} +8 -8
  94. package/package.json +3 -3
  95. package/dist/build-KCIH3HI6.js.map +0 -1
  96. package/dist/chunk-5XXXJ6DT.js.map +0 -1
  97. package/dist/chunk-KI4DOTN4.js.map +0 -1
  98. package/dist/chunk-OSBGZYXZ.js.map +0 -1
  99. package/dist/chunk-Q4NSUGMS.js.map +0 -1
  100. package/dist/chunk-SX7ZAHNE.js.map +0 -1
  101. package/dist/chunk-TNI4MARH.js.map +0 -1
  102. package/dist/deno-deploy-EUQDK2RC.js.map +0 -1
  103. package/dist/registry-4JHCZ6EF.js +0 -23
  104. package/dist/start-TYXS5I4M.js.map +0 -1
  105. /package/dist/{actions-virtual-module-2SM6YMTM.js.map → actions-virtual-module-EAKYEMGF.js.map} +0 -0
  106. /package/dist/{actions-virtual-module-5LRBP6YK.js.map → actions-virtual-module-OHENNMQG.js.map} +0 -0
  107. /package/dist/{agent-4FXJXZFD.js.map → agent-VJTZ4DQ7.js.map} +0 -0
  108. /package/dist/{app-typed-client-LIIPHLO6.js.map → app-typed-client-ILLQYTXP.js.map} +0 -0
  109. /package/dist/{app-typed-client-JFPV4YLS.js.map → app-typed-client-LHH77QDB.js.map} +0 -0
  110. /package/dist/{chunk-KOG5K26V.js.map → chunk-2Z7TANA4.js.map} +0 -0
  111. /package/dist/{chunk-V6VARIR3.js.map → chunk-4EQEJ4PH.js.map} +0 -0
  112. /package/dist/{chunk-3NAKOWTX.js.map → chunk-6U67SO5U.js.map} +0 -0
  113. /package/dist/{chunk-YWWMYEAC.js.map → chunk-CLSYYO5F.js.map} +0 -0
  114. /package/dist/{chunk-JMN4COMK.js.map → chunk-D7STTNQN.js.map} +0 -0
  115. /package/dist/{chunk-DSPEN7ZJ.js.map → chunk-DKX7LF5Q.js.map} +0 -0
  116. /package/dist/{chunk-6AGXDYVE.js.map → chunk-IB5VIXLR.js.map} +0 -0
  117. /package/dist/{chunk-PGDBSK2L.js.map → chunk-L6UHMWXU.js.map} +0 -0
  118. /package/dist/{chunk-MS4A7N77.js.map → chunk-PEL3MJLM.js.map} +0 -0
  119. /package/dist/{chunk-FFS7IRZF.js.map → chunk-SZWGXHY3.js.map} +0 -0
  120. /package/dist/{chunk-TMQ52AZR.js.map → chunk-X2LFNDJQ.js.map} +0 -0
  121. /package/dist/{chunk-FWEXQ2BR.js.map → chunk-XLSJWKSP.js.map} +0 -0
  122. /package/dist/{chunk-NZDGV4YJ.js.map → chunk-YCF3N4JS.js.map} +0 -0
  123. /package/dist/{dev-CD4YU5FN.js.map → dev-JDJWMRLH.js.map} +0 -0
  124. /package/dist/{info-2H7I55YN.js.map → info-SUX7QVA3.js.map} +0 -0
  125. /package/dist/{internal-api-TMLSNYMP.js.map → internal-api-6XNLCNI5.js.map} +0 -0
  126. /package/dist/{internal-api-WAKX6AZW.js.map → internal-api-VDYOXOT7.js.map} +0 -0
  127. /package/dist/{load-config-VMKOV44Y.js.map → load-config-52H3EKAA.js.map} +0 -0
  128. /package/dist/{mcp-HS26ZCU4.js.map → mcp-WJNCTM7M.js.map} +0 -0
  129. /package/dist/{node-OSVPYQ3D.js.map → node-QUGTW6XV.js.map} +0 -0
  130. /package/dist/{observability-bootstrap-YQKMEYW7.js.map → observability-bootstrap-FP6LMMJE.js.map} +0 -0
  131. /package/dist/{openapi-N4CXRUNT.js.map → openapi-Q53YOBIU.js.map} +0 -0
  132. /package/dist/{preview-YDJSRJ2K.js.map → preview-BARW75PN.js.map} +0 -0
  133. /package/dist/{registry-4JHCZ6EF.js.map → registry-SDBODRU6.js.map} +0 -0
  134. /package/dist/{routes-ECCDRHXZ.js.map → routes-FDF4SLOB.js.map} +0 -0
  135. /package/dist/{schema-ND7GHPQK.js.map → schema-LHNWUR4V.js.map} +0 -0
  136. /package/dist/{schemas-QCMSKQDK.js.map → schemas-2R2J64ZH.js.map} +0 -0
  137. /package/dist/{server-boundary-RX2AFU5U.js.map → server-boundary-AO6EELRF.js.map} +0 -0
  138. /package/dist/{server-boundary-NZPP33G6.js.map → server-boundary-E5GFVPLB.js.map} +0 -0
  139. /package/dist/{vite-plugin-DDILUFYY.js.map → vite-plugin-3ON45MQE.js.map} +0 -0
@@ -5,22 +5,22 @@ import {
5
5
  } from "./chunk-GEQJAFSV.js";
6
6
  import {
7
7
  initCacheEngineFromConfig
8
- } from "./chunk-NZDGV4YJ.js";
8
+ } from "./chunk-YCF3N4JS.js";
9
9
  import {
10
10
  preflightNodeAndBindings
11
11
  } from "./chunk-KTMQUYED.js";
12
12
  import {
13
13
  theoPluginAsync
14
- } from "./chunk-V6VARIR3.js";
15
- import "./chunk-PGDBSK2L.js";
14
+ } from "./chunk-4EQEJ4PH.js";
15
+ import "./chunk-L6UHMWXU.js";
16
16
  import "./chunk-BDH5GKFZ.js";
17
17
  import "./chunk-3PWQQWT6.js";
18
18
  import "./chunk-FDOOBTXJ.js";
19
- import "./chunk-5XXXJ6DT.js";
19
+ import "./chunk-E2RC367T.js";
20
20
  import "./chunk-BINRCILF.js";
21
21
  import "./chunk-QD5X56I3.js";
22
22
  import "./chunk-WSMLSIS5.js";
23
- import "./chunk-MS4A7N77.js";
23
+ import "./chunk-PEL3MJLM.js";
24
24
  import "./chunk-C27VRUVN.js";
25
25
  import "./chunk-7UZZHCRN.js";
26
26
  import "./chunk-47MM2JUK.js";
@@ -30,16 +30,16 @@ import "./chunk-NO6HA4FG.js";
30
30
  import "./chunk-Z7CJHDLS.js";
31
31
  import {
32
32
  loadConfig
33
- } from "./chunk-FFS7IRZF.js";
33
+ } from "./chunk-SZWGXHY3.js";
34
34
  import {
35
35
  loadEnv
36
36
  } from "./chunk-3QPVEJ7D.js";
37
37
  import "./chunk-U5PWXS3J.js";
38
- import "./chunk-TMQ52AZR.js";
38
+ import "./chunk-X2LFNDJQ.js";
39
39
  import {
40
40
  orchestrateDev
41
41
  } from "./chunk-ZNZISJDZ.js";
42
- import "./chunk-OSBGZYXZ.js";
42
+ import "./chunk-D2R5SJEL.js";
43
43
  import "./chunk-5ODOE6EF.js";
44
44
  import "./chunk-VQIUTCK2.js";
45
45
 
@@ -128,4 +128,4 @@ export {
128
128
  devCommand,
129
129
  startDevServer
130
130
  };
131
- //# sourceMappingURL=dev-CD4YU5FN.js.map
131
+ //# sourceMappingURL=dev-JDJWMRLH.js.map
@@ -578,6 +578,8 @@ declare function _resetMiddlewareCacheForTests(): void;
578
578
  declare class MiddlewareNextUnavailableError extends Error {
579
579
  constructor();
580
580
  }
581
+ /** Test seam: the latch is per-process, and a suite needs to assert the FIRST warning repeatedly. */
582
+ declare function _resetUninvocableWarningsForTests(): void;
581
583
  declare function runMiddlewareAndContext(req: IncomingMessage, res: ServerResponse, loadModule: LoadModule, serverDir: string): Promise<MiddlewareResult>;
582
584
  /**
583
585
  * Run ONLY the application's `server/context.ts` factory and return what it produced.
@@ -986,4 +988,4 @@ interface OpenApiDocsOptions {
986
988
  */
987
989
  declare function createOpenApiHandler(opts?: OpenApiDocsOptions): (request: Request) => Response | null;
988
990
 
989
- export { extractTraceId as $, appendCookieToHeaders as A, BATCH_PATH as B, type CookieOptions as C, appendDeleteCookieToHeaders as D, type ExecuteActionOptions as E, FileTooLargeError as F, callProcedure as G, type HandleBatchOptions as H, createCorsHandler as I, createCorsWebHandler as J, createOpenApiHandler as K, createServerContext as L, MAX_ERROR_HTML_BYTES as M, NotFoundError as N, type OpenApiDocsOptions as O, type ParsedBody as P, deleteCookie as Q, type RawRouteInput as R, STRIPPED_HEADERS as S, TRACE_HEADER as T, type UploadedFile as U, envelopeCodeToStatus as V, type W3CTraceContext as W, executeAction as X, executeRoute as Y, executeWebRequest as Z, _resetMiddlewareCacheForTests as _, type BatchExecuteFn as a, extractTraceIdFromRequest as a0, extractW3CTraceContext as a1, fromUnknown as a2, getCookie as a3, getCookieFromRequest as a4, handleBatchRequest as a5, handleRequestError as a6, handleWebRequestError as a7, injectModulePreloads as a8, jsonTransformer as a9, loadCustomErrorPages as aa, matchesOrigin as ab, parseCookieHeader as ac, parseRequestBody as ad, parseTraceparent as ae, parseTraceparentContext as af, resolveTransformer as ag, resolveUserModule as ah, runMiddlewareAndContext as ai, sendError as aj, sendJson as ak, serializeCookie as al, serveStaticFile as am, serverErrorToEnvelope as an, setCookie as ao, superjsonTransformer as ap, validateRouteInput as aq, BatchPathConflictError as b, type BatchPayload as c, type BatchRequestItem as d, type BatchResponse as e, type BatchResultItem as f, type BodyParserOptions as g, type CorsConfig as h, type CorsHandler as i, type CorsOrigin as j, type CorsWebHandler as k, type CustomErrorPages as l, type ExecuteRouteContext as m, MiddlewareNextUnavailableError as n, type MiddlewareResult as o, type ProcedureInput as p, ProcedureInputError as q, ProcedureOutputError as r, type RouteInputChannel as s, type RouteInputSchemas as t, type RouteInputValidation as u, type SendErrorInput as v, type SendErrorOptions as w, TRACE_PARENT_HEADER as x, TheoError as y, type TheoTransformer as z };
991
+ export { executeWebRequest as $, _resetUninvocableWarningsForTests as A, BATCH_PATH as B, type CookieOptions as C, appendCookieToHeaders as D, type ExecuteActionOptions as E, FileTooLargeError as F, appendDeleteCookieToHeaders as G, type HandleBatchOptions as H, callProcedure as I, createCorsHandler as J, createCorsWebHandler as K, createOpenApiHandler as L, MAX_ERROR_HTML_BYTES as M, NotFoundError as N, type OpenApiDocsOptions as O, type ParsedBody as P, createServerContext as Q, type RawRouteInput as R, STRIPPED_HEADERS as S, TRACE_HEADER as T, type UploadedFile as U, deleteCookie as V, type W3CTraceContext as W, envelopeCodeToStatus as X, executeAction as Y, executeRoute as Z, _resetMiddlewareCacheForTests as _, type BatchExecuteFn as a, extractTraceId as a0, extractTraceIdFromRequest as a1, extractW3CTraceContext as a2, fromUnknown as a3, getCookie as a4, getCookieFromRequest as a5, handleBatchRequest as a6, handleRequestError as a7, handleWebRequestError as a8, injectModulePreloads as a9, jsonTransformer as aa, loadCustomErrorPages as ab, matchesOrigin as ac, parseCookieHeader as ad, parseRequestBody as ae, parseTraceparent as af, parseTraceparentContext as ag, resolveTransformer as ah, resolveUserModule as ai, runMiddlewareAndContext as aj, sendError as ak, sendJson as al, serializeCookie as am, serveStaticFile as an, serverErrorToEnvelope as ao, setCookie as ap, superjsonTransformer as aq, validateRouteInput as ar, BatchPathConflictError as b, type BatchPayload as c, type BatchRequestItem as d, type BatchResponse as e, type BatchResultItem as f, type BodyParserOptions as g, type CorsConfig as h, type CorsHandler as i, type CorsOrigin as j, type CorsWebHandler as k, type CustomErrorPages as l, type ExecuteRouteContext as m, MiddlewareNextUnavailableError as n, type MiddlewareResult as o, type ProcedureInput as p, ProcedureInputError as q, ProcedureOutputError as r, type RouteInputChannel as s, type RouteInputSchemas as t, type RouteInputValidation as u, type SendErrorInput as v, type SendErrorOptions as w, TRACE_PARENT_HEADER as x, TheoError as y, type TheoTransformer as z };
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ import { z } from 'zod';
2
2
  import { T as TheoErrorEnvelope } from './error-envelope-DG47lt8E.js';
3
3
  export { theoPlugin } from './vite-plugin/index.js';
4
4
  import 'vite';
5
- import './rate-limit-BdNDZ3vt.js';
5
+ import './rate-limit-fULgvzCz.js';
6
6
  import 'node:http';
7
- import './rate-limit-store-BEJnhWdw.js';
7
+ import './rate-limit-store-n1wD4Yk4.js';
8
8
  import './csrf-readiness-store-CjIoub3U.js';
9
9
  import './schema-BdakLtuC.js';
10
10
 
@@ -69,7 +69,17 @@ declare const theoConfigSchema: z.ZodObject<{
69
69
  rateLimit: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
70
70
  windowMs: z.ZodNumber;
71
71
  max: z.ZodNumber;
72
+ store: z.ZodOptional<z.ZodObject<{
73
+ module: z.ZodString;
74
+ factory: z.ZodString;
75
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
76
+ }, z.core.$strip>>;
72
77
  }, z.core.$strip>, z.ZodObject<{
78
+ store: z.ZodOptional<z.ZodObject<{
79
+ module: z.ZodString;
80
+ factory: z.ZodString;
81
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
82
+ }, z.core.$strip>>;
73
83
  default: z.ZodOptional<z.ZodObject<{
74
84
  windowMs: z.ZodNumber;
75
85
  max: z.ZodNumber;
package/dist/index.js CHANGED
@@ -8,19 +8,19 @@ import {
8
8
  scanRoutes,
9
9
  theoConfigSchema,
10
10
  theoPlugin
11
- } from "./chunk-FWEXQ2BR.js";
11
+ } from "./chunk-XLSJWKSP.js";
12
12
  import "./chunk-7NRFZBAL.js";
13
13
  import "./chunk-SSYDEKWF.js";
14
- import "./chunk-JMN4COMK.js";
15
- import "./chunk-3NAKOWTX.js";
16
- import "./chunk-SX7ZAHNE.js";
14
+ import "./chunk-D7STTNQN.js";
15
+ import "./chunk-6U67SO5U.js";
16
+ import "./chunk-WBB3MEEN.js";
17
17
  import "./chunk-OIGJOYX6.js";
18
18
  import "./chunk-U3SPBLJF.js";
19
19
  import "./chunk-WSOAN4KN.js";
20
20
  import "./chunk-ENSFCKP6.js";
21
21
  import "./chunk-OGLS76RN.js";
22
- import "./chunk-KOG5K26V.js";
23
- import "./chunk-4D4YVXHR.js";
22
+ import "./chunk-2Z7TANA4.js";
23
+ import "./chunk-SYIQV4UH.js";
24
24
  import "./chunk-P6OS7652.js";
25
25
  import "./chunk-ZDUO4IYN.js";
26
26
  import "./chunk-IDWN2OSG.js";
@@ -31,7 +31,7 @@ import "./chunk-ZDCCILMP.js";
31
31
  import "./chunk-CHTAWXC2.js";
32
32
  import "./chunk-AVQU5CQF.js";
33
33
  import "./chunk-VSLTKX5L.js";
34
- import "./chunk-TNI4MARH.js";
34
+ import "./chunk-SFGGRC35.js";
35
35
  import "./chunk-4ERYQBL6.js";
36
36
  import "./chunk-4CIHLTSI.js";
37
37
  import "./chunk-FHQP23Z5.js";
@@ -5,12 +5,12 @@ import {
5
5
  } from "./chunk-47MM2JUK.js";
6
6
  import {
7
7
  loadConfig
8
- } from "./chunk-FFS7IRZF.js";
8
+ } from "./chunk-SZWGXHY3.js";
9
9
  import "./chunk-3QPVEJ7D.js";
10
10
  import "./chunk-U5PWXS3J.js";
11
- import "./chunk-TMQ52AZR.js";
11
+ import "./chunk-X2LFNDJQ.js";
12
12
  import "./chunk-ZNZISJDZ.js";
13
- import "./chunk-OSBGZYXZ.js";
13
+ import "./chunk-D2R5SJEL.js";
14
14
 
15
15
  // src/cli/commands/info.ts
16
16
  import { existsSync, readFileSync } from "fs";
@@ -103,4 +103,4 @@ export {
103
103
  buildInfo,
104
104
  infoCommand
105
105
  };
106
- //# sourceMappingURL=info-2H7I55YN.js.map
106
+ //# sourceMappingURL=info-SUX7QVA3.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  dispatchControllerRequest
3
- } from "./chunk-JMN4COMK.js";
3
+ } from "./chunk-D7STTNQN.js";
4
4
  import {
5
5
  mountAgent
6
- } from "./chunk-SX7ZAHNE.js";
6
+ } from "./chunk-WBB3MEEN.js";
7
7
  import {
8
8
  CSP_REPORT_PATH,
9
9
  CsrfReadinessStore,
@@ -18,11 +18,11 @@ import {
18
18
  executeAction,
19
19
  executeRoute,
20
20
  handleBatchRequest
21
- } from "./chunk-KOG5K26V.js";
21
+ } from "./chunk-2Z7TANA4.js";
22
22
  import {
23
23
  incomingMessageToHandlerRequest,
24
24
  sendError
25
- } from "./chunk-4D4YVXHR.js";
25
+ } from "./chunk-SYIQV4UH.js";
26
26
  import "./chunk-P6OS7652.js";
27
27
  import "./chunk-ZDUO4IYN.js";
28
28
  import "./chunk-IDWN2OSG.js";
@@ -43,7 +43,7 @@ import {
43
43
  } from "./chunk-VSLTKX5L.js";
44
44
  import {
45
45
  createRateLimiter
46
- } from "./chunk-TNI4MARH.js";
46
+ } from "./chunk-SFGGRC35.js";
47
47
  import {
48
48
  createViteLoader
49
49
  } from "./chunk-4ERYQBL6.js";
@@ -108,4 +108,4 @@ export {
108
108
  scanWebSocketRoutes,
109
109
  sendError
110
110
  };
111
- //# sourceMappingURL=internal-api-TMLSNYMP.js.map
111
+ //# sourceMappingURL=internal-api-6XNLCNI5.js.map
@@ -16,7 +16,7 @@ import {
16
16
  handleCspReport,
17
17
  handleCsrfReadiness,
18
18
  mountAgent
19
- } from "./chunk-5XXXJ6DT.js";
19
+ } from "./chunk-E2RC367T.js";
20
20
  import {
21
21
  resolveProvider
22
22
  } from "./chunk-BINRCILF.js";
@@ -31,7 +31,7 @@ import {
31
31
  import {
32
32
  TRACE_HEADER,
33
33
  extractTraceId
34
- } from "./chunk-MS4A7N77.js";
34
+ } from "./chunk-PEL3MJLM.js";
35
35
  import {
36
36
  logRequest
37
37
  } from "./chunk-C27VRUVN.js";
@@ -52,7 +52,7 @@ import {
52
52
  } from "./chunk-NO6HA4FG.js";
53
53
  import "./chunk-Z7CJHDLS.js";
54
54
  import "./chunk-U5PWXS3J.js";
55
- import "./chunk-OSBGZYXZ.js";
55
+ import "./chunk-D2R5SJEL.js";
56
56
  import "./chunk-VQIUTCK2.js";
57
57
  export {
58
58
  ACTION_SCHEMAS_DIR,
@@ -87,4 +87,4 @@ export {
87
87
  scanWebSocketRoutes,
88
88
  sendError
89
89
  };
90
- //# sourceMappingURL=internal-api-WAKX6AZW.js.map
90
+ //# sourceMappingURL=internal-api-VDYOXOT7.js.map
@@ -3,14 +3,14 @@ import "tsx/esm";
3
3
  import {
4
4
  deepMerge,
5
5
  loadConfig
6
- } from "./chunk-FFS7IRZF.js";
6
+ } from "./chunk-SZWGXHY3.js";
7
7
  import "./chunk-3QPVEJ7D.js";
8
8
  import "./chunk-U5PWXS3J.js";
9
- import "./chunk-TMQ52AZR.js";
9
+ import "./chunk-X2LFNDJQ.js";
10
10
  import "./chunk-ZNZISJDZ.js";
11
- import "./chunk-OSBGZYXZ.js";
11
+ import "./chunk-D2R5SJEL.js";
12
12
  export {
13
13
  deepMerge,
14
14
  loadConfig
15
15
  };
16
- //# sourceMappingURL=load-config-VMKOV44Y.js.map
16
+ //# sourceMappingURL=load-config-52H3EKAA.js.map
@@ -2,7 +2,7 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  createAgentSsrLoader
5
- } from "./chunk-DSPEN7ZJ.js";
5
+ } from "./chunk-DKX7LF5Q.js";
6
6
  import {
7
7
  extractAppResources,
8
8
  handleMcpJsonRpc
@@ -46,7 +46,7 @@ async function serveMcpStdio(mod, name, appResources, streams) {
46
46
  // src/cli/commands/mcp.ts
47
47
  async function mcpCommand(name, deps = {}) {
48
48
  const projectRoot = deps.projectRoot ?? process.cwd();
49
- const agentsDir = deps.agentsDir ?? (await (await import("./load-config-VMKOV44Y.js")).loadConfig(projectRoot)).agentsDir;
49
+ const agentsDir = deps.agentsDir ?? (await (await import("./load-config-52H3EKAA.js")).loadConfig(projectRoot)).agentsDir;
50
50
  const agent = scanAgents(projectRoot, agentsDir).find((a) => a.name === name);
51
51
  if (!agent) {
52
52
  const names = scanAgents(projectRoot, agentsDir).map((a) => a.name).join(", ");
@@ -75,4 +75,4 @@ async function mcpCommand(name, deps = {}) {
75
75
  export {
76
76
  mcpCommand
77
77
  };
78
- //# sourceMappingURL=mcp-HS26ZCU4.js.map
78
+ //# sourceMappingURL=mcp-WJNCTM7M.js.map
@@ -2,7 +2,7 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  deployedEntryPreamble
5
- } from "./chunk-6AGXDYVE.js";
5
+ } from "./chunk-IB5VIXLR.js";
6
6
  import {
7
7
  JSON_NOT_FOUND_RESPONSE,
8
8
  agentsDirLiteral,
@@ -13,10 +13,10 @@ import {
13
13
  describeDeployedSecurityHeaders,
14
14
  rateLimitCheckFragment,
15
15
  scannedFromLoaderCache
16
- } from "./chunk-IGCOTLQI.js";
16
+ } from "./chunk-C5CAL3DX.js";
17
17
  import {
18
18
  nodeAdapter
19
- } from "./chunk-KI4DOTN4.js";
19
+ } from "./chunk-G7U7FQVC.js";
20
20
  import {
21
21
  buildSecurityHeaders
22
22
  } from "./chunk-WSMLSIS5.js";
@@ -256,6 +256,8 @@ var netlifyAdapter = {
256
256
  // The document is served by Netlify's static host and does not pass through
257
257
  // it (usetheokit/theokit#412).
258
258
  appliesConfig: ["securityHeaders", "csrf", "disallowed", "cors", "serialization"],
259
+ // B-257 — per-invocation: an in-process counter does not survive, so a declared limit needs a durable store.
260
+ enforcesRateLimit: "with-a-store",
259
261
  build(config, cwd, ctx) {
260
262
  return buildNetlify(config, cwd, {}, ctx);
261
263
  }
@@ -267,4 +269,4 @@ export {
267
269
  netlifyAdapter,
268
270
  renderNetlifyFunction
269
271
  };
270
- //# sourceMappingURL=netlify-LEYEQZK6.js.map
272
+ //# sourceMappingURL=netlify-3HEWV375.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/netlify.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Netlify deploy adapter. All paths derived from `cwd` and a fixed\n * `.theokit/netlify/` output layout. Build-time tool — no HTTP input.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport {\n deployedAgentsFragment,\n JSON_NOT_FOUND_RESPONSE,\n scannedFromLoaderCache,\n} from './deployed-agents.js'\nimport { type DeployedCorsOptions } from './deployed-cors.js'\nimport { type DeployedCsrfOptions } from './deployed-csrf.js'\nimport { deployedEntryPreamble } from './deployed-preamble.js'\nimport { deployedRateLimitFragment, rateLimitCheckFragment } from './deployed-rate-limit.js'\nimport type { DeployedRateLimitOptions } from './deployed-rate-limit.js'\nimport {\n agentsDirLiteral,\n type DeployedAgentsDirOptions,\n deployedRuntimeConfigFragment,\n type DeployedRuntimeConfigOptions,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport { buildSecurityHeaders, describeDeployedSecurityHeaders } from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport class NetlifyConflictError extends Error {\n constructor(path: string, currentTo: string) {\n super(\n `netlify.toml has a conflicting redirect: \"${path}\" → \"${currentTo}\". ` +\n `Remove it manually or edit it to point to \"/.netlify/functions/theo\" ` +\n `before running the Netlify adapter again.`,\n )\n this.name = 'NetlifyConflictError'\n }\n}\n\nexport interface NetlifyBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeFile?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n readTomlIfExists?: () => string | null\n}\n\nexport function renderNetlifyFunction(\n opts: { securityHeaders?: SecurityHeadersConfig } & DeployedAgentsDirOptions &\n DeployedCsrfOptions &\n DeployedRuntimeConfigOptions &\n DeployedCorsOptions &\n DeployedRateLimitOptions = {},\n): string {\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n // B-235. Netlify's handler is `(request, context)` and already RECEIVES a Web `Request`, which\n // is the shape this branch needs — the item filed against it counted `new Request` occurrences\n // and concluded the opposite, measuring how the entry OBTAINS a request rather than whether it\n // has one. Deno is the model: same `loaderCache`, same filesystem `serverDir`, same guard.\n const agentsFragment = deployedAgentsFragment(scannedFromLoaderCache(agentsDirLiteral(opts)), {\n // Netlify answers its own 404 inline rather than through a helper, so the fragment is given\n // the expression instead of a call it would have to declare.\n notFound: `new Response('Not Found', { status: 404 })`,\n })\n return [\n `// Generated by Theo — Netlify Functions adapter`,\n `import { resolve } from 'node:path'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,\n ``,\n `const cwd = process.cwd()`,\n `const serverDir = resolve(cwd, 'server')`,\n `let routesCache = null`,\n `let loaderCache = null`,\n ``,\n `// #410 — the security baseline \\`theokit start\\` puts on every response,`,\n `// carried here as a literal because the deployed function has no`,\n `// theo.config.ts to read. The netlify.toml redirect routes only /api/* here,`,\n `// so this covers the API and not the document Netlify's static host serves`,\n `// (usetheokit/theokit#412).`,\n ...deployedEntryPreamble(runtimeConfig, agentsFragment, opts, 'netlify'),\n ``,\n // B-027 — emitted only when a limit is declared.\n ...(opts.rateLimit === undefined\n ? []\n : [\n `import { createRateLimiterWeb } from 'theokit/server'`,\n `import { resolveClientIpFromRequest } from 'theokit/server/rate-limit'`,\n ``,\n ]),\n ...deployedRateLimitFragment(\n opts.rateLimit,\n 'netlify',\n // `context.ip` is the platform's own answer, read from the handler context Netlify passes.\n // The forwarded fallback covers a function shape that carries no context, and returns\n // `undefined` unless a `trustProxy` was declared — which is the named 503, not a shared key.\n `context?.ip ?? resolveClientIpFromRequest(request, TRUST_PROXY)`,\n // Both names are bound in the OUTER handler, which is where the check runs. Threading\n // `context` into `handleRequest` — which the plan called for — buys nothing: the limiter\n // never runs in there.\n 'request, context',\n ),\n ``,\n `export default async (request, context) => {`,\n ` // #409 — the preflight is answered BEFORE anything routes: an OPTIONS the router`,\n ` // handles is an OPTIONS the browser never gets a CORS answer to.`,\n ` const preflight = corsPreflight(request)`,\n ` if (preflight !== null) return withSecurityHeaders(preflight, SECURITY_HEADERS)`,\n ...rateLimitCheckFragment(opts.rateLimit, ' ', 'request, context'),\n ` return withCors(request, withSecurityHeaders(await handleRequest(request), SECURITY_HEADERS))`,\n `}`,\n ``,\n `async function handleRequest(request) {`,\n ` const url = new URL(request.url)`,\n ` if (!url.pathname.startsWith('/api/')${agentsFragment.hostBypass}) {`,\n ` return new Response('Not Found', { status: 404 })`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ...agentsFragment.branch,\n ``,\n ` const match = matchRoute(url.pathname, routesCache)`,\n ` if (!match) return ${JSON_NOT_FOUND_RESPONSE}`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` // #382 — the run is not awaited: toResponse() settles at the headers and`,\n ` // carries a live body, so Netlify streams while the handler writes.`,\n ` return toResponse(executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId, ...CSRF_CONFIG, ${runtimeConfig.executeRouteSpread} }))`,\n `}`,\n ].join('\\n')\n}\n\nconst THEO_REDIRECT_FROM = '/api/*'\nconst THEO_REDIRECT_TO = '/.netlify/functions/theo'\n\n/**\n * TOML merge (EC-2) — non-destructive.\n *\n * We avoid adding a TOML parser dependency by working at the line level. The\n * supported subset is sufficient for the Netlify use case:\n * - detect existing `[[redirects]]` blocks\n * - find a block whose `from = \"...\"` matches our target\n * - error if such a block points elsewhere\n * - otherwise append our block\n *\n * Unknown sections (e.g. `[build]`, `[[headers]]`, `[context.production.environment]`)\n * are preserved as-is.\n */\ninterface NetlifyRedirectBlock {\n startLine: number\n endLine: number\n from?: string\n to?: string\n}\n\nfunction parseRedirectBlocks(lines: readonly string[]): NetlifyRedirectBlock[] {\n const blocks: NetlifyRedirectBlock[] = []\n let current: NetlifyRedirectBlock | null = null\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n if (trimmed === '[[redirects]]') {\n if (current) {\n current.endLine = i - 1\n blocks.push(current)\n }\n current = { startLine: i, endLine: i }\n continue\n }\n if (current && /^\\[(\\[|[^[])/.test(trimmed)) {\n // Entering another section — close current.\n current.endLine = i - 1\n blocks.push(current)\n current = null\n }\n if (current) {\n const fromMatch = /^from\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (fromMatch) current.from = fromMatch[1]\n const toMatch = /^to\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (toMatch) current.to = toMatch[1]\n current.endLine = i\n }\n }\n if (current) blocks.push(current)\n return blocks\n}\n\nconst THEO_REDIRECT_TARGET = [\n '[[redirects]]',\n ' from = \"/api/*\"',\n ' to = \"/.netlify/functions/theo\"',\n ' status = 200',\n ' force = true',\n].join('\\n')\n\n/**\n * Marks the block this build owns, so it can be REGENERATED rather than merely not duplicated.\n *\n * Idempotence alone would be the wrong contract here: the block carries configuration, so a\n * `security.headers` change has to reach the file. Leaving an existing block in place — which is\n * what the redirect above correctly does, because its content is fixed — would silently pin the\n * baseline to whatever the first build emitted.\n */\nconst THEO_HEADERS_MARKER = '# Generated by Theo — security baseline (usetheokit/theokit#412)'\n\n/**\n * The `[[headers]]` block telling Netlify's static host the baseline.\n *\n * The emitted function applies these to every response IT returns, and it never returns the HTML\n * document: the `[[redirects]]` block routes only `/api/*`, so the page comes from Netlify's static\n * host and carried none of them. The values come from `buildSecurityHeaders` — the same function\n * the handler calls — rather than being written out here, because two lists of headers that must\n * agree are two lists that eventually do not.\n */\nfunction renderHeadersBlock(securityHeaders: SecurityHeadersConfig | undefined): string {\n const values = buildSecurityHeaders(securityHeaders ?? {}, { production: true })\n return [\n THEO_HEADERS_MARKER,\n '[[headers]]',\n ' for = \"/*\"',\n ' [headers.values]',\n ...Object.entries(values).map(([k, v]) => ` ${k} = ${JSON.stringify(v)}`),\n ].join('\\n')\n}\n\n/**\n * Drop a previously generated block, so the next one replaces it.\n *\n * Bounded by the marker and the next top-level section at column 0 (or EOF) — the shape TOML gives\n * a block. A user's own `[[headers]]` is untouched because it does not carry the marker: their\n * file, their rules, and Netlify applies both.\n */\nfunction withoutGeneratedHeaders(source: string): string {\n const lines = source.split(/\\r?\\n/)\n const start = lines.indexOf(THEO_HEADERS_MARKER)\n if (start < 0) return source\n\n const opensSection = (line: string | undefined): boolean => (line ?? '').startsWith('[')\n\n let end = start + 1\n while (end < lines.length && !opensSection(lines[end])) end += 1\n // The marker line itself opens the block, so `end` now sits on the NEXT section — but the block's\n // own `[[headers]]` is the first `[` after the marker, so step past it before scanning again.\n end += 1\n while (end < lines.length && !opensSection(lines[end])) end += 1\n\n return [...lines.slice(0, start), ...lines.slice(end)].join('\\n').replace(/\\n{3,}/gu, '\\n\\n')\n}\n\nexport function mergeNetlifyToml(\n existing: string | null,\n securityHeaders?: SecurityHeadersConfig,\n): string {\n const headersBlock = renderHeadersBlock(securityHeaders)\n\n if (existing === null || existing.trim().length === 0) {\n return `${THEO_REDIRECT_TARGET}\\n\\n${headersBlock}\\n`\n }\n\n existing = withoutGeneratedHeaders(existing)\n\n const blocks = parseRedirectBlocks(existing.split(/\\r?\\n/))\n\n // Detect conflict: every `/api/*` block must point at our function.\n for (const b of blocks) {\n if (b.from !== THEO_REDIRECT_FROM) continue\n if (b.to === THEO_REDIRECT_TO) {\n // The redirect is already there. The headers block is regenerated regardless, because it\n // carries configuration and the redirect does not.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return `${existing}${sep}\\n${headersBlock}\\n`\n }\n throw new NetlifyConflictError(b.from, b.to ?? '(unknown)')\n }\n\n // No conflict — append target block.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return `${existing}${sep}\\n${THEO_REDIRECT_TARGET}\\n\\n${headersBlock}\\n`\n}\n\nexport async function buildNetlify(\n config: TheoConfig,\n cwd: string,\n deps: NetlifyBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('netlify', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n const writeFile =\n deps.writeFile ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n const readTomlIfExists =\n deps.readTomlIfExists ??\n (() => {\n const p = resolve(cwd, 'netlify.toml')\n return existsSync(p) ? readFileSync(p, 'utf-8') : null\n })\n\n const fnDir = resolve(cwd, '.netlify/functions')\n ensureDir(fnDir)\n writeFile(\n resolve(fnDir, 'theo.mjs'),\n renderNetlifyFunction({\n securityHeaders: config.security?.headers,\n // B-235 — pillar (a): the option existed and no build passed it, so a project with a\n // configured agents directory got the default `agents` on this target. Same defect\n // B-185 fixed for bun and deno, one target over.\n agentsDir: config.agentsDir,\n csrf: config.security?.csrf,\n disallowed: config.security?.disallowed,\n cors: config.security?.cors,\n // #425 — a selector, not a transformer, so it rides as a literal like the values above.\n serialization: config.serialization,\n }),\n )\n\n const existingToml = readTomlIfExists()\n const merged = mergeNetlifyToml(existingToml, config.security?.headers)\n writeFile(resolve(cwd, 'netlify.toml'), merged)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ Netlify output → .netlify/functions/theo.mjs + netlify.toml')\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: 'netlify',\n securityHeaders: config.security?.headers,\n mintsNonce: false,\n documentHeaders: 'platform-configured',\n })}\\n`,\n )\n}\n\nexport const netlifyAdapter: DeployAdapter = {\n name: 'netlify',\n streamsResponses: true,\n // #409 / #410 — the generated entry calls `executeRoute` with routes, loader\n // and serverDir only. CSRF, route policy, file middleware and Zod validation\n // still run because they live inside `executeRoute`; none of the remaining\n // configurable concerns reach it. Declared explicitly rather than omitted so\n // the gap is a statement in the source and not an absence.\n //\n // `securityHeaders` IS applied -- to every response this function returns.\n // The document is served by Netlify's static host and does not pass through\n // it (usetheokit/theokit#412).\n appliesConfig: ['securityHeaders', 'csrf', 'disallowed', 'cors', 'serialization'],\n build(config, cwd, ctx) {\n return buildNetlify(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":["sep"]}
1
+ {"version":3,"sources":["../src/adapters/netlify.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Netlify deploy adapter. All paths derived from `cwd` and a fixed\n * `.theokit/netlify/` output layout. Build-time tool — no HTTP input.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport {\n deployedAgentsFragment,\n JSON_NOT_FOUND_RESPONSE,\n scannedFromLoaderCache,\n} from './deployed-agents.js'\nimport { type DeployedCorsOptions } from './deployed-cors.js'\nimport { type DeployedCsrfOptions } from './deployed-csrf.js'\nimport { deployedEntryPreamble } from './deployed-preamble.js'\nimport { deployedRateLimitFragment, rateLimitCheckFragment } from './deployed-rate-limit.js'\nimport type { DeployedRateLimitOptions } from './deployed-rate-limit.js'\nimport {\n agentsDirLiteral,\n type DeployedAgentsDirOptions,\n deployedRuntimeConfigFragment,\n type DeployedRuntimeConfigOptions,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport { buildSecurityHeaders, describeDeployedSecurityHeaders } from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport class NetlifyConflictError extends Error {\n constructor(path: string, currentTo: string) {\n super(\n `netlify.toml has a conflicting redirect: \"${path}\" → \"${currentTo}\". ` +\n `Remove it manually or edit it to point to \"/.netlify/functions/theo\" ` +\n `before running the Netlify adapter again.`,\n )\n this.name = 'NetlifyConflictError'\n }\n}\n\nexport interface NetlifyBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeFile?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n readTomlIfExists?: () => string | null\n}\n\nexport function renderNetlifyFunction(\n opts: { securityHeaders?: SecurityHeadersConfig } & DeployedAgentsDirOptions &\n DeployedCsrfOptions &\n DeployedRuntimeConfigOptions &\n DeployedCorsOptions &\n DeployedRateLimitOptions = {},\n): string {\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n // B-235. Netlify's handler is `(request, context)` and already RECEIVES a Web `Request`, which\n // is the shape this branch needs — the item filed against it counted `new Request` occurrences\n // and concluded the opposite, measuring how the entry OBTAINS a request rather than whether it\n // has one. Deno is the model: same `loaderCache`, same filesystem `serverDir`, same guard.\n const agentsFragment = deployedAgentsFragment(scannedFromLoaderCache(agentsDirLiteral(opts)), {\n // Netlify answers its own 404 inline rather than through a helper, so the fragment is given\n // the expression instead of a call it would have to declare.\n notFound: `new Response('Not Found', { status: 404 })`,\n })\n return [\n `// Generated by Theo — Netlify Functions adapter`,\n `import { resolve } from 'node:path'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,\n ``,\n `const cwd = process.cwd()`,\n `const serverDir = resolve(cwd, 'server')`,\n `let routesCache = null`,\n `let loaderCache = null`,\n ``,\n `// #410 — the security baseline \\`theokit start\\` puts on every response,`,\n `// carried here as a literal because the deployed function has no`,\n `// theo.config.ts to read. The netlify.toml redirect routes only /api/* here,`,\n `// so this covers the API and not the document Netlify's static host serves`,\n `// (usetheokit/theokit#412).`,\n ...deployedEntryPreamble(runtimeConfig, agentsFragment, opts, 'netlify'),\n ``,\n // B-027 — emitted only when a limit is declared.\n ...(opts.rateLimit === undefined\n ? []\n : [\n `import { createRateLimiterWeb } from 'theokit/server'`,\n `import { resolveClientIpFromRequest } from 'theokit/server/rate-limit'`,\n ``,\n ]),\n ...deployedRateLimitFragment(\n opts.rateLimit,\n 'netlify',\n // `context.ip` is the platform's own answer, read from the handler context Netlify passes.\n // The forwarded fallback covers a function shape that carries no context, and returns\n // `undefined` unless a `trustProxy` was declared — which is the named 503, not a shared key.\n `context?.ip ?? resolveClientIpFromRequest(request, TRUST_PROXY)`,\n // Both names are bound in the OUTER handler, which is where the check runs. Threading\n // `context` into `handleRequest` — which the plan called for — buys nothing: the limiter\n // never runs in there.\n 'request, context',\n ),\n ``,\n `export default async (request, context) => {`,\n ` // #409 — the preflight is answered BEFORE anything routes: an OPTIONS the router`,\n ` // handles is an OPTIONS the browser never gets a CORS answer to.`,\n ` const preflight = corsPreflight(request)`,\n ` if (preflight !== null) return withSecurityHeaders(preflight, SECURITY_HEADERS)`,\n ...rateLimitCheckFragment(opts.rateLimit, ' ', 'request, context'),\n ` return withCors(request, withSecurityHeaders(await handleRequest(request), SECURITY_HEADERS))`,\n `}`,\n ``,\n `async function handleRequest(request) {`,\n ` const url = new URL(request.url)`,\n ` if (!url.pathname.startsWith('/api/')${agentsFragment.hostBypass}) {`,\n ` return new Response('Not Found', { status: 404 })`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ...agentsFragment.branch,\n ``,\n ` const match = matchRoute(url.pathname, routesCache)`,\n ` if (!match) return ${JSON_NOT_FOUND_RESPONSE}`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` // #382 — the run is not awaited: toResponse() settles at the headers and`,\n ` // carries a live body, so Netlify streams while the handler writes.`,\n ` return toResponse(executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId, ...CSRF_CONFIG, ${runtimeConfig.executeRouteSpread} }))`,\n `}`,\n ].join('\\n')\n}\n\nconst THEO_REDIRECT_FROM = '/api/*'\nconst THEO_REDIRECT_TO = '/.netlify/functions/theo'\n\n/**\n * TOML merge (EC-2) — non-destructive.\n *\n * We avoid adding a TOML parser dependency by working at the line level. The\n * supported subset is sufficient for the Netlify use case:\n * - detect existing `[[redirects]]` blocks\n * - find a block whose `from = \"...\"` matches our target\n * - error if such a block points elsewhere\n * - otherwise append our block\n *\n * Unknown sections (e.g. `[build]`, `[[headers]]`, `[context.production.environment]`)\n * are preserved as-is.\n */\ninterface NetlifyRedirectBlock {\n startLine: number\n endLine: number\n from?: string\n to?: string\n}\n\nfunction parseRedirectBlocks(lines: readonly string[]): NetlifyRedirectBlock[] {\n const blocks: NetlifyRedirectBlock[] = []\n let current: NetlifyRedirectBlock | null = null\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n if (trimmed === '[[redirects]]') {\n if (current) {\n current.endLine = i - 1\n blocks.push(current)\n }\n current = { startLine: i, endLine: i }\n continue\n }\n if (current && /^\\[(\\[|[^[])/.test(trimmed)) {\n // Entering another section — close current.\n current.endLine = i - 1\n blocks.push(current)\n current = null\n }\n if (current) {\n const fromMatch = /^from\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (fromMatch) current.from = fromMatch[1]\n const toMatch = /^to\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (toMatch) current.to = toMatch[1]\n current.endLine = i\n }\n }\n if (current) blocks.push(current)\n return blocks\n}\n\nconst THEO_REDIRECT_TARGET = [\n '[[redirects]]',\n ' from = \"/api/*\"',\n ' to = \"/.netlify/functions/theo\"',\n ' status = 200',\n ' force = true',\n].join('\\n')\n\n/**\n * Marks the block this build owns, so it can be REGENERATED rather than merely not duplicated.\n *\n * Idempotence alone would be the wrong contract here: the block carries configuration, so a\n * `security.headers` change has to reach the file. Leaving an existing block in place — which is\n * what the redirect above correctly does, because its content is fixed — would silently pin the\n * baseline to whatever the first build emitted.\n */\nconst THEO_HEADERS_MARKER = '# Generated by Theo — security baseline (usetheokit/theokit#412)'\n\n/**\n * The `[[headers]]` block telling Netlify's static host the baseline.\n *\n * The emitted function applies these to every response IT returns, and it never returns the HTML\n * document: the `[[redirects]]` block routes only `/api/*`, so the page comes from Netlify's static\n * host and carried none of them. The values come from `buildSecurityHeaders` — the same function\n * the handler calls — rather than being written out here, because two lists of headers that must\n * agree are two lists that eventually do not.\n */\nfunction renderHeadersBlock(securityHeaders: SecurityHeadersConfig | undefined): string {\n const values = buildSecurityHeaders(securityHeaders ?? {}, { production: true })\n return [\n THEO_HEADERS_MARKER,\n '[[headers]]',\n ' for = \"/*\"',\n ' [headers.values]',\n ...Object.entries(values).map(([k, v]) => ` ${k} = ${JSON.stringify(v)}`),\n ].join('\\n')\n}\n\n/**\n * Drop a previously generated block, so the next one replaces it.\n *\n * Bounded by the marker and the next top-level section at column 0 (or EOF) — the shape TOML gives\n * a block. A user's own `[[headers]]` is untouched because it does not carry the marker: their\n * file, their rules, and Netlify applies both.\n */\nfunction withoutGeneratedHeaders(source: string): string {\n const lines = source.split(/\\r?\\n/)\n const start = lines.indexOf(THEO_HEADERS_MARKER)\n if (start < 0) return source\n\n const opensSection = (line: string | undefined): boolean => (line ?? '').startsWith('[')\n\n let end = start + 1\n while (end < lines.length && !opensSection(lines[end])) end += 1\n // The marker line itself opens the block, so `end` now sits on the NEXT section — but the block's\n // own `[[headers]]` is the first `[` after the marker, so step past it before scanning again.\n end += 1\n while (end < lines.length && !opensSection(lines[end])) end += 1\n\n return [...lines.slice(0, start), ...lines.slice(end)].join('\\n').replace(/\\n{3,}/gu, '\\n\\n')\n}\n\nexport function mergeNetlifyToml(\n existing: string | null,\n securityHeaders?: SecurityHeadersConfig,\n): string {\n const headersBlock = renderHeadersBlock(securityHeaders)\n\n if (existing === null || existing.trim().length === 0) {\n return `${THEO_REDIRECT_TARGET}\\n\\n${headersBlock}\\n`\n }\n\n existing = withoutGeneratedHeaders(existing)\n\n const blocks = parseRedirectBlocks(existing.split(/\\r?\\n/))\n\n // Detect conflict: every `/api/*` block must point at our function.\n for (const b of blocks) {\n if (b.from !== THEO_REDIRECT_FROM) continue\n if (b.to === THEO_REDIRECT_TO) {\n // The redirect is already there. The headers block is regenerated regardless, because it\n // carries configuration and the redirect does not.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return `${existing}${sep}\\n${headersBlock}\\n`\n }\n throw new NetlifyConflictError(b.from, b.to ?? '(unknown)')\n }\n\n // No conflict — append target block.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return `${existing}${sep}\\n${THEO_REDIRECT_TARGET}\\n\\n${headersBlock}\\n`\n}\n\nexport async function buildNetlify(\n config: TheoConfig,\n cwd: string,\n deps: NetlifyBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('netlify', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n const writeFile =\n deps.writeFile ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n const readTomlIfExists =\n deps.readTomlIfExists ??\n (() => {\n const p = resolve(cwd, 'netlify.toml')\n return existsSync(p) ? readFileSync(p, 'utf-8') : null\n })\n\n const fnDir = resolve(cwd, '.netlify/functions')\n ensureDir(fnDir)\n writeFile(\n resolve(fnDir, 'theo.mjs'),\n renderNetlifyFunction({\n securityHeaders: config.security?.headers,\n // B-235 — pillar (a): the option existed and no build passed it, so a project with a\n // configured agents directory got the default `agents` on this target. Same defect\n // B-185 fixed for bun and deno, one target over.\n agentsDir: config.agentsDir,\n csrf: config.security?.csrf,\n disallowed: config.security?.disallowed,\n cors: config.security?.cors,\n // #425 — a selector, not a transformer, so it rides as a literal like the values above.\n serialization: config.serialization,\n }),\n )\n\n const existingToml = readTomlIfExists()\n const merged = mergeNetlifyToml(existingToml, config.security?.headers)\n writeFile(resolve(cwd, 'netlify.toml'), merged)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ Netlify output → .netlify/functions/theo.mjs + netlify.toml')\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: 'netlify',\n securityHeaders: config.security?.headers,\n mintsNonce: false,\n documentHeaders: 'platform-configured',\n })}\\n`,\n )\n}\n\nexport const netlifyAdapter: DeployAdapter = {\n name: 'netlify',\n streamsResponses: true,\n // #409 / #410 — the generated entry calls `executeRoute` with routes, loader\n // and serverDir only. CSRF, route policy, file middleware and Zod validation\n // still run because they live inside `executeRoute`; none of the remaining\n // configurable concerns reach it. Declared explicitly rather than omitted so\n // the gap is a statement in the source and not an absence.\n //\n // `securityHeaders` IS applied -- to every response this function returns.\n // The document is served by Netlify's static host and does not pass through\n // it (usetheokit/theokit#412).\n appliesConfig: ['securityHeaders', 'csrf', 'disallowed', 'cors', 'serialization'],\n // B-257 — per-invocation: an in-process counter does not survive, so a declared limit needs a durable store.\n enforcesRateLimit: 'with-a-store',\n build(config, cwd, ctx) {\n return buildNetlify(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":["sep"]}
@@ -2,9 +2,9 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  nodeAdapter
5
- } from "./chunk-KI4DOTN4.js";
5
+ } from "./chunk-G7U7FQVC.js";
6
6
  import "./chunk-ZNZISJDZ.js";
7
7
  export {
8
8
  nodeAdapter
9
9
  };
10
- //# sourceMappingURL=node-OSVPYQ3D.js.map
10
+ //# sourceMappingURL=node-QUGTW6XV.js.map
@@ -4,13 +4,13 @@ import {
4
4
  _resetObservabilityAdapter,
5
5
  createObservabilityPluginFromConfig,
6
6
  getObservabilityAdapter
7
- } from "./chunk-MS4A7N77.js";
7
+ } from "./chunk-PEL3MJLM.js";
8
8
  import "./chunk-C27VRUVN.js";
9
9
  import "./chunk-7UZZHCRN.js";
10
- import "./chunk-OSBGZYXZ.js";
10
+ import "./chunk-D2R5SJEL.js";
11
11
  export {
12
12
  _resetObservabilityAdapter,
13
13
  createObservabilityPluginFromConfig,
14
14
  getObservabilityAdapter
15
15
  };
16
- //# sourceMappingURL=observability-bootstrap-YQKMEYW7.js.map
16
+ //# sourceMappingURL=observability-bootstrap-FP6LMMJE.js.map
@@ -12,12 +12,12 @@ import "./chunk-NO6HA4FG.js";
12
12
  import "./chunk-Z7CJHDLS.js";
13
13
  import {
14
14
  loadConfig
15
- } from "./chunk-FFS7IRZF.js";
15
+ } from "./chunk-SZWGXHY3.js";
16
16
  import "./chunk-3QPVEJ7D.js";
17
17
  import "./chunk-U5PWXS3J.js";
18
- import "./chunk-TMQ52AZR.js";
18
+ import "./chunk-X2LFNDJQ.js";
19
19
  import "./chunk-ZNZISJDZ.js";
20
- import "./chunk-OSBGZYXZ.js";
20
+ import "./chunk-D2R5SJEL.js";
21
21
 
22
22
  // src/cli/commands/openapi.ts
23
23
  import { mkdtempSync, rmSync } from "fs";
@@ -84,4 +84,4 @@ function emitOpenApiInMemory(manifest, config) {
84
84
  export {
85
85
  openapiCommand
86
86
  };
87
- //# sourceMappingURL=openapi-N4CXRUNT.js.map
87
+ //# sourceMappingURL=openapi-Q53YOBIU.js.map
@@ -4,8 +4,8 @@ import "tsx/esm";
4
4
  // src/cli/commands/preview.ts
5
5
  async function defaultSteps() {
6
6
  const [{ buildCommand }, { startCommand }] = await Promise.all([
7
- import("./build-KCIH3HI6.js"),
8
- import("./start-TYXS5I4M.js")
7
+ import("./build-23QF5FNQ.js"),
8
+ import("./start-4JTYXQQJ.js")
9
9
  ]);
10
10
  return {
11
11
  build: (opts) => buildCommand(opts),
@@ -20,4 +20,4 @@ async function previewCommand(options = {}, steps) {
20
20
  export {
21
21
  previewCommand
22
22
  };
23
- //# sourceMappingURL=preview-YDJSRJ2K.js.map
23
+ //# sourceMappingURL=preview-BARW75PN.js.map
@@ -1,5 +1,5 @@
1
1
  import { IncomingMessage } from 'node:http';
2
- import { R as RateLimitStore } from './rate-limit-store-BEJnhWdw.js';
2
+ import { R as RateLimitStore, B as BakedStoreDeclaration } from './rate-limit-store-n1wD4Yk4.js';
3
3
 
4
4
  /**
5
5
  * Rate limit configuration — basic single-bucket shape. Per ADR D2, the
@@ -9,6 +9,16 @@ import { R as RateLimitStore } from './rate-limit-store-BEJnhWdw.js';
9
9
  interface RateLimitConfig {
10
10
  windowMs: number;
11
11
  max: number;
12
+ /**
13
+ * B-257 — the durable store a DEPLOYED entry constructs. Declared here because the config schema
14
+ * accepts it and this type is what the parsed config narrows to.
15
+ *
16
+ * The synchronous facades below IGNORE it: they cannot await, which is the whole reason
17
+ * `createDurableRateLimiterWeb` exists beside them (ADR 0018). A deployed entry reads this field
18
+ * at BUILD time, through `adapters/deployed-rate-limit.ts`, and constructs the store in the
19
+ * generated source — it is never read at request time by this module.
20
+ */
21
+ store?: RateLimitStore | BakedStoreDeclaration;
12
22
  }
13
23
  interface RateLimitResult {
14
24
  limited: boolean;
@@ -21,7 +21,54 @@ interface RateLimitState {
21
21
  count: number;
22
22
  /** Absolute timestamp (ms since epoch) when this window expires. */
23
23
  resetAt: number;
24
+ /**
25
+ * B-261 — how many requests the window BEFORE this one admitted, when the store can say.
26
+ *
27
+ * Optional, and the optionality IS the compatibility line: a third-party store returning only
28
+ * `{ count, resetAt }` keeps working with the behaviour it already had rather than breaking. A store
29
+ * that supplies this gets a sliding window instead of a fixed one.
30
+ *
31
+ * Why it is needed: with `resetAt` pinned at the first request of a window, filling the window late
32
+ * and the next one early spends two budgets inside one window's worth of time. Measured before the
33
+ * fix — `windowMs: 400, max: 5` admitted 10 in ~405ms, a factor of exactly 2.
34
+ */
35
+ previousCount?: number;
36
+ }
37
+ /**
38
+ * A durable store named at BUILD time, for an adapter to construct in a generated entry.
39
+ *
40
+ * B-257 — NOT a `RateLimitStore`. It has no methods, nothing calls it, and it exists only as text
41
+ * an adapter bakes into a generated entry: `config/schemas/rate-limit.ts` accepts it,
42
+ * `adapters/deployed-rate-limit.ts` emits it, and `createDurableRateLimiterWeb` receives the store
43
+ * the generated code constructed from it.
44
+ *
45
+ * It lives beside `RateLimitStore` because the two share a config key and a reader meeting one
46
+ * must be able to see the other. Declared once: the same shape was written inline in two modules
47
+ * before this, and two copies of one type diverge on the first change to either.
48
+ */
49
+ interface BakedStoreDeclaration {
50
+ module: string;
51
+ factory: string;
52
+ options?: Record<string, string | number | boolean>;
24
53
  }
54
+ /**
55
+ * The count a sliding window charges: the current window, plus whatever of the previous one is still
56
+ * inside `windowMs`.
57
+ *
58
+ * B-261 — it lives HERE and not in either limiter, because review found the first version living in
59
+ * `rate-limit-durable.ts` only: the durable path stopped bursting and the sync path still admitted 10
60
+ * against a nominal 5. Two limiters over one store disagreeing about what `max` means is the exact
61
+ * divergence B-260 had closed on the header contract, one item earlier, on a different axis.
62
+ *
63
+ * The weight is how much of the previous window has NOT yet slid out — nearly 1 just after a boundary,
64
+ * decaying to 0 by the end of the window. Clamped to 0..1, because a clock that moved would otherwise
65
+ * produce a negative weight (which CREDITS the caller) or one above 1 (which charges it for traffic
66
+ * that never happened).
67
+ *
68
+ * A store that cannot report `previousCount` yields the current count alone — today's behaviour and
69
+ * today's burst, so nothing breaks for a store nobody updated.
70
+ */
71
+ declare function slidingCount(state: RateLimitState, windowMs: number): number;
25
72
  interface RateLimitStore {
26
73
  /**
27
74
  * Atomic increment-and-get. If the key is missing OR the previous
@@ -69,4 +116,4 @@ declare class InMemoryStore implements RateLimitStore {
69
116
  reset(key: string): Promise<void>;
70
117
  }
71
118
 
72
- export { InMemoryStore as I, type RateLimitStore as R, type RateLimitState as a };
119
+ export { type BakedStoreDeclaration as B, InMemoryStore as I, type RateLimitStore as R, type RateLimitState as a, slidingCount as s };
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+
4
+ // src/adapters/registry.ts
5
+ var adapterRegistry = {
6
+ node: async () => (await import("./node-QUGTW6XV.js")).nodeAdapter,
7
+ vercel: async () => (await import("./vercel-5PMYEEKF.js")).vercelAdapter,
8
+ cloudflare: async () => (await import("./cloudflare-4M4XVNUH.js")).cloudflareAdapter,
9
+ static: async () => (await import("./static-NQPLPAB6.js")).staticAdapter,
10
+ bun: async () => (await import("./bun-NKVHOFSJ.js")).bunAdapter,
11
+ "deno-deploy": async () => (await import("./deno-deploy-IVACZ62D.js")).denoDeployAdapter,
12
+ netlify: async () => (await import("./netlify-3HEWV375.js")).netlifyAdapter,
13
+ "aws-lambda": async () => (await import("./aws-lambda-7AZNJGWU.js")).awsLambdaAdapter,
14
+ "theo-cloud": async () => (await import("./theo-cloud-HMFNZ3FS.js")).theoCloudAdapter
15
+ };
16
+ async function resolveAdapter(target) {
17
+ const factory = adapterRegistry[target];
18
+ return factory();
19
+ }
20
+ export {
21
+ resolveAdapter
22
+ };
23
+ //# sourceMappingURL=registry-SDBODRU6.js.map
@@ -11,12 +11,12 @@ import {
11
11
  import "./chunk-Z7CJHDLS.js";
12
12
  import {
13
13
  loadConfig
14
- } from "./chunk-FFS7IRZF.js";
14
+ } from "./chunk-SZWGXHY3.js";
15
15
  import "./chunk-3QPVEJ7D.js";
16
16
  import "./chunk-U5PWXS3J.js";
17
- import "./chunk-TMQ52AZR.js";
17
+ import "./chunk-X2LFNDJQ.js";
18
18
  import "./chunk-ZNZISJDZ.js";
19
- import "./chunk-OSBGZYXZ.js";
19
+ import "./chunk-D2R5SJEL.js";
20
20
 
21
21
  // src/cli/commands/routes.ts
22
22
  import { resolve, relative } from "path";
@@ -64,4 +64,4 @@ async function routesCommand() {
64
64
  export {
65
65
  routesCommand
66
66
  };
67
- //# sourceMappingURL=routes-ECCDRHXZ.js.map
67
+ //# sourceMappingURL=routes-FDF4SLOB.js.map