@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.
- package/LICENSE +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- 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"}
|
package/dist/router.mjs
ADDED
|
@@ -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":"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"}
|
|
@@ -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"}
|