@yolk-sdk/emulators 0.1.0-canary.96

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 (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2135 -0
  3. package/dist/anthropic.d.mts +269 -0
  4. package/dist/anthropic.d.mts.map +1 -0
  5. package/dist/anthropic.mjs +177 -0
  6. package/dist/anthropic.mjs.map +1 -0
  7. package/dist/chat-completions.d.mts +288 -0
  8. package/dist/chat-completions.d.mts.map +1 -0
  9. package/dist/chat-completions.mjs +451 -0
  10. package/dist/chat-completions.mjs.map +1 -0
  11. package/dist/codex.d.mts +265 -0
  12. package/dist/codex.d.mts.map +1 -0
  13. package/dist/codex.mjs +199 -0
  14. package/dist/codex.mjs.map +1 -0
  15. package/dist/dropbox/api.d.mts +63 -0
  16. package/dist/dropbox/api.d.mts.map +1 -0
  17. package/dist/dropbox/api.mjs +565 -0
  18. package/dist/dropbox/api.mjs.map +1 -0
  19. package/dist/dropbox/state.d.mts +136 -0
  20. package/dist/dropbox/state.d.mts.map +1 -0
  21. package/dist/dropbox/state.mjs +209 -0
  22. package/dist/dropbox/state.mjs.map +1 -0
  23. package/dist/dropbox.d.mts +79 -0
  24. package/dist/dropbox.d.mts.map +1 -0
  25. package/dist/dropbox.mjs +124 -0
  26. package/dist/dropbox.mjs.map +1 -0
  27. package/dist/email-fixtures.d.mts +36 -0
  28. package/dist/email-fixtures.d.mts.map +1 -0
  29. package/dist/email-fixtures.mjs +1080 -0
  30. package/dist/email-fixtures.mjs.map +1 -0
  31. package/dist/email.d.mts +160 -0
  32. package/dist/email.d.mts.map +1 -0
  33. package/dist/email.mjs +608 -0
  34. package/dist/email.mjs.map +1 -0
  35. package/dist/emulator-compose.d.mts +40 -0
  36. package/dist/emulator-compose.d.mts.map +1 -0
  37. package/dist/emulator-compose.mjs +91 -0
  38. package/dist/emulator-compose.mjs.map +1 -0
  39. package/dist/emulator-http.d.mts +53 -0
  40. package/dist/emulator-http.d.mts.map +1 -0
  41. package/dist/emulator-http.mjs +117 -0
  42. package/dist/emulator-http.mjs.map +1 -0
  43. package/dist/emulator-kernel.d.mts +176 -0
  44. package/dist/emulator-kernel.d.mts.map +1 -0
  45. package/dist/emulator-kernel.mjs +413 -0
  46. package/dist/emulator-kernel.mjs.map +1 -0
  47. package/dist/fixture-route.d.mts +129 -0
  48. package/dist/fixture-route.d.mts.map +1 -0
  49. package/dist/fixture-route.mjs +257 -0
  50. package/dist/fixture-route.mjs.map +1 -0
  51. package/dist/fortnox/api.d.mts +92 -0
  52. package/dist/fortnox/api.d.mts.map +1 -0
  53. package/dist/fortnox/api.mjs +750 -0
  54. package/dist/fortnox/api.mjs.map +1 -0
  55. package/dist/fortnox/state.d.mts +533 -0
  56. package/dist/fortnox/state.d.mts.map +1 -0
  57. package/dist/fortnox/state.mjs +612 -0
  58. package/dist/fortnox/state.mjs.map +1 -0
  59. package/dist/fortnox.d.mts +128 -0
  60. package/dist/fortnox.d.mts.map +1 -0
  61. package/dist/fortnox.mjs +403 -0
  62. package/dist/fortnox.mjs.map +1 -0
  63. package/dist/gateway-evaluate-recordings.d.mts +12 -0
  64. package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
  65. package/dist/gateway-evaluate-recordings.mjs +128 -0
  66. package/dist/gateway-evaluate-recordings.mjs.map +1 -0
  67. package/dist/gateway.d.mts +272 -0
  68. package/dist/gateway.d.mts.map +1 -0
  69. package/dist/gateway.mjs +400 -0
  70. package/dist/gateway.mjs.map +1 -0
  71. package/dist/github/api.d.mts +63 -0
  72. package/dist/github/api.d.mts.map +1 -0
  73. package/dist/github/api.mjs +527 -0
  74. package/dist/github/api.mjs.map +1 -0
  75. package/dist/github/state.d.mts +272 -0
  76. package/dist/github/state.d.mts.map +1 -0
  77. package/dist/github/state.mjs +303 -0
  78. package/dist/github/state.mjs.map +1 -0
  79. package/dist/github.d.mts +77 -0
  80. package/dist/github.d.mts.map +1 -0
  81. package/dist/github.mjs +180 -0
  82. package/dist/github.mjs.map +1 -0
  83. package/dist/google/calendar.d.mts +10 -0
  84. package/dist/google/calendar.d.mts.map +1 -0
  85. package/dist/google/calendar.mjs +252 -0
  86. package/dist/google/calendar.mjs.map +1 -0
  87. package/dist/google/drive.d.mts +8 -0
  88. package/dist/google/drive.d.mts.map +1 -0
  89. package/dist/google/drive.mjs +252 -0
  90. package/dist/google/drive.mjs.map +1 -0
  91. package/dist/google/gmail.d.mts +10 -0
  92. package/dist/google/gmail.d.mts.map +1 -0
  93. package/dist/google/gmail.mjs +591 -0
  94. package/dist/google/gmail.mjs.map +1 -0
  95. package/dist/google/shared.d.mts +107 -0
  96. package/dist/google/shared.d.mts.map +1 -0
  97. package/dist/google/shared.mjs +146 -0
  98. package/dist/google/shared.mjs.map +1 -0
  99. package/dist/google/state.d.mts +385 -0
  100. package/dist/google/state.d.mts.map +1 -0
  101. package/dist/google/state.mjs +496 -0
  102. package/dist/google/state.mjs.map +1 -0
  103. package/dist/google.d.mts +75 -0
  104. package/dist/google.d.mts.map +1 -0
  105. package/dist/google.mjs +210 -0
  106. package/dist/google.mjs.map +1 -0
  107. package/dist/linkedin-search/api.d.mts +45 -0
  108. package/dist/linkedin-search/api.d.mts.map +1 -0
  109. package/dist/linkedin-search/api.mjs +181 -0
  110. package/dist/linkedin-search/api.mjs.map +1 -0
  111. package/dist/linkedin-search/state.d.mts +123 -0
  112. package/dist/linkedin-search/state.d.mts.map +1 -0
  113. package/dist/linkedin-search/state.mjs +261 -0
  114. package/dist/linkedin-search/state.mjs.map +1 -0
  115. package/dist/linkedin-search.d.mts +69 -0
  116. package/dist/linkedin-search.d.mts.map +1 -0
  117. package/dist/linkedin-search.mjs +158 -0
  118. package/dist/linkedin-search.mjs.map +1 -0
  119. package/dist/mcp/api.d.mts +44 -0
  120. package/dist/mcp/api.d.mts.map +1 -0
  121. package/dist/mcp/api.mjs +557 -0
  122. package/dist/mcp/api.mjs.map +1 -0
  123. package/dist/mcp/recordings.d.mts +40 -0
  124. package/dist/mcp/recordings.d.mts.map +1 -0
  125. package/dist/mcp/recordings.mjs +2390 -0
  126. package/dist/mcp/recordings.mjs.map +1 -0
  127. package/dist/mcp/state.d.mts +71 -0
  128. package/dist/mcp/state.d.mts.map +1 -0
  129. package/dist/mcp/state.mjs +83 -0
  130. package/dist/mcp/state.mjs.map +1 -0
  131. package/dist/mcp.d.mts +84 -0
  132. package/dist/mcp.d.mts.map +1 -0
  133. package/dist/mcp.mjs +241 -0
  134. package/dist/mcp.mjs.map +1 -0
  135. package/dist/messages.d.mts +183 -0
  136. package/dist/messages.d.mts.map +1 -0
  137. package/dist/messages.mjs +532 -0
  138. package/dist/messages.mjs.map +1 -0
  139. package/dist/microsoft/api.d.mts +47 -0
  140. package/dist/microsoft/api.d.mts.map +1 -0
  141. package/dist/microsoft/api.mjs +178 -0
  142. package/dist/microsoft/api.mjs.map +1 -0
  143. package/dist/microsoft/calendar.d.mts +30 -0
  144. package/dist/microsoft/calendar.d.mts.map +1 -0
  145. package/dist/microsoft/calendar.mjs +271 -0
  146. package/dist/microsoft/calendar.mjs.map +1 -0
  147. package/dist/microsoft/drive.d.mts +36 -0
  148. package/dist/microsoft/drive.d.mts.map +1 -0
  149. package/dist/microsoft/drive.mjs +298 -0
  150. package/dist/microsoft/drive.mjs.map +1 -0
  151. package/dist/microsoft/graph.d.mts +198 -0
  152. package/dist/microsoft/graph.d.mts.map +1 -0
  153. package/dist/microsoft/graph.mjs +264 -0
  154. package/dist/microsoft/graph.mjs.map +1 -0
  155. package/dist/microsoft/mail.d.mts +48 -0
  156. package/dist/microsoft/mail.d.mts.map +1 -0
  157. package/dist/microsoft/mail.mjs +488 -0
  158. package/dist/microsoft/mail.mjs.map +1 -0
  159. package/dist/microsoft/state.d.mts +480 -0
  160. package/dist/microsoft/state.d.mts.map +1 -0
  161. package/dist/microsoft/state.mjs +625 -0
  162. package/dist/microsoft/state.mjs.map +1 -0
  163. package/dist/microsoft.d.mts +168 -0
  164. package/dist/microsoft.d.mts.map +1 -0
  165. package/dist/microsoft.mjs +483 -0
  166. package/dist/microsoft.mjs.map +1 -0
  167. package/dist/node.d.mts +47 -0
  168. package/dist/node.d.mts.map +1 -0
  169. package/dist/node.mjs +178 -0
  170. package/dist/node.mjs.map +1 -0
  171. package/dist/notion/api.d.mts +51 -0
  172. package/dist/notion/api.d.mts.map +1 -0
  173. package/dist/notion/api.mjs +507 -0
  174. package/dist/notion/api.mjs.map +1 -0
  175. package/dist/notion/state.d.mts +329 -0
  176. package/dist/notion/state.d.mts.map +1 -0
  177. package/dist/notion/state.mjs +432 -0
  178. package/dist/notion/state.mjs.map +1 -0
  179. package/dist/notion.d.mts +72 -0
  180. package/dist/notion.d.mts.map +1 -0
  181. package/dist/notion.mjs +127 -0
  182. package/dist/notion.mjs.map +1 -0
  183. package/dist/openai.d.mts +165 -0
  184. package/dist/openai.d.mts.map +1 -0
  185. package/dist/openai.mjs +141 -0
  186. package/dist/openai.mjs.map +1 -0
  187. package/dist/opencode-recordings.d.mts +7 -0
  188. package/dist/opencode-recordings.d.mts.map +1 -0
  189. package/dist/opencode-recordings.mjs +222 -0
  190. package/dist/opencode-recordings.mjs.map +1 -0
  191. package/dist/opencode.d.mts +70 -0
  192. package/dist/opencode.d.mts.map +1 -0
  193. package/dist/opencode.mjs +217 -0
  194. package/dist/opencode.mjs.map +1 -0
  195. package/dist/r2-fixtures.d.mts +36 -0
  196. package/dist/r2-fixtures.d.mts.map +1 -0
  197. package/dist/r2-fixtures.mjs +245 -0
  198. package/dist/r2-fixtures.mjs.map +1 -0
  199. package/dist/r2-guard.d.mts +43 -0
  200. package/dist/r2-guard.d.mts.map +1 -0
  201. package/dist/r2-guard.mjs +215 -0
  202. package/dist/r2-guard.mjs.map +1 -0
  203. package/dist/r2.d.mts +166 -0
  204. package/dist/r2.d.mts.map +1 -0
  205. package/dist/r2.mjs +517 -0
  206. package/dist/r2.mjs.map +1 -0
  207. package/dist/responses.d.mts +231 -0
  208. package/dist/responses.d.mts.map +1 -0
  209. package/dist/responses.mjs +556 -0
  210. package/dist/responses.mjs.map +1 -0
  211. package/dist/route-evidence.d.mts +45 -0
  212. package/dist/route-evidence.d.mts.map +1 -0
  213. package/dist/route-evidence.mjs +56 -0
  214. package/dist/route-evidence.mjs.map +1 -0
  215. package/dist/router.d.mts +93 -0
  216. package/dist/router.d.mts.map +1 -0
  217. package/dist/router.mjs +259 -0
  218. package/dist/router.mjs.map +1 -0
  219. package/dist/stateful-core.d.mts +17 -0
  220. package/dist/stateful-core.d.mts.map +1 -0
  221. package/dist/stateful-core.mjs +41 -0
  222. package/dist/stateful-core.mjs.map +1 -0
  223. package/dist/stateful-emulator.d.mts +565 -0
  224. package/dist/stateful-emulator.d.mts.map +1 -0
  225. package/dist/stateful-emulator.mjs +1228 -0
  226. package/dist/stateful-emulator.mjs.map +1 -0
  227. package/dist/stateful-secrets.d.mts +84 -0
  228. package/dist/stateful-secrets.d.mts.map +1 -0
  229. package/dist/stateful-secrets.mjs +216 -0
  230. package/dist/stateful-secrets.mjs.map +1 -0
  231. package/dist/subscription-usage-recordings.d.mts +9 -0
  232. package/dist/subscription-usage-recordings.d.mts.map +1 -0
  233. package/dist/subscription-usage-recordings.mjs +53 -0
  234. package/dist/subscription-usage-recordings.mjs.map +1 -0
  235. package/dist/subscription-usage.d.mts +53 -0
  236. package/dist/subscription-usage.d.mts.map +1 -0
  237. package/dist/subscription-usage.mjs +21 -0
  238. package/dist/subscription-usage.mjs.map +1 -0
  239. package/dist/telegram/api.d.mts +34 -0
  240. package/dist/telegram/api.d.mts.map +1 -0
  241. package/dist/telegram/api.mjs +257 -0
  242. package/dist/telegram/api.mjs.map +1 -0
  243. package/dist/telegram/state.d.mts +128 -0
  244. package/dist/telegram/state.d.mts.map +1 -0
  245. package/dist/telegram/state.mjs +154 -0
  246. package/dist/telegram/state.mjs.map +1 -0
  247. package/dist/telegram.d.mts +62 -0
  248. package/dist/telegram.d.mts.map +1 -0
  249. package/dist/telegram.mjs +127 -0
  250. package/dist/telegram.mjs.map +1 -0
  251. package/dist/todoist/api.d.mts +55 -0
  252. package/dist/todoist/api.d.mts.map +1 -0
  253. package/dist/todoist/api.mjs +415 -0
  254. package/dist/todoist/api.mjs.map +1 -0
  255. package/dist/todoist/state.d.mts +275 -0
  256. package/dist/todoist/state.d.mts.map +1 -0
  257. package/dist/todoist/state.mjs +345 -0
  258. package/dist/todoist/state.mjs.map +1 -0
  259. package/dist/todoist.d.mts +68 -0
  260. package/dist/todoist.d.mts.map +1 -0
  261. package/dist/todoist.mjs +181 -0
  262. package/dist/todoist.mjs.map +1 -0
  263. package/dist/xai.d.mts +267 -0
  264. package/dist/xai.d.mts.map +1 -0
  265. package/dist/xai.mjs +252 -0
  266. package/dist/xai.mjs.map +1 -0
  267. package/package.json +157 -0
  268. package/src/anthropic.ts +289 -0
  269. package/src/chat-completions.ts +924 -0
  270. package/src/codex.ts +296 -0
  271. package/src/dropbox/api.ts +1084 -0
  272. package/src/dropbox/state.ts +364 -0
  273. package/src/dropbox.ts +203 -0
  274. package/src/email-fixtures.ts +1297 -0
  275. package/src/email.ts +1108 -0
  276. package/src/emulator-compose.ts +147 -0
  277. package/src/emulator-http.ts +184 -0
  278. package/src/emulator-kernel.ts +844 -0
  279. package/src/fixture-route.ts +561 -0
  280. package/src/fortnox/api.ts +1352 -0
  281. package/src/fortnox/state.ts +798 -0
  282. package/src/fortnox.ts +801 -0
  283. package/src/gateway-evaluate-recordings.ts +153 -0
  284. package/src/gateway.ts +530 -0
  285. package/src/github/api.ts +986 -0
  286. package/src/github/state.ts +439 -0
  287. package/src/github.ts +271 -0
  288. package/src/google/calendar.ts +486 -0
  289. package/src/google/drive.ts +471 -0
  290. package/src/google/gmail.ts +1011 -0
  291. package/src/google/shared.ts +321 -0
  292. package/src/google/state.ts +686 -0
  293. package/src/google.ts +298 -0
  294. package/src/linkedin-search/api.ts +363 -0
  295. package/src/linkedin-search/state.ts +373 -0
  296. package/src/linkedin-search.ts +241 -0
  297. package/src/mcp/api.ts +995 -0
  298. package/src/mcp/recordings.ts +2665 -0
  299. package/src/mcp/state.ts +125 -0
  300. package/src/mcp.ts +319 -0
  301. package/src/messages.ts +844 -0
  302. package/src/microsoft/api.ts +426 -0
  303. package/src/microsoft/calendar.ts +491 -0
  304. package/src/microsoft/drive.ts +541 -0
  305. package/src/microsoft/graph.ts +550 -0
  306. package/src/microsoft/mail.ts +833 -0
  307. package/src/microsoft/state.ts +822 -0
  308. package/src/microsoft.ts +982 -0
  309. package/src/node.ts +293 -0
  310. package/src/notion/api.ts +976 -0
  311. package/src/notion/state.ts +512 -0
  312. package/src/notion.ts +210 -0
  313. package/src/openai.ts +198 -0
  314. package/src/opencode-recordings.ts +257 -0
  315. package/src/opencode.ts +271 -0
  316. package/src/r2-fixtures.ts +295 -0
  317. package/src/r2-guard.ts +323 -0
  318. package/src/r2.ts +890 -0
  319. package/src/responses.ts +901 -0
  320. package/src/route-evidence.ts +90 -0
  321. package/src/router.ts +490 -0
  322. package/src/stateful-core.ts +70 -0
  323. package/src/stateful-emulator.ts +2571 -0
  324. package/src/stateful-secrets.ts +299 -0
  325. package/src/subscription-usage-recordings.ts +77 -0
  326. package/src/subscription-usage.ts +68 -0
  327. package/src/telegram/api.ts +437 -0
  328. package/src/telegram/state.ts +229 -0
  329. package/src/telegram.ts +227 -0
  330. package/src/todoist/api.ts +736 -0
  331. package/src/todoist/state.ts +456 -0
  332. package/src/todoist.ts +316 -0
  333. package/src/xai.ts +341 -0
@@ -0,0 +1,45 @@
1
+ //#region src/route-evidence.d.ts
2
+ /** How well an emulated route's wire shape is backed by live observation. */
3
+ type EmulatorEvidence = 'verified' | 'unverified';
4
+ /** One emulated route and the conformance evidence behind its wire shape. */
5
+ type EmulatorRouteEvidence = {
6
+ readonly method: string; /** Path template, for example `/v1/chat/completions` or `/3/invoices/{DocumentNumber}`. */
7
+ readonly path: string; /** `provider` for model/provider APIs, `connector` for connector APIs. */
8
+ readonly kind: 'provider' | 'connector'; /** True when the route changes state in the real service. */
9
+ readonly write: boolean; /** Conformance case ids whose wire claims this route follows. */
10
+ readonly caseIds: ReadonlyArray<string>;
11
+ readonly evidence: EmulatorEvidence; /** Calendar date (`YYYY-MM-DD`, UTC) of the live observation behind `verified` evidence. */
12
+ readonly observedAt?: string | undefined;
13
+ };
14
+ /** Response header carried by every response from an unverified route. */
15
+ declare const emulatorEvidenceHeader = "x-emulator-evidence";
16
+ /** `METHOD /path` key for a manifest route. */
17
+ declare const emulatorRouteKey: (method: string, path: string) => string;
18
+ declare const EmulatorRouteUnmapped_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
19
+ readonly _tag: "EmulatorRouteUnmapped";
20
+ } & Readonly<A>;
21
+ /**
22
+ * An emulator's manifest and its handlers disagree: a manifest route has no
23
+ * handler, or a handler has no manifest route. A bug in the emulator, thrown
24
+ * when the emulator is constructed.
25
+ */
26
+ declare class EmulatorRouteUnmapped extends EmulatorRouteUnmapped_base<{
27
+ readonly route: string;
28
+ readonly problem: 'no-handler' | 'no-manifest-route';
29
+ }> {
30
+ get message(): string;
31
+ }
32
+ type BoundEmulatorRoute<H> = {
33
+ readonly route: EmulatorRouteEvidence;
34
+ readonly handler: H;
35
+ };
36
+ /**
37
+ * Pair every manifest route with its own handler (keyed by
38
+ * `emulatorRouteKey`). Throws `EmulatorRouteUnmapped` when a manifest route
39
+ * has no handler or a handler has no manifest route, so a manifest entry can
40
+ * never be served by another route's handler.
41
+ */
42
+ declare const bindRouteHandlers: <H>(routes: ReadonlyArray<EmulatorRouteEvidence>, handlers: ReadonlyMap<string, H>) => ReadonlyArray<BoundEmulatorRoute<H>>;
43
+ //#endregion
44
+ export { BoundEmulatorRoute, EmulatorEvidence, EmulatorRouteEvidence, EmulatorRouteUnmapped, bindRouteHandlers, emulatorEvidenceHeader, emulatorRouteKey };
45
+ //# sourceMappingURL=route-evidence.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-evidence.d.mts","names":[],"sources":["../src/route-evidence.ts"],"mappings":";;KAaY,gBAAA;;KAGA,qBAAA;EAAA,SACD,MAAA,UAJiB;EAAA,SAMjB,IAAA,UAHsB;EAAA,SAKtB,IAAA,4BAK0B;EAAA,SAH1B,KAAA,WAJA;EAAA,SAMA,OAAA,EAAS,aAAA;EAAA,SACT,QAAA,EAAU,gBAAgB,EAD1B;EAAA,SAGA,UAAA;AAAA;;cAIE,sBAAA;;cAGA,gBAAA,GAAoB,MAAA,UAAgB,IAAY;AAAA,cAC1B,0BAAA;;;;AAJA;AAGnC;;;cAQa,qBAAA,SAA8B,0BAAA;EAAA,SAChC,KAAA;EAAA,SACA,OAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,KAOH,kBAAA;EAAA,SACD,KAAA,EAAO,qBAAA;EAAA,SACP,OAAA,EAAS,CAAC;AAAA;;;;;;;cASR,iBAAA,MACX,MAAA,EAAQ,aAAA,CAAc,qBAAA,GACtB,QAAA,EAAU,WAAA,SAAoB,CAAA,MAC7B,aAAA,CAAc,kBAAA,CAAmB,CAAA"}
@@ -0,0 +1,56 @@
1
+ import { Data } from "effect";
2
+ //#region src/route-evidence.ts
3
+ /**
4
+ * Route evidence manifests shared by the emulators (internal; re-exported as
5
+ * types by each emulator subpath).
6
+ *
7
+ * Emulators never import SDK code. Each emulated route instead names the
8
+ * conformance case ids whose recorded wire shapes it follows, and how well
9
+ * that shape is backed: `verified` routes follow a live observation (dated by
10
+ * `observedAt`), `unverified` routes follow synthetic placeholders. The repo
11
+ * evidence check (`pnpm packages:evidence`) validates the case ids.
12
+ */
13
+ /** Response header carried by every response from an unverified route. */
14
+ const emulatorEvidenceHeader = "x-emulator-evidence";
15
+ /** `METHOD /path` key for a manifest route. */
16
+ const emulatorRouteKey = (method, path) => `${method.toUpperCase()} ${path}`;
17
+ /**
18
+ * An emulator's manifest and its handlers disagree: a manifest route has no
19
+ * handler, or a handler has no manifest route. A bug in the emulator, thrown
20
+ * when the emulator is constructed.
21
+ */
22
+ var EmulatorRouteUnmapped = class extends Data.TaggedError("EmulatorRouteUnmapped") {
23
+ get message() {
24
+ return this.problem === "no-handler" ? `Emulator manifest route ${this.route} has no handler` : `Emulator handler ${this.route} has no manifest route`;
25
+ }
26
+ };
27
+ /**
28
+ * Pair every manifest route with its own handler (keyed by
29
+ * `emulatorRouteKey`). Throws `EmulatorRouteUnmapped` when a manifest route
30
+ * has no handler or a handler has no manifest route, so a manifest entry can
31
+ * never be served by another route's handler.
32
+ */
33
+ const bindRouteHandlers = (routes, handlers) => {
34
+ const bound = routes.map((route) => {
35
+ const key = emulatorRouteKey(route.method, route.path);
36
+ const handler = handlers.get(key);
37
+ if (handler === void 0) throw new EmulatorRouteUnmapped({
38
+ route: key,
39
+ problem: "no-handler"
40
+ });
41
+ return {
42
+ route,
43
+ handler
44
+ };
45
+ });
46
+ const manifestKeys = new Set(routes.map((route) => emulatorRouteKey(route.method, route.path)));
47
+ for (const key of handlers.keys()) if (!manifestKeys.has(key)) throw new EmulatorRouteUnmapped({
48
+ route: key,
49
+ problem: "no-manifest-route"
50
+ });
51
+ return bound;
52
+ };
53
+ //#endregion
54
+ export { EmulatorRouteUnmapped, bindRouteHandlers, emulatorEvidenceHeader, emulatorRouteKey };
55
+
56
+ //# sourceMappingURL=route-evidence.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-evidence.mjs","names":[],"sources":["../src/route-evidence.ts"],"sourcesContent":["/**\n * Route evidence manifests shared by the emulators (internal; re-exported as\n * types by each emulator subpath).\n *\n * Emulators never import SDK code. Each emulated route instead names the\n * conformance case ids whose recorded wire shapes it follows, and how well\n * that shape is backed: `verified` routes follow a live observation (dated by\n * `observedAt`), `unverified` routes follow synthetic placeholders. The repo\n * evidence check (`pnpm packages:evidence`) validates the case ids.\n */\nimport { Data } from 'effect'\n\n/** How well an emulated route's wire shape is backed by live observation. */\nexport type EmulatorEvidence = 'verified' | 'unverified'\n\n/** One emulated route and the conformance evidence behind its wire shape. */\nexport type EmulatorRouteEvidence = {\n readonly method: string\n /** Path template, for example `/v1/chat/completions` or `/3/invoices/{DocumentNumber}`. */\n readonly path: string\n /** `provider` for model/provider APIs, `connector` for connector APIs. */\n readonly kind: 'provider' | 'connector'\n /** True when the route changes state in the real service. */\n readonly write: boolean\n /** Conformance case ids whose wire claims this route follows. */\n readonly caseIds: ReadonlyArray<string>\n readonly evidence: EmulatorEvidence\n /** Calendar date (`YYYY-MM-DD`, UTC) of the live observation behind `verified` evidence. */\n readonly observedAt?: string | undefined\n}\n\n/** Response header carried by every response from an unverified route. */\nexport const emulatorEvidenceHeader = 'x-emulator-evidence'\n\n/** `METHOD /path` key for a manifest route. */\nexport const emulatorRouteKey = (method: string, path: string): string =>\n `${method.toUpperCase()} ${path}`\n\n/**\n * An emulator's manifest and its handlers disagree: a manifest route has no\n * handler, or a handler has no manifest route. A bug in the emulator, thrown\n * when the emulator is constructed.\n */\nexport class EmulatorRouteUnmapped extends Data.TaggedError('EmulatorRouteUnmapped')<{\n readonly route: string\n readonly problem: 'no-handler' | 'no-manifest-route'\n}> {\n override get message(): string {\n return this.problem === 'no-handler'\n ? `Emulator manifest route ${this.route} has no handler`\n : `Emulator handler ${this.route} has no manifest route`\n }\n}\n\nexport type BoundEmulatorRoute<H> = {\n readonly route: EmulatorRouteEvidence\n readonly handler: H\n}\n\n/**\n * Pair every manifest route with its own handler (keyed by\n * `emulatorRouteKey`). Throws `EmulatorRouteUnmapped` when a manifest route\n * has no handler or a handler has no manifest route, so a manifest entry can\n * never be served by another route's handler.\n */\nexport const bindRouteHandlers = <H>(\n routes: ReadonlyArray<EmulatorRouteEvidence>,\n handlers: ReadonlyMap<string, H>\n): ReadonlyArray<BoundEmulatorRoute<H>> => {\n const bound = routes.map(route => {\n const key = emulatorRouteKey(route.method, route.path)\n const handler = handlers.get(key)\n\n if (handler === undefined) {\n throw new EmulatorRouteUnmapped({ route: key, problem: 'no-handler' })\n }\n\n return { route, handler }\n })\n\n const manifestKeys = new Set(routes.map(route => emulatorRouteKey(route.method, route.path)))\n\n for (const key of handlers.keys()) {\n if (!manifestKeys.has(key)) {\n throw new EmulatorRouteUnmapped({ route: key, problem: 'no-manifest-route' })\n }\n }\n\n return bound\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB,QAAgB,SAC/C,GAAG,OAAO,YAAY,EAAE,GAAG;;;;;;AAO7B,IAAa,wBAAb,cAA2C,KAAK,YAAY,uBAAuB,EAGhF;CACD,IAAa,UAAkB;EAC7B,OAAO,KAAK,YAAY,eACpB,2BAA2B,KAAK,MAAM,mBACtC,oBAAoB,KAAK,MAAM;CACrC;AACF;;;;;;;AAaA,MAAa,qBACX,QACA,aACyC;CACzC,MAAM,QAAQ,OAAO,KAAI,UAAS;EAChC,MAAM,MAAM,iBAAiB,MAAM,QAAQ,MAAM,IAAI;EACrD,MAAM,UAAU,SAAS,IAAI,GAAG;EAEhC,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,sBAAsB;GAAE,OAAO;GAAK,SAAS;EAAa,CAAC;EAGvE,OAAO;GAAE;GAAO;EAAQ;CAC1B,CAAC;CAED,MAAM,eAAe,IAAI,IAAI,OAAO,KAAI,UAAS,iBAAiB,MAAM,QAAQ,MAAM,IAAI,CAAC,CAAC;CAE5F,KAAK,MAAM,OAAO,SAAS,KAAK,GAC9B,IAAI,CAAC,aAAa,IAAI,GAAG,GACvB,MAAM,IAAI,sBAAsB;EAAE,OAAO;EAAK,SAAS;CAAoB,CAAC;CAIhF,OAAO;AACT"}
@@ -0,0 +1,93 @@
1
+ import { Layer } from "effect";
2
+ import { HttpClient } from "effect/unstable/http";
3
+
4
+ //#region src/router.d.ts
5
+ /** A fetch handler, such as the one returned by an emulator factory. */
6
+ type EmulatorFetch = (request: Request) => Promise<Response>;
7
+ /** A loopback emulator process (http or https on 127.0.0.0/8, `::1`, or `localhost`). */
8
+ type EmulatorUrlTarget = {
9
+ readonly kind: 'url';
10
+ readonly baseUrl: string;
11
+ };
12
+ /** An emulator fetch handler called in-process. */
13
+ type EmulatorHandlerTarget = {
14
+ readonly kind: 'handler';
15
+ readonly fetch: EmulatorFetch;
16
+ };
17
+ type EmulatorTarget = EmulatorUrlTarget | EmulatorHandlerTarget;
18
+ /**
19
+ * Maps one real origin (for example `https://ai-gateway.vercel.sh`) to an
20
+ * emulator target. Path and query of each request are kept.
21
+ */
22
+ type EmulatorRoute<T extends EmulatorTarget = EmulatorTarget> = {
23
+ readonly origin: string;
24
+ readonly target: T;
25
+ };
26
+ declare const EmulatorRoute: {
27
+ /** Route an origin to a loopback emulator process. */readonly url: (origin: string, baseUrl: string) => EmulatorRoute<EmulatorUrlTarget>; /** Route an origin to an in-process emulator fetch handler. */
28
+ readonly handler: (origin: string, fetch: EmulatorFetch) => EmulatorRoute<EmulatorHandlerTarget>;
29
+ };
30
+ declare const EmulatorRouteInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
31
+ readonly _tag: "EmulatorRouteInvalid";
32
+ } & Readonly<A>;
33
+ /** A route table that cannot be used: bad origin, duplicate origin, wrong target, or non-loopback base URL. */
34
+ declare class EmulatorRouteInvalid extends EmulatorRouteInvalid_base<{
35
+ readonly reason: string;
36
+ }> {
37
+ get message(): string;
38
+ }
39
+ declare const EmulatorEnvironmentRefused_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
40
+ readonly _tag: "EmulatorEnvironmentRefused";
41
+ } & Readonly<A>;
42
+ /** The router refused to build: `NODE_ENV` is `production`, or it could not be read. */
43
+ declare class EmulatorEnvironmentRefused extends EmulatorEnvironmentRefused_base<{
44
+ readonly reason: 'production' | 'unreadable';
45
+ }> {
46
+ get message(): string;
47
+ }
48
+ type EmulatorRouterError = EmulatorRouteInvalid | EmulatorEnvironmentRefused;
49
+ declare const EmulatedHttpClient: {
50
+ /**
51
+ * `HttpClient` that rewrites each routed origin to its loopback emulator
52
+ * base URL (keeping path and query) and sends it through the host's real
53
+ * `HttpClient` underneath. Requests to any other origin fail closed with an
54
+ * `HttpClientError` that names only the origin.
55
+ *
56
+ * The check and rewrite run at the send step (the client's postprocess), so
57
+ * redirect follow-ups (`HttpClient.followRedirects` on top) and requests
58
+ * changed by a host's `HttpClient.mapRequest` are routed or refused too.
59
+ * With `FetchHttpClient` underneath, requests are sent with
60
+ * `redirect: 'manual'`: a 3xx from an emulator comes back to the caller as
61
+ * a 3xx and native fetch never follows it. Other `RequestInit` defaults are
62
+ * kept when they are provided where this layer is built or where the request
63
+ * runs; defaults provided only to the underlying `FetchHttpClient.layer`
64
+ * itself are replaced, so provide them around the whole client stack. Put
65
+ * `HttpClient.followRedirects` on top of this layer, never underneath it: a
66
+ * redirect loop below the route check sends follow-ups without it. Any
67
+ * other underlying client must not follow redirects by itself, because a
68
+ * redirect it follows internally never passes through the route table.
69
+ *
70
+ * Building fails with `EmulatorRouteInvalid` when a route is malformed,
71
+ * duplicated, not a `url` target, or not on loopback, and with
72
+ * `EmulatorEnvironmentRefused` when `NODE_ENV` is `production` or cannot be read.
73
+ */
74
+ readonly layer: (routes: ReadonlyArray<EmulatorRoute<EmulatorUrlTarget>>) => Layer.Layer<HttpClient.HttpClient, EmulatorRouterError, HttpClient.HttpClient>;
75
+ };
76
+ declare const InProcessHttpClient: {
77
+ /**
78
+ * `HttpClient` that turns each request to a routed origin into a web
79
+ * `Request` (method, URL, headers, and an Empty, Uint8Array, or string Raw
80
+ * body) and calls that route's fetch handler directly; the web `Response`
81
+ * comes back through `HttpClientResponse.fromWeb`. Other body kinds fail
82
+ * with an `HttpClientError` (`EncodeError`). Unknown origins fail closed
83
+ * with an `HttpClientError` that names only the origin.
84
+ *
85
+ * Building fails with `EmulatorRouteInvalid` for malformed, duplicated, or
86
+ * non-`handler` routes and with `EmulatorEnvironmentRefused` when
87
+ * `NODE_ENV` is `production` or cannot be read.
88
+ */
89
+ readonly layer: (routes: ReadonlyArray<EmulatorRoute<EmulatorHandlerTarget>>) => Layer.Layer<HttpClient.HttpClient, EmulatorRouterError>;
90
+ };
91
+ //#endregion
92
+ export { EmulatedHttpClient, EmulatorEnvironmentRefused, EmulatorFetch, EmulatorHandlerTarget, EmulatorRoute, EmulatorRouteInvalid, EmulatorRouterError, EmulatorTarget, EmulatorUrlTarget, InProcessHttpClient };
93
+ //# sourceMappingURL=router.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.mts","names":[],"sources":["../src/router.ts"],"mappings":";;;;AA0BkE;AAAA,KAAtD,aAAA,IAAiB,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA;;KAG9C,iBAAA;EAAA,SACD,IAAA;EAAA,SACA,OAAO;AAAA;;KAIN,qBAAA;EAAA,SACD,IAAA;EAAA,SACA,KAAA,EAAO,aAAa;AAAA;AAAA,KAGnB,cAAA,GAAiB,iBAAA,GAAoB,qBAAqB;;AAHvC;AAG/B;;KAMY,aAAA,WAAwB,cAAA,GAAiB,cAAA;EAAA,SAC1C,MAAA;EAAA,SACA,MAAA,EAAQ,CAAA;AAAA;AAAA,cAGN,aAAA;EALY,qFAOH,OAAA,aAAoB,aAAA,CAAc,iBAAA,GAPH;EAAA,mCAY3B,KAAA,EAAS,aAAA,KAAgB,aAAA,CAAc,qBAAA;AAAA;AAAA,cAIvD,yBAAA;;;;cAGG,oBAAA,SAA6B,yBAAA;EAAA,SAC/B,MAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,cAGd,+BAAA;;;;cAGY,0BAAA,SAAmC,+BAAA;EAAA,SACrC,MAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,KAOH,mBAAA,GAAsB,oBAAA,GAAuB,0BAA0B;AAAA,cAuOtE,kBAAA;;;;;;;;;;;;AAjQyE;AAI5E;;;;;;;;;;;;2BAuRE,aAAA,CAAc,aAAA,CAAc,iBAAA,OACnC,KAAA,CAAM,KAAA,CAAM,UAAA,CAAW,UAAA,EAAY,mBAAA,EAAqB,UAAA,CAAW,UAAA;AAAA;AAAA,cAqE3D,mBAAA;;;;;;;;;;;;;2BAcD,aAAA,CAAc,aAAA,CAAc,qBAAA,OACnC,KAAA,CAAM,KAAA,CAAM,UAAA,CAAW,UAAA,EAAY,mBAAA;AAAA"}
@@ -0,0 +1,259 @@
1
+ import { Config, Data, Effect, Layer, Match, Option, Predicate } from "effect";
2
+ import { FetchHttpClient, Headers as Headers$1, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
3
+ //#region src/router.ts
4
+ /**
5
+ * Effect `HttpClient` routing to emulators.
6
+ *
7
+ * Hosts pick a transport by swapping `HttpClient` layers: `EmulatedHttpClient`
8
+ * rewrites real origins to loopback emulator processes over the host's own
9
+ * client, and `InProcessHttpClient` calls emulator fetch handlers directly.
10
+ * Both fail closed on unknown origins and refuse to build when `NODE_ENV` is
11
+ * `production`. The route check runs at the send step, so every request that
12
+ * is actually sent (including redirect follow-ups and requests a host changes
13
+ * with `HttpClient.mapRequest` on top) goes through the route table. Replay
14
+ * lives in `@yolk-sdk/conformance/replay`; live traffic is the host's own
15
+ * client.
16
+ *
17
+ * @experimental
18
+ */
19
+ const EmulatorRoute = {
20
+ /** Route an origin to a loopback emulator process. */
21
+ url: (origin, baseUrl) => ({
22
+ origin,
23
+ target: {
24
+ kind: "url",
25
+ baseUrl
26
+ }
27
+ }),
28
+ /** Route an origin to an in-process emulator fetch handler. */
29
+ handler: (origin, fetch) => ({
30
+ origin,
31
+ target: {
32
+ kind: "handler",
33
+ fetch
34
+ }
35
+ })
36
+ };
37
+ /** A route table that cannot be used: bad origin, duplicate origin, wrong target, or non-loopback base URL. */
38
+ var EmulatorRouteInvalid = class extends Data.TaggedError("EmulatorRouteInvalid") {
39
+ get message() {
40
+ return `Invalid emulator route: ${this.reason}`;
41
+ }
42
+ };
43
+ /** The router refused to build: `NODE_ENV` is `production`, or it could not be read. */
44
+ var EmulatorEnvironmentRefused = class extends Data.TaggedError("EmulatorEnvironmentRefused") {
45
+ get message() {
46
+ return this.reason === "production" ? "Emulator routing refuses to run when NODE_ENV is production" : "Emulator routing could not read NODE_ENV";
47
+ }
48
+ };
49
+ /**
50
+ * Fails when `NODE_ENV` is `production`. A missing `NODE_ENV` is allowed; an
51
+ * unreadable one fails closed.
52
+ */
53
+ const refuseProductionEnvironment = Effect.gen(function* () {
54
+ const nodeEnv = yield* Config.option(Config.String("NODE_ENV"));
55
+ return Option.getOrUndefined(nodeEnv);
56
+ }).pipe(Effect.mapError(() => new EmulatorEnvironmentRefused({ reason: "unreadable" })), Effect.flatMap((nodeEnv) => nodeEnv?.trim().toLowerCase() === "production" ? Effect.fail(new EmulatorEnvironmentRefused({ reason: "production" })) : Effect.void));
57
+ const ipv4LoopbackPattern = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
58
+ /**
59
+ * True for `http:`/`https:` URLs whose host is loopback: `localhost`, `::1`,
60
+ * or an IPv4 address in 127.0.0.0/8 (after WHATWG URL normalization).
61
+ * IPv4-mapped IPv6 forms such as `[::ffff:127.0.0.1]` are rejected: the
62
+ * allowlist stays minimal, and `127.0.0.1` says the same thing.
63
+ */
64
+ const isLoopbackUrl = (input) => {
65
+ if (!URL.canParse(input)) return false;
66
+ const url = new URL(input);
67
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
68
+ const host = url.hostname.toLowerCase();
69
+ return host === "localhost" || host === "[::1]" || ipv4LoopbackPattern.test(host);
70
+ };
71
+ const validOrigin = (origin) => {
72
+ if (!URL.canParse(origin)) return new EmulatorRouteInvalid({ reason: "a route origin is not an absolute URL" });
73
+ const url = new URL(origin);
74
+ if (url.protocol !== "http:" && url.protocol !== "https:") return new EmulatorRouteInvalid({ reason: `origin ${url.origin} must be http or https` });
75
+ if (url.username !== "" || url.password !== "" || url.pathname !== "/" && url.pathname !== "" || url.search !== "" || url.hash !== "") return new EmulatorRouteInvalid({ reason: `origin ${url.origin} must be a bare origin without credentials, path, query, or hash` });
76
+ return url.origin;
77
+ };
78
+ const validBaseUrl = (origin, baseUrl) => {
79
+ if (!isLoopbackUrl(baseUrl)) return new EmulatorRouteInvalid({ reason: `the base URL for ${origin} must be http(s) on loopback (127.0.0.0/8, ::1, or localhost)` });
80
+ const url = new URL(baseUrl);
81
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") return new EmulatorRouteInvalid({ reason: `the base URL for ${origin} must not carry credentials, a query, or a hash` });
82
+ return url;
83
+ };
84
+ const buildTable = (routes, kind, resolve) => Effect.gen(function* () {
85
+ const table = /* @__PURE__ */ new Map();
86
+ for (const route of routes) {
87
+ const origin = validOrigin(route.origin);
88
+ if (origin instanceof EmulatorRouteInvalid) return yield* Effect.fail(origin);
89
+ if (route.target.kind !== kind) return yield* Effect.fail(new EmulatorRouteInvalid({ reason: `the route for ${origin} needs a ${kind} target` }));
90
+ if (table.has(origin)) return yield* Effect.fail(new EmulatorRouteInvalid({ reason: `duplicate route for ${origin}` }));
91
+ const resolved = resolve(origin, route.target);
92
+ if (resolved instanceof EmulatorRouteInvalid) return yield* Effect.fail(resolved);
93
+ table.set(origin, resolved);
94
+ }
95
+ return table;
96
+ });
97
+ const requestOrigin = (url) => URL.canParse(url) ? new URL(url).origin : void 0;
98
+ /**
99
+ * Fail-closed error for a request whose origin has no route. The attached
100
+ * request carries only the method and origin (no path, query, headers, or
101
+ * body), so the message never leaks more than the origin.
102
+ */
103
+ const unroutedOriginError = (request, origin) => {
104
+ const label = origin ?? "unparseable-origin";
105
+ return new HttpClientError.HttpClientError({ reason: new HttpClientError.TransportError({
106
+ request: HttpClientRequest.make(request.method)(origin ?? ""),
107
+ description: `no emulator route for origin ${label}`
108
+ }) });
109
+ };
110
+ const joinPaths = (basePath, requestPath) => {
111
+ return `${basePath.endsWith("/") ? basePath.slice(0, -1) : basePath}${requestPath.startsWith("/") ? requestPath : `/${requestPath}`}`;
112
+ };
113
+ const rewriteToBase = (requestUrl, base) => {
114
+ const source = new URL(requestUrl);
115
+ const target = new URL(base.toString());
116
+ target.pathname = joinPaths(base.pathname, source.pathname);
117
+ target.search = source.search;
118
+ target.hash = source.hash;
119
+ return target.toString();
120
+ };
121
+ /**
122
+ * Merge `RequestInit` values (later wins; `headers` are merged, not replaced).
123
+ */
124
+ /** Name/value pairs of any `HeadersInit` shape (web `Headers`, pair list, or record). */
125
+ const requestInitHeaderPairs = (headers) => {
126
+ if (headers instanceof Headers) return [...headers.entries()];
127
+ if (Array.isArray(headers)) return headers.flatMap((pair) => {
128
+ const [name, value] = pair;
129
+ return name === void 0 || value === void 0 ? [] : [[name, value]];
130
+ });
131
+ return Object.entries(headers);
132
+ };
133
+ const mergeRequestInit = (...inits) => {
134
+ const merged = {};
135
+ let headers = Headers$1.empty;
136
+ for (const init of inits) {
137
+ if (init === void 0) continue;
138
+ Object.assign(merged, init);
139
+ if (init.headers !== void 0) headers = Headers$1.merge(headers, Headers$1.fromInput(requestInitHeaderPairs(init.headers)));
140
+ }
141
+ return Object.keys(headers).length === 0 ? merged : {
142
+ ...merged,
143
+ headers: { ...headers }
144
+ };
145
+ };
146
+ /**
147
+ * Run `effect` with native fetch told not to follow redirects
148
+ * (`redirect: 'manual'`). The `RequestInit` supplied here replaces the one the
149
+ * underlying `FetchHttpClient` layer captured when it was built (Effect merges
150
+ * the request-time context over the captured one), so the defaults visible
151
+ * where the emulated layer is built and where the request runs are merged in
152
+ * and only `redirect` is overridden. Only the `FetchHttpClient` transport reads
153
+ * this service.
154
+ */
155
+ const withManualRedirects = (buildInit) => (effect) => Effect.flatMap(Effect.serviceOption(FetchHttpClient.RequestInit), (init) => Effect.provideService(effect, FetchHttpClient.RequestInit, mergeRequestInit(buildInit, Option.getOrUndefined(init), { redirect: "manual" })));
156
+ const EmulatedHttpClient = {
157
+ /**
158
+ * `HttpClient` that rewrites each routed origin to its loopback emulator
159
+ * base URL (keeping path and query) and sends it through the host's real
160
+ * `HttpClient` underneath. Requests to any other origin fail closed with an
161
+ * `HttpClientError` that names only the origin.
162
+ *
163
+ * The check and rewrite run at the send step (the client's postprocess), so
164
+ * redirect follow-ups (`HttpClient.followRedirects` on top) and requests
165
+ * changed by a host's `HttpClient.mapRequest` are routed or refused too.
166
+ * With `FetchHttpClient` underneath, requests are sent with
167
+ * `redirect: 'manual'`: a 3xx from an emulator comes back to the caller as
168
+ * a 3xx and native fetch never follows it. Other `RequestInit` defaults are
169
+ * kept when they are provided where this layer is built or where the request
170
+ * runs; defaults provided only to the underlying `FetchHttpClient.layer`
171
+ * itself are replaced, so provide them around the whole client stack. Put
172
+ * `HttpClient.followRedirects` on top of this layer, never underneath it: a
173
+ * redirect loop below the route check sends follow-ups without it. Any
174
+ * other underlying client must not follow redirects by itself, because a
175
+ * redirect it follows internally never passes through the route table.
176
+ *
177
+ * Building fails with `EmulatorRouteInvalid` when a route is malformed,
178
+ * duplicated, not a `url` target, or not on loopback, and with
179
+ * `EmulatorEnvironmentRefused` when `NODE_ENV` is `production` or cannot be read.
180
+ */
181
+ layer: (routes) => Layer.effect(HttpClient.HttpClient, Effect.gen(function* () {
182
+ yield* refuseProductionEnvironment;
183
+ const table = yield* buildTable(routes, "url", (origin, target) => validBaseUrl(origin, target.baseUrl));
184
+ const underlying = yield* HttpClient.HttpClient;
185
+ const manualRedirects = withManualRedirects(Option.getOrUndefined(yield* Effect.serviceOption(FetchHttpClient.RequestInit)));
186
+ const route = (request) => {
187
+ const origin = requestOrigin(request.url);
188
+ const base = origin === void 0 ? void 0 : table.get(origin);
189
+ return base === void 0 ? Effect.fail(unroutedOriginError(request, origin)) : Effect.succeed(HttpClientRequest.setUrl(request, rewriteToBase(request.url, base)));
190
+ };
191
+ return HttpClient.makeWith((request) => manualRedirects(underlying.postprocess(Effect.flatMap(request, route))), underlying.preprocess);
192
+ })) };
193
+ /** Error for a request body the in-process client cannot hand to a fetch handler. */
194
+ const unsupportedBodyError = (request, kind) => new HttpClientError.HttpClientError({ reason: new HttpClientError.EncodeError({
195
+ request,
196
+ description: `in-process emulators accept only Empty, Uint8Array, and string Raw bodies (got ${kind})`
197
+ }) });
198
+ const webRequestBody = (request) => {
199
+ const body = request.body;
200
+ return Match.value(body).pipe(Match.tagsExhaustive({
201
+ Empty: () => Effect.succeed(void 0),
202
+ Uint8Array: ({ body: bytes }) => Effect.succeed(new Uint8Array(bytes)),
203
+ Raw: ({ body: raw }) => Predicate.isString(raw) ? Effect.succeed(raw) : Effect.fail(unsupportedBodyError(request, "non-string Raw")),
204
+ FormData: () => Effect.fail(unsupportedBodyError(request, "FormData")),
205
+ Stream: () => Effect.fail(unsupportedBodyError(request, "Stream"))
206
+ }));
207
+ };
208
+ const headerEntries = (headers) => Object.entries(headers);
209
+ const InProcessHttpClient = {
210
+ /**
211
+ * `HttpClient` that turns each request to a routed origin into a web
212
+ * `Request` (method, URL, headers, and an Empty, Uint8Array, or string Raw
213
+ * body) and calls that route's fetch handler directly; the web `Response`
214
+ * comes back through `HttpClientResponse.fromWeb`. Other body kinds fail
215
+ * with an `HttpClientError` (`EncodeError`). Unknown origins fail closed
216
+ * with an `HttpClientError` that names only the origin.
217
+ *
218
+ * Building fails with `EmulatorRouteInvalid` for malformed, duplicated, or
219
+ * non-`handler` routes and with `EmulatorEnvironmentRefused` when
220
+ * `NODE_ENV` is `production` or cannot be read.
221
+ */
222
+ layer: (routes) => Layer.effect(HttpClient.HttpClient, Effect.gen(function* () {
223
+ yield* refuseProductionEnvironment;
224
+ const table = yield* buildTable(routes, "handler", (_origin, target) => target.fetch);
225
+ return HttpClient.make((request, url, signal) => Effect.gen(function* () {
226
+ const handler = table.get(url.origin);
227
+ if (handler === void 0) return yield* Effect.fail(unroutedOriginError(request, url.origin));
228
+ const body = yield* webRequestBody(request);
229
+ const webRequest = yield* Effect.try({
230
+ try: () => {
231
+ const init = {
232
+ method: request.method,
233
+ headers: headerEntries(request.headers),
234
+ signal
235
+ };
236
+ if (body !== void 0) init.body = body;
237
+ return new Request(url.toString(), init);
238
+ },
239
+ catch: (cause) => new HttpClientError.HttpClientError({ reason: new HttpClientError.EncodeError({
240
+ request,
241
+ cause,
242
+ description: "could not build a web Request for the in-process emulator"
243
+ }) })
244
+ });
245
+ const response = yield* Effect.tryPromise({
246
+ try: () => handler(webRequest),
247
+ catch: (cause) => new HttpClientError.HttpClientError({ reason: new HttpClientError.TransportError({
248
+ request,
249
+ cause,
250
+ description: "the in-process emulator handler failed"
251
+ }) })
252
+ });
253
+ return HttpClientResponse.fromWeb(request, response);
254
+ }));
255
+ })) };
256
+ //#endregion
257
+ export { EmulatedHttpClient, EmulatorEnvironmentRefused, EmulatorRoute, EmulatorRouteInvalid, InProcessHttpClient };
258
+
259
+ //# sourceMappingURL=router.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.mjs","names":["HttpHeaders"],"sources":["../src/router.ts"],"sourcesContent":["/**\n * Effect `HttpClient` routing to emulators.\n *\n * Hosts pick a transport by swapping `HttpClient` layers: `EmulatedHttpClient`\n * rewrites real origins to loopback emulator processes over the host's own\n * client, and `InProcessHttpClient` calls emulator fetch handlers directly.\n * Both fail closed on unknown origins and refuse to build when `NODE_ENV` is\n * `production`. The route check runs at the send step, so every request that\n * is actually sent (including redirect follow-ups and requests a host changes\n * with `HttpClient.mapRequest` on top) goes through the route table. Replay\n * lives in `@yolk-sdk/conformance/replay`; live traffic is the host's own\n * client.\n *\n * @experimental\n */\nimport { Config, Data, Effect, Layer, Match, Option, Predicate } from 'effect'\nimport {\n FetchHttpClient,\n Headers as HttpHeaders,\n HttpClient,\n HttpClientError,\n HttpClientRequest,\n HttpClientResponse\n} from 'effect/unstable/http'\n\n/** A fetch handler, such as the one returned by an emulator factory. */\nexport type EmulatorFetch = (request: Request) => Promise<Response>\n\n/** A loopback emulator process (http or https on 127.0.0.0/8, `::1`, or `localhost`). */\nexport type EmulatorUrlTarget = {\n readonly kind: 'url'\n readonly baseUrl: string\n}\n\n/** An emulator fetch handler called in-process. */\nexport type EmulatorHandlerTarget = {\n readonly kind: 'handler'\n readonly fetch: EmulatorFetch\n}\n\nexport type EmulatorTarget = EmulatorUrlTarget | EmulatorHandlerTarget\n\n/**\n * Maps one real origin (for example `https://ai-gateway.vercel.sh`) to an\n * emulator target. Path and query of each request are kept.\n */\nexport type EmulatorRoute<T extends EmulatorTarget = EmulatorTarget> = {\n readonly origin: string\n readonly target: T\n}\n\nexport const EmulatorRoute = {\n /** Route an origin to a loopback emulator process. */\n url: (origin: string, baseUrl: string): EmulatorRoute<EmulatorUrlTarget> => ({\n origin,\n target: { kind: 'url', baseUrl }\n }),\n /** Route an origin to an in-process emulator fetch handler. */\n handler: (origin: string, fetch: EmulatorFetch): EmulatorRoute<EmulatorHandlerTarget> => ({\n origin,\n target: { kind: 'handler', fetch }\n })\n} as const\n\n/** A route table that cannot be used: bad origin, duplicate origin, wrong target, or non-loopback base URL. */\nexport class EmulatorRouteInvalid extends Data.TaggedError('EmulatorRouteInvalid')<{\n readonly reason: string\n}> {\n override get message(): string {\n return `Invalid emulator route: ${this.reason}`\n }\n}\n\n/** The router refused to build: `NODE_ENV` is `production`, or it could not be read. */\nexport class EmulatorEnvironmentRefused extends Data.TaggedError('EmulatorEnvironmentRefused')<{\n readonly reason: 'production' | 'unreadable'\n}> {\n override get message(): string {\n return this.reason === 'production'\n ? 'Emulator routing refuses to run when NODE_ENV is production'\n : 'Emulator routing could not read NODE_ENV'\n }\n}\n\nexport type EmulatorRouterError = EmulatorRouteInvalid | EmulatorEnvironmentRefused\n\n/**\n * Fails when `NODE_ENV` is `production`. A missing `NODE_ENV` is allowed; an\n * unreadable one fails closed.\n */\nconst refuseProductionEnvironment: Effect.Effect<void, EmulatorEnvironmentRefused> = Effect.gen(\n function* () {\n const nodeEnv = yield* Config.option(Config.String('NODE_ENV'))\n\n return Option.getOrUndefined(nodeEnv)\n }\n).pipe(\n Effect.mapError(() => new EmulatorEnvironmentRefused({ reason: 'unreadable' })),\n Effect.flatMap(nodeEnv =>\n nodeEnv?.trim().toLowerCase() === 'production'\n ? Effect.fail(new EmulatorEnvironmentRefused({ reason: 'production' }))\n : Effect.void\n )\n)\n\nconst ipv4LoopbackPattern = /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/\n\n/**\n * True for `http:`/`https:` URLs whose host is loopback: `localhost`, `::1`,\n * or an IPv4 address in 127.0.0.0/8 (after WHATWG URL normalization).\n * IPv4-mapped IPv6 forms such as `[::ffff:127.0.0.1]` are rejected: the\n * allowlist stays minimal, and `127.0.0.1` says the same thing.\n */\nconst isLoopbackUrl = (input: string): boolean => {\n if (!URL.canParse(input)) {\n return false\n }\n\n const url = new URL(input)\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n return false\n }\n\n const host = url.hostname.toLowerCase()\n\n return host === 'localhost' || host === '[::1]' || ipv4LoopbackPattern.test(host)\n}\n\nconst validOrigin = (origin: string): string | EmulatorRouteInvalid => {\n if (!URL.canParse(origin)) {\n return new EmulatorRouteInvalid({ reason: 'a route origin is not an absolute URL' })\n }\n\n const url = new URL(origin)\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n return new EmulatorRouteInvalid({ reason: `origin ${url.origin} must be http or https` })\n }\n\n if (\n url.username !== '' ||\n url.password !== '' ||\n (url.pathname !== '/' && url.pathname !== '') ||\n url.search !== '' ||\n url.hash !== ''\n ) {\n return new EmulatorRouteInvalid({\n reason: `origin ${url.origin} must be a bare origin without credentials, path, query, or hash`\n })\n }\n\n return url.origin\n}\n\nconst validBaseUrl = (origin: string, baseUrl: string): URL | EmulatorRouteInvalid => {\n if (!isLoopbackUrl(baseUrl)) {\n return new EmulatorRouteInvalid({\n reason: `the base URL for ${origin} must be http(s) on loopback (127.0.0.0/8, ::1, or localhost)`\n })\n }\n\n const url = new URL(baseUrl)\n\n if (url.username !== '' || url.password !== '' || url.search !== '' || url.hash !== '') {\n return new EmulatorRouteInvalid({\n reason: `the base URL for ${origin} must not carry credentials, a query, or a hash`\n })\n }\n\n return url\n}\n\ntype RouteTable<A> = ReadonlyMap<string, A>\n\nconst buildTable = <T extends EmulatorTarget, A>(\n routes: ReadonlyArray<EmulatorRoute<T>>,\n kind: T['kind'],\n resolve: (origin: string, target: T) => A | EmulatorRouteInvalid\n): Effect.Effect<RouteTable<A>, EmulatorRouteInvalid> =>\n Effect.gen(function* () {\n const table = new Map<string, A>()\n\n for (const route of routes) {\n const origin = validOrigin(route.origin)\n\n if (origin instanceof EmulatorRouteInvalid) {\n return yield* Effect.fail(origin)\n }\n\n if (route.target.kind !== kind) {\n return yield* Effect.fail(\n new EmulatorRouteInvalid({ reason: `the route for ${origin} needs a ${kind} target` })\n )\n }\n\n if (table.has(origin)) {\n return yield* Effect.fail(\n new EmulatorRouteInvalid({ reason: `duplicate route for ${origin}` })\n )\n }\n\n const resolved = resolve(origin, route.target)\n\n if (resolved instanceof EmulatorRouteInvalid) {\n return yield* Effect.fail(resolved)\n }\n\n table.set(origin, resolved)\n }\n\n return table\n })\n\nconst requestOrigin = (url: string): string | undefined =>\n URL.canParse(url) ? new URL(url).origin : undefined\n\n/**\n * Fail-closed error for a request whose origin has no route. The attached\n * request carries only the method and origin (no path, query, headers, or\n * body), so the message never leaks more than the origin.\n */\nconst unroutedOriginError = (\n request: HttpClientRequest.HttpClientRequest,\n origin: string | undefined\n) => {\n const label = origin ?? 'unparseable-origin'\n\n return new HttpClientError.HttpClientError({\n reason: new HttpClientError.TransportError({\n request: HttpClientRequest.make(request.method)(origin ?? ''),\n description: `no emulator route for origin ${label}`\n })\n })\n}\n\nconst joinPaths = (basePath: string, requestPath: string): string => {\n const prefix = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath\n\n return `${prefix}${requestPath.startsWith('/') ? requestPath : `/${requestPath}`}`\n}\n\nconst rewriteToBase = (requestUrl: string, base: URL): string => {\n const source = new URL(requestUrl)\n const target = new URL(base.toString())\n\n target.pathname = joinPaths(base.pathname, source.pathname)\n target.search = source.search\n target.hash = source.hash\n\n return target.toString()\n}\n\n/**\n * Merge `RequestInit` values (later wins; `headers` are merged, not replaced).\n */\n/** Name/value pairs of any `HeadersInit` shape (web `Headers`, pair list, or record). */\nconst requestInitHeaderPairs = (headers: HeadersInit): ReadonlyArray<readonly [string, string]> => {\n if (headers instanceof Headers) return [...headers.entries()]\n\n if (Array.isArray(headers)) {\n return headers.flatMap(pair => {\n const [name, value] = pair\n\n return name === undefined || value === undefined ? [] : [[name, value] as const]\n })\n }\n\n return Object.entries(headers)\n}\n\nconst mergeRequestInit = (\n ...inits: ReadonlyArray<globalThis.RequestInit | undefined>\n): globalThis.RequestInit => {\n const merged: globalThis.RequestInit = {}\n // Effect's `Headers.fromInput` never throws (unlike web `Headers`), so an invalid host default\n // surfaces from fetch as a typed transport error instead of a defect here.\n let headers = HttpHeaders.empty\n\n for (const init of inits) {\n if (init === undefined) continue\n\n Object.assign(merged, init)\n\n if (init.headers !== undefined) {\n headers = HttpHeaders.merge(\n headers,\n HttpHeaders.fromInput(requestInitHeaderPairs(init.headers))\n )\n }\n }\n\n return Object.keys(headers).length === 0 ? merged : { ...merged, headers: { ...headers } }\n}\n\n/**\n * Run `effect` with native fetch told not to follow redirects\n * (`redirect: 'manual'`). The `RequestInit` supplied here replaces the one the\n * underlying `FetchHttpClient` layer captured when it was built (Effect merges\n * the request-time context over the captured one), so the defaults visible\n * where the emulated layer is built and where the request runs are merged in\n * and only `redirect` is overridden. Only the `FetchHttpClient` transport reads\n * this service.\n */\nconst withManualRedirects =\n (buildInit: globalThis.RequestInit | undefined) =>\n <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Effect.flatMap(Effect.serviceOption(FetchHttpClient.RequestInit), init =>\n Effect.provideService(\n effect,\n FetchHttpClient.RequestInit,\n mergeRequestInit(buildInit, Option.getOrUndefined(init), { redirect: 'manual' })\n )\n )\n\nexport const EmulatedHttpClient = {\n /**\n * `HttpClient` that rewrites each routed origin to its loopback emulator\n * base URL (keeping path and query) and sends it through the host's real\n * `HttpClient` underneath. Requests to any other origin fail closed with an\n * `HttpClientError` that names only the origin.\n *\n * The check and rewrite run at the send step (the client's postprocess), so\n * redirect follow-ups (`HttpClient.followRedirects` on top) and requests\n * changed by a host's `HttpClient.mapRequest` are routed or refused too.\n * With `FetchHttpClient` underneath, requests are sent with\n * `redirect: 'manual'`: a 3xx from an emulator comes back to the caller as\n * a 3xx and native fetch never follows it. Other `RequestInit` defaults are\n * kept when they are provided where this layer is built or where the request\n * runs; defaults provided only to the underlying `FetchHttpClient.layer`\n * itself are replaced, so provide them around the whole client stack. Put\n * `HttpClient.followRedirects` on top of this layer, never underneath it: a\n * redirect loop below the route check sends follow-ups without it. Any\n * other underlying client must not follow redirects by itself, because a\n * redirect it follows internally never passes through the route table.\n *\n * Building fails with `EmulatorRouteInvalid` when a route is malformed,\n * duplicated, not a `url` target, or not on loopback, and with\n * `EmulatorEnvironmentRefused` when `NODE_ENV` is `production` or cannot be read.\n */\n layer: (\n routes: ReadonlyArray<EmulatorRoute<EmulatorUrlTarget>>\n ): Layer.Layer<HttpClient.HttpClient, EmulatorRouterError, HttpClient.HttpClient> =>\n Layer.effect(\n HttpClient.HttpClient,\n Effect.gen(function* () {\n yield* refuseProductionEnvironment\n\n const table = yield* buildTable(routes, 'url', (origin, target: EmulatorUrlTarget) =>\n validBaseUrl(origin, target.baseUrl)\n )\n\n const underlying = yield* HttpClient.HttpClient\n\n // `RequestInit` defaults visible where this layer is built (for example provided around the\n // whole client stack) are kept for routed requests.\n const manualRedirects = withManualRedirects(\n Option.getOrUndefined(yield* Effect.serviceOption(FetchHttpClient.RequestInit))\n )\n\n const route = (request: HttpClientRequest.HttpClientRequest) => {\n const origin = requestOrigin(request.url)\n const base = origin === undefined ? undefined : table.get(origin)\n\n return base === undefined\n ? Effect.fail(unroutedOriginError(request, origin))\n : Effect.succeed(HttpClientRequest.setUrl(request, rewriteToBase(request.url, base)))\n }\n\n // Route in postprocess, not preprocess: every request sent goes through postprocess, while\n // `followRedirects` skips preprocess for follow-ups and `mapRequest` runs after it.\n return HttpClient.makeWith(\n request => manualRedirects(underlying.postprocess(Effect.flatMap(request, route))),\n underlying.preprocess\n )\n })\n )\n} as const\n\n/** Error for a request body the in-process client cannot hand to a fetch handler. */\nconst unsupportedBodyError = (request: HttpClientRequest.HttpClientRequest, kind: string) =>\n new HttpClientError.HttpClientError({\n reason: new HttpClientError.EncodeError({\n request,\n description: `in-process emulators accept only Empty, Uint8Array, and string Raw bodies (got ${kind})`\n })\n })\n\nconst webRequestBody = (\n request: HttpClientRequest.HttpClientRequest\n): Effect.Effect<BodyInit | undefined, HttpClientError.HttpClientError> => {\n const body = request.body\n\n return Match.value(body).pipe(\n Match.tagsExhaustive({\n Empty: () => Effect.succeed(undefined),\n // Copy into an ArrayBuffer-backed view, as `Request` requires.\n Uint8Array: ({ body: bytes }) => Effect.succeed<BodyInit>(new Uint8Array(bytes)),\n Raw: ({ body: raw }) =>\n Predicate.isString(raw)\n ? Effect.succeed<BodyInit>(raw)\n : Effect.fail(unsupportedBodyError(request, 'non-string Raw')),\n FormData: () => Effect.fail(unsupportedBodyError(request, 'FormData')),\n Stream: () => Effect.fail(unsupportedBodyError(request, 'Stream'))\n })\n )\n}\n\nconst headerEntries = (headers: Readonly<Record<string, string>>): Array<[string, string]> =>\n Object.entries(headers)\n\nexport const InProcessHttpClient = {\n /**\n * `HttpClient` that turns each request to a routed origin into a web\n * `Request` (method, URL, headers, and an Empty, Uint8Array, or string Raw\n * body) and calls that route's fetch handler directly; the web `Response`\n * comes back through `HttpClientResponse.fromWeb`. Other body kinds fail\n * with an `HttpClientError` (`EncodeError`). Unknown origins fail closed\n * with an `HttpClientError` that names only the origin.\n *\n * Building fails with `EmulatorRouteInvalid` for malformed, duplicated, or\n * non-`handler` routes and with `EmulatorEnvironmentRefused` when\n * `NODE_ENV` is `production` or cannot be read.\n */\n layer: (\n routes: ReadonlyArray<EmulatorRoute<EmulatorHandlerTarget>>\n ): Layer.Layer<HttpClient.HttpClient, EmulatorRouterError> =>\n Layer.effect(\n HttpClient.HttpClient,\n Effect.gen(function* () {\n yield* refuseProductionEnvironment\n\n const table = yield* buildTable(\n routes,\n 'handler',\n (_origin, target: EmulatorHandlerTarget) => target.fetch\n )\n\n return HttpClient.make((request, url, signal) =>\n Effect.gen(function* () {\n const handler = table.get(url.origin)\n\n if (handler === undefined) {\n return yield* Effect.fail(unroutedOriginError(request, url.origin))\n }\n\n const body = yield* webRequestBody(request)\n\n const webRequest = yield* Effect.try({\n try: () => {\n const init: RequestInit = {\n method: request.method,\n headers: headerEntries(request.headers),\n signal\n }\n\n if (body !== undefined) {\n init.body = body\n }\n\n return new Request(url.toString(), init)\n },\n catch: cause =>\n new HttpClientError.HttpClientError({\n reason: new HttpClientError.EncodeError({\n request,\n cause,\n description: 'could not build a web Request for the in-process emulator'\n })\n })\n })\n\n const response = yield* Effect.tryPromise({\n try: () => handler(webRequest),\n catch: cause =>\n new HttpClientError.HttpClientError({\n reason: new HttpClientError.TransportError({\n request,\n cause,\n description: 'the in-process emulator handler failed'\n })\n })\n })\n\n return HttpClientResponse.fromWeb(request, response)\n })\n )\n })\n )\n} as const\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmDA,MAAa,gBAAgB;;CAE3B,MAAM,QAAgB,aAAuD;EAC3E;EACA,QAAQ;GAAE,MAAM;GAAO;EAAQ;CACjC;;CAEA,UAAU,QAAgB,WAAgE;EACxF;EACA,QAAQ;GAAE,MAAM;GAAW;EAAM;CACnC;AACF;;AAGA,IAAa,uBAAb,cAA0C,KAAK,YAAY,sBAAsB,EAE9E;CACD,IAAa,UAAkB;EAC7B,OAAO,2BAA2B,KAAK;CACzC;AACF;;AAGA,IAAa,6BAAb,cAAgD,KAAK,YAAY,4BAA4B,EAE1F;CACD,IAAa,UAAkB;EAC7B,OAAO,KAAK,WAAW,eACnB,gEACA;CACN;AACF;;;;;AAQA,MAAM,8BAA+E,OAAO,IAC1F,aAAa;CACX,MAAM,UAAU,OAAO,OAAO,OAAO,OAAO,OAAO,UAAU,CAAC;CAE9D,OAAO,OAAO,eAAe,OAAO;AACtC,CACF,EAAE,KACA,OAAO,eAAe,IAAI,2BAA2B,EAAE,QAAQ,aAAa,CAAC,CAAC,GAC9E,OAAO,SAAQ,YACb,SAAS,KAAK,EAAE,YAAY,MAAM,eAC9B,OAAO,KAAK,IAAI,2BAA2B,EAAE,QAAQ,aAAa,CAAC,CAAC,IACpE,OAAO,IACb,CACF;AAEA,MAAM,sBAAsB;;;;;;;AAQ5B,MAAM,iBAAiB,UAA2B;CAChD,IAAI,CAAC,IAAI,SAAS,KAAK,GACrB,OAAO;CAGT,MAAM,MAAM,IAAI,IAAI,KAAK;CAEzB,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAC/C,OAAO;CAGT,MAAM,OAAO,IAAI,SAAS,YAAY;CAEtC,OAAO,SAAS,eAAe,SAAS,WAAW,oBAAoB,KAAK,IAAI;AAClF;AAEA,MAAM,eAAe,WAAkD;CACrE,IAAI,CAAC,IAAI,SAAS,MAAM,GACtB,OAAO,IAAI,qBAAqB,EAAE,QAAQ,wCAAwC,CAAC;CAGrF,MAAM,MAAM,IAAI,IAAI,MAAM;CAE1B,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAC/C,OAAO,IAAI,qBAAqB,EAAE,QAAQ,UAAU,IAAI,OAAO,wBAAwB,CAAC;CAG1F,IACE,IAAI,aAAa,MACjB,IAAI,aAAa,MAChB,IAAI,aAAa,OAAO,IAAI,aAAa,MAC1C,IAAI,WAAW,MACf,IAAI,SAAS,IAEb,OAAO,IAAI,qBAAqB,EAC9B,QAAQ,UAAU,IAAI,OAAO,kEAC/B,CAAC;CAGH,OAAO,IAAI;AACb;AAEA,MAAM,gBAAgB,QAAgB,YAAgD;CACpF,IAAI,CAAC,cAAc,OAAO,GACxB,OAAO,IAAI,qBAAqB,EAC9B,QAAQ,oBAAoB,OAAO,+DACrC,CAAC;CAGH,MAAM,MAAM,IAAI,IAAI,OAAO;CAE3B,IAAI,IAAI,aAAa,MAAM,IAAI,aAAa,MAAM,IAAI,WAAW,MAAM,IAAI,SAAS,IAClF,OAAO,IAAI,qBAAqB,EAC9B,QAAQ,oBAAoB,OAAO,iDACrC,CAAC;CAGH,OAAO;AACT;AAIA,MAAM,cACJ,QACA,MACA,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,wBAAQ,IAAI,IAAe;CAEjC,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,YAAY,MAAM,MAAM;EAEvC,IAAI,kBAAkB,sBACpB,OAAO,OAAO,OAAO,KAAK,MAAM;EAGlC,IAAI,MAAM,OAAO,SAAS,MACxB,OAAO,OAAO,OAAO,KACnB,IAAI,qBAAqB,EAAE,QAAQ,iBAAiB,OAAO,WAAW,KAAK,SAAS,CAAC,CACvF;EAGF,IAAI,MAAM,IAAI,MAAM,GAClB,OAAO,OAAO,OAAO,KACnB,IAAI,qBAAqB,EAAE,QAAQ,uBAAuB,SAAS,CAAC,CACtE;EAGF,MAAM,WAAW,QAAQ,QAAQ,MAAM,MAAM;EAE7C,IAAI,oBAAoB,sBACtB,OAAO,OAAO,OAAO,KAAK,QAAQ;EAGpC,MAAM,IAAI,QAAQ,QAAQ;CAC5B;CAEA,OAAO;AACT,CAAC;AAEH,MAAM,iBAAiB,QACrB,IAAI,SAAS,GAAG,IAAI,IAAI,IAAI,GAAG,EAAE,SAAS,KAAA;;;;;;AAO5C,MAAM,uBACJ,SACA,WACG;CACH,MAAM,QAAQ,UAAU;CAExB,OAAO,IAAI,gBAAgB,gBAAgB,EACzC,QAAQ,IAAI,gBAAgB,eAAe;EACzC,SAAS,kBAAkB,KAAK,QAAQ,MAAM,EAAE,UAAU,EAAE;EAC5D,aAAa,gCAAgC;CAC/C,CAAC,EACH,CAAC;AACH;AAEA,MAAM,aAAa,UAAkB,gBAAgC;CAGnE,OAAO,GAFQ,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,WAE7C,YAAY,WAAW,GAAG,IAAI,cAAc,IAAI;AACrE;AAEA,MAAM,iBAAiB,YAAoB,SAAsB;CAC/D,MAAM,SAAS,IAAI,IAAI,UAAU;CACjC,MAAM,SAAS,IAAI,IAAI,KAAK,SAAS,CAAC;CAEtC,OAAO,WAAW,UAAU,KAAK,UAAU,OAAO,QAAQ;CAC1D,OAAO,SAAS,OAAO;CACvB,OAAO,OAAO,OAAO;CAErB,OAAO,OAAO,SAAS;AACzB;;;;;AAMA,MAAM,0BAA0B,YAAmE;CACjG,IAAI,mBAAmB,SAAS,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC;CAE5D,IAAI,MAAM,QAAQ,OAAO,GACvB,OAAO,QAAQ,SAAQ,SAAQ;EAC7B,MAAM,CAAC,MAAM,SAAS;EAEtB,OAAO,SAAS,KAAA,KAAa,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAU;CACjF,CAAC;CAGH,OAAO,OAAO,QAAQ,OAAO;AAC/B;AAEA,MAAM,oBACJ,GAAG,UACwB;CAC3B,MAAM,SAAiC,CAAC;CAGxC,IAAI,UAAUA,UAAY;CAE1B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,SAAS,KAAA,GAAW;EAExB,OAAO,OAAO,QAAQ,IAAI;EAE1B,IAAI,KAAK,YAAY,KAAA,GACnB,UAAUA,UAAY,MACpB,SACAA,UAAY,UAAU,uBAAuB,KAAK,OAAO,CAAC,CAC5D;CAEJ;CAEA,OAAO,OAAO,KAAK,OAAO,EAAE,WAAW,IAAI,SAAS;EAAE,GAAG;EAAQ,SAAS,EAAE,GAAG,QAAQ;CAAE;AAC3F;;;;;;;;;;AAWA,MAAM,uBACH,eACS,WACR,OAAO,QAAQ,OAAO,cAAc,gBAAgB,WAAW,IAAG,SAChE,OAAO,eACL,QACA,gBAAgB,aAChB,iBAAiB,WAAW,OAAO,eAAe,IAAI,GAAG,EAAE,UAAU,SAAS,CAAC,CACjF,CACF;AAEJ,MAAa,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;AAyBhC,QACE,WAEA,MAAM,OACJ,WAAW,YACX,OAAO,IAAI,aAAa;CACtB,OAAO;CAEP,MAAM,QAAQ,OAAO,WAAW,QAAQ,QAAQ,QAAQ,WACtD,aAAa,QAAQ,OAAO,OAAO,CACrC;CAEA,MAAM,aAAa,OAAO,WAAW;CAIrC,MAAM,kBAAkB,oBACtB,OAAO,eAAe,OAAO,OAAO,cAAc,gBAAgB,WAAW,CAAC,CAChF;CAEA,MAAM,SAAS,YAAiD;EAC9D,MAAM,SAAS,cAAc,QAAQ,GAAG;EACxC,MAAM,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,MAAM,IAAI,MAAM;EAEhE,OAAO,SAAS,KAAA,IACZ,OAAO,KAAK,oBAAoB,SAAS,MAAM,CAAC,IAChD,OAAO,QAAQ,kBAAkB,OAAO,SAAS,cAAc,QAAQ,KAAK,IAAI,CAAC,CAAC;CACxF;CAIA,OAAO,WAAW,UAChB,YAAW,gBAAgB,WAAW,YAAY,OAAO,QAAQ,SAAS,KAAK,CAAC,CAAC,GACjF,WAAW,UACb;AACF,CAAC,CACH,EACJ;;AAGA,MAAM,wBAAwB,SAA8C,SAC1E,IAAI,gBAAgB,gBAAgB,EAClC,QAAQ,IAAI,gBAAgB,YAAY;CACtC;CACA,aAAa,kFAAkF,KAAK;AACtG,CAAC,EACH,CAAC;AAEH,MAAM,kBACJ,YACyE;CACzE,MAAM,OAAO,QAAQ;CAErB,OAAO,MAAM,MAAM,IAAI,EAAE,KACvB,MAAM,eAAe;EACnB,aAAa,OAAO,QAAQ,KAAA,CAAS;EAErC,aAAa,EAAE,MAAM,YAAY,OAAO,QAAkB,IAAI,WAAW,KAAK,CAAC;EAC/E,MAAM,EAAE,MAAM,UACZ,UAAU,SAAS,GAAG,IAClB,OAAO,QAAkB,GAAG,IAC5B,OAAO,KAAK,qBAAqB,SAAS,gBAAgB,CAAC;EACjE,gBAAgB,OAAO,KAAK,qBAAqB,SAAS,UAAU,CAAC;EACrE,cAAc,OAAO,KAAK,qBAAqB,SAAS,QAAQ,CAAC;CACnE,CAAC,CACH;AACF;AAEA,MAAM,iBAAiB,YACrB,OAAO,QAAQ,OAAO;AAExB,MAAa,sBAAsB;;;;;;;;;;;;;AAajC,QACE,WAEA,MAAM,OACJ,WAAW,YACX,OAAO,IAAI,aAAa;CACtB,OAAO;CAEP,MAAM,QAAQ,OAAO,WACnB,QACA,YACC,SAAS,WAAkC,OAAO,KACrD;CAEA,OAAO,WAAW,MAAM,SAAS,KAAK,WACpC,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,MAAM,IAAI,IAAI,MAAM;EAEpC,IAAI,YAAY,KAAA,GACd,OAAO,OAAO,OAAO,KAAK,oBAAoB,SAAS,IAAI,MAAM,CAAC;EAGpE,MAAM,OAAO,OAAO,eAAe,OAAO;EAE1C,MAAM,aAAa,OAAO,OAAO,IAAI;GACnC,WAAW;IACT,MAAM,OAAoB;KACxB,QAAQ,QAAQ;KAChB,SAAS,cAAc,QAAQ,OAAO;KACtC;IACF;IAEA,IAAI,SAAS,KAAA,GACX,KAAK,OAAO;IAGd,OAAO,IAAI,QAAQ,IAAI,SAAS,GAAG,IAAI;GACzC;GACA,QAAO,UACL,IAAI,gBAAgB,gBAAgB,EAClC,QAAQ,IAAI,gBAAgB,YAAY;IACtC;IACA;IACA,aAAa;GACf,CAAC,EACH,CAAC;EACL,CAAC;EAED,MAAM,WAAW,OAAO,OAAO,WAAW;GACxC,WAAW,QAAQ,UAAU;GAC7B,QAAO,UACL,IAAI,gBAAgB,gBAAgB,EAClC,QAAQ,IAAI,gBAAgB,eAAe;IACzC;IACA;IACA,aAAa;GACf,CAAC,EACH,CAAC;EACL,CAAC;EAED,OAAO,mBAAmB,QAAQ,SAAS,QAAQ;CACrD,CAAC,CACH;AACF,CAAC,CACH,EACJ"}
@@ -0,0 +1,17 @@
1
+ import { CoreDispatch, StatefulCore } from "./stateful-emulator.mjs";
2
+
3
+ //#region src/stateful-core.d.ts
4
+ type StatefulCoreOptions<State> = {
5
+ /** The core emulator's name. */readonly name: string; /** The state the runtime starts from (already built from the seed). */
6
+ readonly initial: State; /** Decode a state the core is handed (a string is why it is invalid). */
7
+ readonly decodeState: (value: unknown) => State | string; /** The emulator's input-invalid error for an invalid seed. */
8
+ readonly inputInvalid: (input: 'seed', reason: string) => Error;
9
+ };
10
+ /**
11
+ * The `createCore` of a stateful emulator: a core custom runtime seeded with `initial`, whose
12
+ * state the wrapper reads, snapshots, and restores.
13
+ */
14
+ declare const statefulCoreRuntime: <State extends object>(options: StatefulCoreOptions<State>) => (dispatch: CoreDispatch<State>) => Promise<StatefulCore<State>>;
15
+ //#endregion
16
+ export { StatefulCoreOptions, statefulCoreRuntime };
17
+ //# sourceMappingURL=stateful-core.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateful-core.d.mts","names":[],"sources":["../src/stateful-core.ts"],"mappings":";;;KAcY,mBAAA;2CAED,IAAA,UAFoB;EAAA,SAIpB,OAAA,EAAS,KAAA,EAAA;EAAA,SAET,WAAA,GAAc,KAAA,cAAmB,KAAA,WAEgB;EAAA,SAAjD,YAAA,GAAe,KAAA,UAAe,MAAA,aAAmB,KAAA;AAAA;;;;;cAO/C,mBAAA,yBACY,OAAA,EAAS,mBAAA,CAAoB,KAAA,OAC7C,QAAA,EAAU,YAAA,CAAa,KAAA,MAAS,OAAA,CAAQ,YAAA,CAAa,KAAA"}
@@ -0,0 +1,41 @@
1
+ import { Predicate } from "effect";
2
+ //#region src/stateful-core.ts
3
+ /**
4
+ * The `createCore` of a stateful emulator: a core custom runtime seeded with `initial`, whose
5
+ * state the wrapper reads, snapshots, and restores.
6
+ */
7
+ const statefulCoreRuntime = (options) => async (dispatch) => {
8
+ const { decodeState, initial, inputInvalid } = options;
9
+ const core = await import("@emulators/core");
10
+ const definition = core.defineEmulator({
11
+ name: options.name,
12
+ cors: false,
13
+ state: () => initial,
14
+ validateSeed: (value) => {
15
+ const decoded = decodeState(value);
16
+ if (Predicate.isString(decoded)) throw inputInvalid("seed", decoded);
17
+ return decoded;
18
+ },
19
+ setup: ({ app, state }) => {
20
+ app.post("*", (context) => dispatch(state, context.req.raw));
21
+ }
22
+ });
23
+ const runtime = await core.createCustomRuntime(definition, { seed: initial });
24
+ return {
25
+ fetch: (request) => runtime.fetch(request),
26
+ baseUrl: runtime.baseUrl,
27
+ snapshot: () => runtime.snapshot().state,
28
+ restore: (state) => {
29
+ const current = runtime.snapshot();
30
+ return runtime.restore({
31
+ ...current,
32
+ state
33
+ });
34
+ },
35
+ close: () => runtime.close()
36
+ };
37
+ };
38
+ //#endregion
39
+ export { statefulCoreRuntime };
40
+
41
+ //# sourceMappingURL=stateful-core.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateful-core.mjs","names":[],"sources":["../src/stateful-core.ts"],"sourcesContent":["/**\n * The `@emulators/core` adapter of the stateful wrapper (internal; Node-only): the `createCore`\n * argument of `makeStatefulEmulator` and its variants, shared by every subpath on\n * `src/stateful-emulator.ts`. It loads the core lazily (the core imports Node builtins and reads\n * files at import time), defines a custom emulator whose only route runs the wrapper's dispatch\n * for every forwarded request, and adapts its runtime to `StatefulCore`. The wrapper itself never\n * imports the core, so it stays runtime-portable.\n *\n * @experimental\n */\nimport type { EmulatorSnapshot } from '@emulators/core'\nimport { Predicate } from 'effect'\nimport type { CoreDispatch, StatefulCore } from './stateful-emulator.ts'\n\nexport type StatefulCoreOptions<State> = {\n /** The core emulator's name. */\n readonly name: string\n /** The state the runtime starts from (already built from the seed). */\n readonly initial: State\n /** Decode a state the core is handed (a string is why it is invalid). */\n readonly decodeState: (value: unknown) => State | string\n /** The emulator's input-invalid error for an invalid seed. */\n readonly inputInvalid: (input: 'seed', reason: string) => Error\n}\n\n/**\n * The `createCore` of a stateful emulator: a core custom runtime seeded with `initial`, whose\n * state the wrapper reads, snapshots, and restores.\n */\nexport const statefulCoreRuntime =\n <State extends object>(options: StatefulCoreOptions<State>) =>\n async (dispatch: CoreDispatch<State>): Promise<StatefulCore<State>> => {\n const { decodeState, initial, inputInvalid } = options\n\n // Loaded lazily: the core imports Node builtins and reads files at import time.\n const core = await import('@emulators/core')\n\n const definition = core.defineEmulator<State>({\n name: options.name,\n cors: false,\n state: () => initial,\n validateSeed: value => {\n const decoded = decodeState(value)\n\n if (Predicate.isString(decoded)) {\n throw inputInvalid('seed', decoded)\n }\n\n return decoded\n },\n // The wrapper matched the route already and forwards every admitted request as a POST.\n setup: ({ app, state }) => {\n app.post('*', context => dispatch(state, context.req.raw))\n }\n })\n\n const runtime = await core.createCustomRuntime(definition, { seed: initial })\n\n return {\n fetch: request => runtime.fetch(request),\n baseUrl: runtime.baseUrl,\n snapshot: () => runtime.snapshot().state,\n restore: state => {\n const current: EmulatorSnapshot<State> = runtime.snapshot()\n\n return runtime.restore({ ...current, state })\n },\n close: () => runtime.close()\n }\n }\n"],"mappings":";;;;;;AA6BA,MAAa,uBACY,YACvB,OAAO,aAAgE;CACrE,MAAM,EAAE,aAAa,SAAS,iBAAiB;CAG/C,MAAM,OAAO,MAAM,OAAO;CAE1B,MAAM,aAAa,KAAK,eAAsB;EAC5C,MAAM,QAAQ;EACd,MAAM;EACN,aAAa;EACb,eAAc,UAAS;GACrB,MAAM,UAAU,YAAY,KAAK;GAEjC,IAAI,UAAU,SAAS,OAAO,GAC5B,MAAM,aAAa,QAAQ,OAAO;GAGpC,OAAO;EACT;EAEA,QAAQ,EAAE,KAAK,YAAY;GACzB,IAAI,KAAK,MAAK,YAAW,SAAS,OAAO,QAAQ,IAAI,GAAG,CAAC;EAC3D;CACF,CAAC;CAED,MAAM,UAAU,MAAM,KAAK,oBAAoB,YAAY,EAAE,MAAM,QAAQ,CAAC;CAE5E,OAAO;EACL,QAAO,YAAW,QAAQ,MAAM,OAAO;EACvC,SAAS,QAAQ;EACjB,gBAAgB,QAAQ,SAAS,EAAE;EACnC,UAAS,UAAS;GAChB,MAAM,UAAmC,QAAQ,SAAS;GAE1D,OAAO,QAAQ,QAAQ;IAAE,GAAG;IAAS;GAAM,CAAC;EAC9C;EACA,aAAa,QAAQ,MAAM;CAC7B;AACF"}