@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,77 @@
|
|
|
1
|
+
import { EmulatorEvidence, EmulatorRouteEvidence, emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
2
|
+
import { StatefulCoverage, StatefulEmulatorApi, StatefulFault, StatefulFaultMatch, StatefulFaultState, StatefulInputKind, StatefulLedgerEntry } from "./stateful-emulator.mjs";
|
|
3
|
+
import { GithubEmulatorComment, GithubEmulatorFile, GithubEmulatorIssue, GithubEmulatorLabel, GithubEmulatorProfile, GithubEmulatorRepository, GithubEmulatorSeed, GithubEmulatorSeedIssue, GithubEmulatorState, GithubEmulatorStateSchema, GithubEmulatorUser } from "./github/state.mjs";
|
|
4
|
+
import { GithubEmulatorDrills, githubEmulatorAccept, githubEmulatorApiVersion, githubEmulatorErrorBodies, githubEmulatorOrigin } from "./github/api.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/github.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Route evidence manifest: every emulated route, whether it writes, and the conformance cases
|
|
9
|
+
* whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
|
|
10
|
+
* by construction (both come from one route table).
|
|
11
|
+
*/
|
|
12
|
+
declare const githubEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence>;
|
|
13
|
+
/**
|
|
14
|
+
* Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
|
|
15
|
+
* `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
|
|
16
|
+
* manifest (another value is rejected when the fault is added).
|
|
17
|
+
*/
|
|
18
|
+
declare const GithubFaultMatch: import("effect/Schema").Struct<{
|
|
19
|
+
readonly method: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
20
|
+
readonly path: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
21
|
+
readonly route: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
22
|
+
}>;
|
|
23
|
+
type GithubFaultMatch = typeof StatefulFaultMatch.Type;
|
|
24
|
+
/**
|
|
25
|
+
* A status fault (400-599): answer matching requests with this status, headers, and body instead
|
|
26
|
+
* of the route's write, so nothing is written. The body defaults to
|
|
27
|
+
* `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
|
|
28
|
+
* Only a request the emulator would answer reaches a fault: a request that is not emulated, by
|
|
29
|
+
* its shape or by the state, never uses one up.
|
|
30
|
+
*/
|
|
31
|
+
declare const GithubFault: import("effect/Schema").Struct<{
|
|
32
|
+
readonly kind: import("effect/Schema").Literal<"status">;
|
|
33
|
+
readonly status: import("effect/Schema").Int;
|
|
34
|
+
readonly headers: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").String>>;
|
|
35
|
+
readonly body: import("effect/Schema").optionalKey<import("effect/Schema").Codec<import("effect/Schema").Json, import("effect/Schema").Json, never, never>>;
|
|
36
|
+
readonly match: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
|
|
37
|
+
readonly method: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
38
|
+
readonly path: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
39
|
+
readonly route: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
40
|
+
}>>;
|
|
41
|
+
readonly count: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
42
|
+
}>;
|
|
43
|
+
type GithubFault = StatefulFault;
|
|
44
|
+
type GithubFaultState = StatefulFaultState;
|
|
45
|
+
type GithubLedgerEntry = StatefulLedgerEntry;
|
|
46
|
+
type GithubCoverage = StatefulCoverage;
|
|
47
|
+
declare const GithubEmulatorInputInvalid_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 & {
|
|
48
|
+
readonly _tag: "GithubEmulatorInputInvalid";
|
|
49
|
+
} & Readonly<A>;
|
|
50
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
51
|
+
declare class GithubEmulatorInputInvalid extends GithubEmulatorInputInvalid_base<{
|
|
52
|
+
readonly input: StatefulInputKind;
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
}> {
|
|
55
|
+
get message(): string;
|
|
56
|
+
}
|
|
57
|
+
type GithubEmulatorOptions = {
|
|
58
|
+
/** Typed seed; defaults to the fixture entities (`profile: 'default'`). */readonly seed?: GithubEmulatorSeed;
|
|
59
|
+
/**
|
|
60
|
+
* Clock in epoch milliseconds (created issue and comment times, close times). Defaults to
|
|
61
|
+
* `Date.now`.
|
|
62
|
+
*/
|
|
63
|
+
readonly now?: () => number; /** Drill knobs (tests only): make the emulator disagree with one conformance claim. */
|
|
64
|
+
readonly drills?: GithubEmulatorDrills;
|
|
65
|
+
};
|
|
66
|
+
type GithubEmulator = StatefulEmulatorApi<GithubEmulatorState, GithubEmulatorSeed>;
|
|
67
|
+
/**
|
|
68
|
+
* Create a stateful GitHub emulator on the `@emulators/core` custom runtime. Each call has its own
|
|
69
|
+
* state, ledger, and faults. Rejects with `GithubEmulatorInputInvalid` for an invalid seed or
|
|
70
|
+
* option. Every request needs `Accept: application/vnd.github+json` and
|
|
71
|
+
* `X-GitHub-Api-Version: 2026-03-10` (else not emulated). See `src/stateful-emulator.ts` for the
|
|
72
|
+
* request precedence.
|
|
73
|
+
*/
|
|
74
|
+
declare const makeGithubEmulator: (options?: GithubEmulatorOptions) => Promise<GithubEmulator>;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { type EmulatorEvidence, type EmulatorRouteEvidence, GithubCoverage, GithubEmulator, GithubEmulatorComment, type GithubEmulatorDrills, GithubEmulatorFile, GithubEmulatorInputInvalid, GithubEmulatorIssue, GithubEmulatorLabel, GithubEmulatorOptions, GithubEmulatorProfile, GithubEmulatorRepository, GithubEmulatorSeed, GithubEmulatorSeedIssue, type GithubEmulatorState, GithubEmulatorStateSchema, GithubEmulatorUser, GithubFault, GithubFaultMatch, GithubFaultState, GithubLedgerEntry, emulatorEvidenceHeader, githubEmulatorAccept, githubEmulatorApiVersion, githubEmulatorErrorBodies, githubEmulatorOrigin, githubEmulatorRoutes, makeGithubEmulator };
|
|
77
|
+
//# sourceMappingURL=github.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.mts","names":[],"sources":["../src/github.ts"],"mappings":";;;;;;;;AA0IsE;AAQtE;;cARa,oBAAA,EAAsB,aAAa,CAAC,qBAAA;;;;;;cAQpC,gBAAA,0BAAgB,MAAA;EAAA;;;;KAEjB,gBAAA,UAA0B,kBAAA,CAAmB,IAAI;;;AAA7D;;;;AAA6D;cAShD,WAAA,0BAAW,MAAA;EAAA;;;;;;;;;;;KAEZ,WAAA,GAAc,aAAa;AAAA,KAE3B,gBAAA,GAAmB,kBAAkB;AAAA,KAErC,iBAAA,GAAoB,mBAAmB;AAAA,KAEvC,cAAA,GAAiB,gBAAgB;AAAA,cAAA,+BAAA;;;;cAGhC,0BAAA,SAAmC,+BAAA;EAAA,SACrC,KAAA,EAAO,iBAAA;EAAA,SACP,MAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,KAKH,qBAAA;sFAED,IAAA,GAAO,kBAAA;;;;;WAKP,GAAA;WAEA,MAAA,GAAS,oBAAoB;AAAA;AAAA,KAG5B,cAAA,GAAiB,mBAAA,CAAoB,mBAAA,EAAqB,kBAAA;;;AA9B/B;AAEvC;;;;cA+Ca,kBAAA,GACX,OAAA,GAAS,qBAAA,KACR,OAAA,CAAQ,cAAA"}
|
package/dist/github.mjs
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
2
|
+
import { StatefulFault, StatefulFaultMatch, checkBooleanDrills, makeStatefulEmulator, routeEvidence } from "./stateful-emulator.mjs";
|
|
3
|
+
import { statefulCoreRuntime } from "./stateful-core.mjs";
|
|
4
|
+
import { GithubEmulatorComment, GithubEmulatorFile, GithubEmulatorIssue, GithubEmulatorLabel, GithubEmulatorProfile, GithubEmulatorRepository, GithubEmulatorSeed, GithubEmulatorSeedIssue, GithubEmulatorStateSchema, GithubEmulatorUser, buildSeedState, decodeState } from "./github/state.mjs";
|
|
5
|
+
import { githubApiRoutes, githubEmulatorAccept, githubEmulatorApiVersion, githubEmulatorDrillKnobs, githubEmulatorErrorBodies, githubEmulatorOrigin } from "./github/api.mjs";
|
|
6
|
+
import { Data, Predicate } from "effect";
|
|
7
|
+
//#region src/github.ts
|
|
8
|
+
/**
|
|
9
|
+
* Stateful GitHub emulator (the REST routes of the GitHub conformance cases on
|
|
10
|
+
* `https://api.github.com`, API version 2026-03-10), built on the upstream `@emulators/core`
|
|
11
|
+
* custom runtime, with a request ledger, status faults, and an `/_emulate/*` control plane.
|
|
12
|
+
*
|
|
13
|
+
* It never imports SDK code: its wire shapes, error bodies, and default seed are copied as data
|
|
14
|
+
* from the synthetic GitHub conformance fixtures, and every route names the conformance cases it
|
|
15
|
+
* follows in `githubEmulatorRoutes`. Only the routes those seven cases send are emulated. Response
|
|
16
|
+
* behaviour comes only from the fixtures: anything they do not show answers one ledgered 400
|
|
17
|
+
* not-emulated, writes nothing, and uses up no fault. That includes the leftover lookup's
|
|
18
|
+
* open-issue listing (`GET /repos/{owner}/{repo}/issues`, no fixture), so the read-only lookup
|
|
19
|
+
* fails and the live runner prints its lookup-failed WARN. Every route answers only on the
|
|
20
|
+
* recorded origin `https://api.github.com` (`fetchOn(origin)` serves it behind a loopback
|
|
21
|
+
* rewrite).
|
|
22
|
+
*
|
|
23
|
+
* Fail closed: a request is recognised only when its raw path is exactly an emulated route shape
|
|
24
|
+
* (every path parameter matches its raw pattern in full) under that route's method, and any
|
|
25
|
+
* `Authorization` header is exactly `Bearer <token>` (a recognisable bearer, below). Every other
|
|
26
|
+
* request is ledgered and answered with constant text only (`/<unrecognised>`, a standard method or
|
|
27
|
+
* `<other>`, an empty query, no body, a constant reason). The bearer value is never compared
|
|
28
|
+
* against anything, stored, forwarded, or ledgered. A recognised bearer must match the RFC 6750
|
|
29
|
+
* `b64token` syntax exactly (`^[A-Za-z0-9\-._~+/]+=*$`, at least 8 characters), start with a
|
|
30
|
+
* character in `[G-Zg-z\-._~+/]` other than `n`, `r`, `t`, `u`, and hold at least one character
|
|
31
|
+
* outside the JSON-number alphabet `[0-9.eE+-]` (every GitHub and Google token form does: `ghp_…`,
|
|
32
|
+
* `github_pat_…`, `gho_…`, `ya29.…`). So no number's text can contain it; it holds no escape
|
|
33
|
+
* introducer (`%`, `\`, `"`), so no escape starts inside it; and its first character is no hex
|
|
34
|
+
* digit and no JSON escape letter, so no stray `%`, `\`, or partial escape to its left can complete
|
|
35
|
+
* with it, and its characters always decode in place. An `Authorization` header with any other
|
|
36
|
+
* value is unrecognisable. A recognised request that repeats the bearer value in its raw path, any
|
|
37
|
+
* path segment, the raw query or any query key or value, any recorded header, or its body is
|
|
38
|
+
* refused and ledgered with constant text only: a standard method, the path `/<unrecognised>`, its
|
|
39
|
+
* route template, an empty query, no headers or body, and a constant reason
|
|
40
|
+
* (`the query repeats the credential`, for example). Each part is checked through the closure of
|
|
41
|
+
* two total, lexical transforms that cannot fail: a tolerant percent-decode (every `%XX` below
|
|
42
|
+
* `%80` becomes its ASCII character; any other `%` sequence is left as it is) and a tolerant
|
|
43
|
+
* JSON-unescape (in any text, whether or not it parses as JSON, `\uXXXX` below `\u0080` and `\"`,
|
|
44
|
+
* `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t` become their characters). Starting from each part's raw
|
|
45
|
+
* text, either transform is applied to every text of the previous step, deduplicated, until no new
|
|
46
|
+
* text appears (a fixpoint), and every text is checked for the bearer as a substring; both
|
|
47
|
+
* transforms never lengthen a text and shorten it whenever they change it. So any depth of
|
|
48
|
+
* percent-encoding or JSON escaping, in any order, is seen through in every part: the raw path and
|
|
49
|
+
* each raw path segment, the raw query and each query key and value (already decoded once by
|
|
50
|
+
* `URLSearchParams`), each recorded header, and the raw body. The work is capped at 64 rounds, 1024
|
|
51
|
+
* distinct texts, or 8 Mi characters read by the transforms, whichever comes first; a part whose
|
|
52
|
+
* closure hits a cap before its fixpoint counts as repeating the credential and is refused with the
|
|
53
|
+
* same constant entry (uncertainty refuses, it never admits; so any part over 4 Mi characters is
|
|
54
|
+
* always refused). Any other recognised request has the bearer value scrubbed from its ledgered
|
|
55
|
+
* fields and every not-emulated reason (plan-time reasons included); its recorded query is keyed by
|
|
56
|
+
* recorded key; a key recorded more than once lists its values in order (as a JSON array); and
|
|
57
|
+
* recorded headers and query keys and values that start like JSON (`{`, `[`, `"`) are recorded
|
|
58
|
+
* parsed with credential-named keys redacted at any depth, or as `<redacted>` when they do not
|
|
59
|
+
* parse, whatever the header's declared format. Refusals never echo a request's own query or body
|
|
60
|
+
* keys.
|
|
61
|
+
*
|
|
62
|
+
* Request-shape latitude (`/github`, the only accepted deviations): any bearer value in the RFC
|
|
63
|
+
* 6750 `b64token` syntax (`[A-Za-z0-9\-._~+/]+=*`) of at least 8 characters, starting with a
|
|
64
|
+
* character in `[G-Zg-z\-._~+/]` other than `n`, `r`, `t`, `u` (so a legacy all-hex token is
|
|
65
|
+
* refused), with at least one outside `[0-9.eE+-]`, that occurs nowhere else in the request (never
|
|
66
|
+
* compared against anything, stored, or ledgered); extra request headers; JSON key order;
|
|
67
|
+
* `content-type` media-type parameters; the order of query parameters; any non-empty issue title
|
|
68
|
+
* and comment body, and any issue body text; any comment listing `since` of the form
|
|
69
|
+
* `YYYY-MM-DDTHH:MM:SSZ`; any label listing `per_page` from 1 to 100, with no `page` or a `page`
|
|
70
|
+
* from 2 to one past the last page; any issue search `q` that starts with the seeded
|
|
71
|
+
* `repo:<owner>/<repo>` qualifier and whose query after it is longer than 256 characters (answered
|
|
72
|
+
* the recorded 422); any issue number the repository has not reached (answered the recorded 404);
|
|
73
|
+
* and any issue, comment, repository label, or file the state holds where a fixture has one, under
|
|
74
|
+
* the per-route state rules. `Authorization` must be exactly `Bearer <token>` (that spelling, one
|
|
75
|
+
* space), `Accept` `application/vnd.github+json`, and `X-GitHub-Api-Version` `2026-03-10`.
|
|
76
|
+
* Everything else (other keys and values, query parameters, empty query components such as a bare
|
|
77
|
+
* `?` or a stray `&`, another origin or repository, a repeated query key, an explicit `page=1`, and
|
|
78
|
+
* a comment listing `per_page` other than 100) is not emulated.
|
|
79
|
+
*
|
|
80
|
+
* Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by
|
|
81
|
+
* `makeGithubEmulator` (importing this module has no side effects).
|
|
82
|
+
*
|
|
83
|
+
* @experimental
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Route evidence manifest: every emulated route, whether it writes, and the conformance cases
|
|
87
|
+
* whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
|
|
88
|
+
* by construction (both come from one route table).
|
|
89
|
+
*/
|
|
90
|
+
const githubEmulatorRoutes = githubApiRoutes.map(routeEvidence);
|
|
91
|
+
/**
|
|
92
|
+
* Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
|
|
93
|
+
* `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
|
|
94
|
+
* manifest (another value is rejected when the fault is added).
|
|
95
|
+
*/
|
|
96
|
+
const GithubFaultMatch = StatefulFaultMatch;
|
|
97
|
+
/**
|
|
98
|
+
* A status fault (400-599): answer matching requests with this status, headers, and body instead
|
|
99
|
+
* of the route's write, so nothing is written. The body defaults to
|
|
100
|
+
* `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
|
|
101
|
+
* Only a request the emulator would answer reaches a fault: a request that is not emulated, by
|
|
102
|
+
* its shape or by the state, never uses one up.
|
|
103
|
+
*/
|
|
104
|
+
const GithubFault = StatefulFault;
|
|
105
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
106
|
+
var GithubEmulatorInputInvalid = class extends Data.TaggedError("GithubEmulatorInputInvalid") {
|
|
107
|
+
get message() {
|
|
108
|
+
return `Invalid GitHub emulator ${this.input}: ${this.reason}`;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const inputInvalid = (input, reason) => new GithubEmulatorInputInvalid({
|
|
112
|
+
input,
|
|
113
|
+
reason
|
|
114
|
+
});
|
|
115
|
+
/** The constant reason of a request on no emulated route shape. */
|
|
116
|
+
const githubUnrecognisedReason = "no emulated GitHub route for this method and path";
|
|
117
|
+
/** The constant reason of a request whose `Authorization` header is not one recognisable bearer. */
|
|
118
|
+
const githubUnrecognisedAuthorizationReason = "an unrecognisable Authorization header is not emulated";
|
|
119
|
+
/**
|
|
120
|
+
* Create a stateful GitHub emulator on the `@emulators/core` custom runtime. Each call has its own
|
|
121
|
+
* state, ledger, and faults. Rejects with `GithubEmulatorInputInvalid` for an invalid seed or
|
|
122
|
+
* option. Every request needs `Accept: application/vnd.github+json` and
|
|
123
|
+
* `X-GitHub-Api-Version: 2026-03-10` (else not emulated). See `src/stateful-emulator.ts` for the
|
|
124
|
+
* request precedence.
|
|
125
|
+
*/
|
|
126
|
+
const makeGithubEmulator = async (options = {}) => {
|
|
127
|
+
const initial = buildSeedState(options.seed ?? {});
|
|
128
|
+
if (Predicate.isString(initial)) throw inputInvalid("seed", initial);
|
|
129
|
+
checkBooleanDrills(options.drills, githubEmulatorDrillKnobs, inputInvalid);
|
|
130
|
+
const drills = options.drills ?? {};
|
|
131
|
+
return makeStatefulEmulator({
|
|
132
|
+
routes: githubApiRoutes,
|
|
133
|
+
env: {
|
|
134
|
+
now: options.now ?? (() => Date.now()),
|
|
135
|
+
drills: {
|
|
136
|
+
linkOmitsNext: drills.linkOmitsNext === true,
|
|
137
|
+
notFoundOmitsDocumentationUrl: drills.notFoundOmitsDocumentationUrl === true,
|
|
138
|
+
validationWithoutErrors: drills.validationWithoutErrors === true,
|
|
139
|
+
contentUnfolded: drills.contentUnfolded === true,
|
|
140
|
+
sinceExcludesEqual: drills.sinceExcludesEqual === true,
|
|
141
|
+
addAnswerOmitsLabel: drills.addAnswerOmitsLabel === true,
|
|
142
|
+
closeWithoutClosedAt: drills.closeWithoutClosedAt === true
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
initial,
|
|
146
|
+
buildSeed: buildSeedState,
|
|
147
|
+
requestProblem: (header) => {
|
|
148
|
+
if (header("accept") !== "application/vnd.github+json") return `Accept other than ${githubEmulatorAccept} is not emulated`;
|
|
149
|
+
return header("x-github-api-version") === "2026-03-10" ? void 0 : `X-GitHub-Api-Version other than ${githubEmulatorApiVersion} is not emulated`;
|
|
150
|
+
},
|
|
151
|
+
recordHeaders: [{
|
|
152
|
+
name: "accept",
|
|
153
|
+
json: false
|
|
154
|
+
}, {
|
|
155
|
+
name: "x-github-api-version",
|
|
156
|
+
json: false
|
|
157
|
+
}],
|
|
158
|
+
failClosed: {
|
|
159
|
+
unrecognised: githubUnrecognisedReason,
|
|
160
|
+
unrecognisedAuthorization: githubUnrecognisedAuthorizationReason
|
|
161
|
+
},
|
|
162
|
+
clearRuntime: () => void 0,
|
|
163
|
+
runtimeState: () => ({}),
|
|
164
|
+
seedSummary: (state) => ({
|
|
165
|
+
labels: state.labels.length,
|
|
166
|
+
issues: state.issues.length,
|
|
167
|
+
files: state.files.length
|
|
168
|
+
}),
|
|
169
|
+
inputInvalid
|
|
170
|
+
}, statefulCoreRuntime({
|
|
171
|
+
name: "github",
|
|
172
|
+
initial,
|
|
173
|
+
decodeState,
|
|
174
|
+
inputInvalid
|
|
175
|
+
}));
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
export { GithubEmulatorComment, GithubEmulatorFile, GithubEmulatorInputInvalid, GithubEmulatorIssue, GithubEmulatorLabel, GithubEmulatorProfile, GithubEmulatorRepository, GithubEmulatorSeed, GithubEmulatorSeedIssue, GithubEmulatorStateSchema, GithubEmulatorUser, GithubFault, GithubFaultMatch, emulatorEvidenceHeader, githubEmulatorAccept, githubEmulatorApiVersion, githubEmulatorErrorBodies, githubEmulatorOrigin, githubEmulatorRoutes, makeGithubEmulator };
|
|
179
|
+
|
|
180
|
+
//# sourceMappingURL=github.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.mjs","names":[],"sources":["../src/github.ts"],"sourcesContent":["/**\n * Stateful GitHub emulator (the REST routes of the GitHub conformance cases on\n * `https://api.github.com`, API version 2026-03-10), built on the upstream `@emulators/core`\n * custom runtime, with a request ledger, status faults, and an `/_emulate/*` control plane.\n *\n * It never imports SDK code: its wire shapes, error bodies, and default seed are copied as data\n * from the synthetic GitHub conformance fixtures, and every route names the conformance cases it\n * follows in `githubEmulatorRoutes`. Only the routes those seven cases send are emulated. Response\n * behaviour comes only from the fixtures: anything they do not show answers one ledgered 400\n * not-emulated, writes nothing, and uses up no fault. That includes the leftover lookup's\n * open-issue listing (`GET /repos/{owner}/{repo}/issues`, no fixture), so the read-only lookup\n * fails and the live runner prints its lookup-failed WARN. Every route answers only on the\n * recorded origin `https://api.github.com` (`fetchOn(origin)` serves it behind a loopback\n * rewrite).\n *\n * Fail closed: a request is recognised only when its raw path is exactly an emulated route shape\n * (every path parameter matches its raw pattern in full) under that route's method, and any\n * `Authorization` header is exactly `Bearer <token>` (a recognisable bearer, below). Every other\n * request is ledgered and answered with constant text only (`/<unrecognised>`, a standard method or\n * `<other>`, an empty query, no body, a constant reason). The bearer value is never compared\n * against anything, stored, forwarded, or ledgered. A recognised bearer must match the RFC 6750\n * `b64token` syntax exactly (`^[A-Za-z0-9\\-._~+/]+=*$`, at least 8 characters), start with a\n * character in `[G-Zg-z\\-._~+/]` other than `n`, `r`, `t`, `u`, and hold at least one character\n * outside the JSON-number alphabet `[0-9.eE+-]` (every GitHub and Google token form does: `ghp_…`,\n * `github_pat_…`, `gho_…`, `ya29.…`). So no number's text can contain it; it holds no escape\n * introducer (`%`, `\\`, `\"`), so no escape starts inside it; and its first character is no hex\n * digit and no JSON escape letter, so no stray `%`, `\\`, or partial escape to its left can complete\n * with it, and its characters always decode in place. An `Authorization` header with any other\n * value is unrecognisable. A recognised request that repeats the bearer value in its raw path, any\n * path segment, the raw query or any query key or value, any recorded header, or its body is\n * refused and ledgered with constant text only: a standard method, the path `/<unrecognised>`, its\n * route template, an empty query, no headers or body, and a constant reason\n * (`the query repeats the credential`, for example). Each part is checked through the closure of\n * two total, lexical transforms that cannot fail: a tolerant percent-decode (every `%XX` below\n * `%80` becomes its ASCII character; any other `%` sequence is left as it is) and a tolerant\n * JSON-unescape (in any text, whether or not it parses as JSON, `\\uXXXX` below `\\u0080` and `\\\"`,\n * `\\\\`, `\\/`, `\\b`, `\\f`, `\\n`, `\\r`, `\\t` become their characters). Starting from each part's raw\n * text, either transform is applied to every text of the previous step, deduplicated, until no new\n * text appears (a fixpoint), and every text is checked for the bearer as a substring; both\n * transforms never lengthen a text and shorten it whenever they change it. So any depth of\n * percent-encoding or JSON escaping, in any order, is seen through in every part: the raw path and\n * each raw path segment, the raw query and each query key and value (already decoded once by\n * `URLSearchParams`), each recorded header, and the raw body. The work is capped at 64 rounds, 1024\n * distinct texts, or 8 Mi characters read by the transforms, whichever comes first; a part whose\n * closure hits a cap before its fixpoint counts as repeating the credential and is refused with the\n * same constant entry (uncertainty refuses, it never admits; so any part over 4 Mi characters is\n * always refused). Any other recognised request has the bearer value scrubbed from its ledgered\n * fields and every not-emulated reason (plan-time reasons included); its recorded query is keyed by\n * recorded key; a key recorded more than once lists its values in order (as a JSON array); and\n * recorded headers and query keys and values that start like JSON (`{`, `[`, `\"`) are recorded\n * parsed with credential-named keys redacted at any depth, or as `<redacted>` when they do not\n * parse, whatever the header's declared format. Refusals never echo a request's own query or body\n * keys.\n *\n * Request-shape latitude (`/github`, the only accepted deviations): any bearer value in the RFC\n * 6750 `b64token` syntax (`[A-Za-z0-9\\-._~+/]+=*`) of at least 8 characters, starting with a\n * character in `[G-Zg-z\\-._~+/]` other than `n`, `r`, `t`, `u` (so a legacy all-hex token is\n * refused), with at least one outside `[0-9.eE+-]`, that occurs nowhere else in the request (never\n * compared against anything, stored, or ledgered); extra request headers; JSON key order;\n * `content-type` media-type parameters; the order of query parameters; any non-empty issue title\n * and comment body, and any issue body text; any comment listing `since` of the form\n * `YYYY-MM-DDTHH:MM:SSZ`; any label listing `per_page` from 1 to 100, with no `page` or a `page`\n * from 2 to one past the last page; any issue search `q` that starts with the seeded\n * `repo:<owner>/<repo>` qualifier and whose query after it is longer than 256 characters (answered\n * the recorded 422); any issue number the repository has not reached (answered the recorded 404);\n * and any issue, comment, repository label, or file the state holds where a fixture has one, under\n * the per-route state rules. `Authorization` must be exactly `Bearer <token>` (that spelling, one\n * space), `Accept` `application/vnd.github+json`, and `X-GitHub-Api-Version` `2026-03-10`.\n * Everything else (other keys and values, query parameters, empty query components such as a bare\n * `?` or a stray `&`, another origin or repository, a repeated query key, an explicit `page=1`, and\n * a comment listing `per_page` other than 100) is not emulated.\n *\n * Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by\n * `makeGithubEmulator` (importing this module has no side effects).\n *\n * @experimental\n */\nimport { Data, Predicate } from 'effect'\nimport {\n githubApiRoutes,\n githubEmulatorAccept,\n githubEmulatorApiVersion,\n githubEmulatorDrillKnobs,\n type GithubApiEnv,\n type GithubEmulatorDrills\n} from './github/api.ts'\nimport {\n buildSeedState,\n decodeState,\n type GithubEmulatorSeed,\n type GithubEmulatorState\n} from './github/state.ts'\nimport type { EmulatorRouteEvidence } from './route-evidence.ts'\nimport {\n StatefulFault,\n StatefulFaultMatch,\n checkBooleanDrills,\n makeStatefulEmulator,\n routeEvidence,\n type StatefulCoverage,\n type StatefulEmulatorApi,\n type StatefulFaultState,\n type StatefulInputKind,\n type StatefulLedgerEntry\n} from './stateful-emulator.ts'\nimport { statefulCoreRuntime } from './stateful-core.ts'\n\nexport type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport { emulatorEvidenceHeader } from './route-evidence.ts'\n\nexport {\n githubEmulatorAccept,\n githubEmulatorApiVersion,\n githubEmulatorErrorBodies,\n githubEmulatorOrigin,\n type GithubEmulatorDrills\n} from './github/api.ts'\n\nexport {\n GithubEmulatorComment,\n GithubEmulatorFile,\n GithubEmulatorIssue,\n GithubEmulatorLabel,\n GithubEmulatorProfile,\n GithubEmulatorRepository,\n GithubEmulatorSeed,\n GithubEmulatorSeedIssue,\n GithubEmulatorStateSchema,\n GithubEmulatorUser,\n type GithubEmulatorState\n} from './github/state.ts'\n\n/**\n * Route evidence manifest: every emulated route, whether it writes, and the conformance cases\n * whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers\n * by construction (both come from one route table).\n */\nexport const githubEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> =\n githubApiRoutes.map(routeEvidence)\n\n/**\n * Optional fault filter; an omitted field matches every request. `method` is the HTTP method;\n * `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the\n * manifest (another value is rejected when the fault is added).\n */\nexport const GithubFaultMatch = StatefulFaultMatch\n\nexport type GithubFaultMatch = typeof StatefulFaultMatch.Type\n\n/**\n * A status fault (400-599): answer matching requests with this status, headers, and body instead\n * of the route's write, so nothing is written. The body defaults to\n * `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.\n * Only a request the emulator would answer reaches a fault: a request that is not emulated, by\n * its shape or by the state, never uses one up.\n */\nexport const GithubFault = StatefulFault\n\nexport type GithubFault = StatefulFault\n\nexport type GithubFaultState = StatefulFaultState\n\nexport type GithubLedgerEntry = StatefulLedgerEntry\n\nexport type GithubCoverage = StatefulCoverage\n\n/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */\nexport class GithubEmulatorInputInvalid extends Data.TaggedError('GithubEmulatorInputInvalid')<{\n readonly input: StatefulInputKind\n readonly reason: string\n}> {\n override get message(): string {\n return `Invalid GitHub emulator ${this.input}: ${this.reason}`\n }\n}\n\nexport type GithubEmulatorOptions = {\n /** Typed seed; defaults to the fixture entities (`profile: 'default'`). */\n readonly seed?: GithubEmulatorSeed\n /**\n * Clock in epoch milliseconds (created issue and comment times, close times). Defaults to\n * `Date.now`.\n */\n readonly now?: () => number\n /** Drill knobs (tests only): make the emulator disagree with one conformance claim. */\n readonly drills?: GithubEmulatorDrills\n}\n\nexport type GithubEmulator = StatefulEmulatorApi<GithubEmulatorState, GithubEmulatorSeed>\n\nconst inputInvalid = (input: StatefulInputKind, reason: string) =>\n new GithubEmulatorInputInvalid({ input, reason })\n\n/** The constant reason of a request on no emulated route shape. */\nconst githubUnrecognisedReason = 'no emulated GitHub route for this method and path'\n\n/** The constant reason of a request whose `Authorization` header is not one recognisable bearer. */\nconst githubUnrecognisedAuthorizationReason =\n 'an unrecognisable Authorization header is not emulated'\n\n/**\n * Create a stateful GitHub emulator on the `@emulators/core` custom runtime. Each call has its own\n * state, ledger, and faults. Rejects with `GithubEmulatorInputInvalid` for an invalid seed or\n * option. Every request needs `Accept: application/vnd.github+json` and\n * `X-GitHub-Api-Version: 2026-03-10` (else not emulated). See `src/stateful-emulator.ts` for the\n * request precedence.\n */\nexport const makeGithubEmulator = async (\n options: GithubEmulatorOptions = {}\n): Promise<GithubEmulator> => {\n const initial = buildSeedState(options.seed ?? {})\n\n if (Predicate.isString(initial)) {\n throw inputInvalid('seed', initial)\n }\n\n checkBooleanDrills(options.drills, githubEmulatorDrillKnobs, inputInvalid)\n\n const drills = options.drills ?? {}\n\n const env: GithubApiEnv = {\n now: options.now ?? (() => Date.now()),\n drills: {\n linkOmitsNext: drills.linkOmitsNext === true,\n notFoundOmitsDocumentationUrl: drills.notFoundOmitsDocumentationUrl === true,\n validationWithoutErrors: drills.validationWithoutErrors === true,\n contentUnfolded: drills.contentUnfolded === true,\n sinceExcludesEqual: drills.sinceExcludesEqual === true,\n addAnswerOmitsLabel: drills.addAnswerOmitsLabel === true,\n closeWithoutClosedAt: drills.closeWithoutClosedAt === true\n }\n }\n\n return makeStatefulEmulator<GithubEmulatorState, GithubApiEnv, GithubEmulatorSeed>(\n {\n routes: githubApiRoutes,\n env,\n initial,\n buildSeed: buildSeedState,\n requestProblem: header => {\n // The values every fixture sends.\n if (header('accept') !== githubEmulatorAccept) {\n return `Accept other than ${githubEmulatorAccept} is not emulated`\n }\n\n return header('x-github-api-version') === githubEmulatorApiVersion\n ? undefined\n : `X-GitHub-Api-Version other than ${githubEmulatorApiVersion} is not emulated`\n },\n recordHeaders: [\n { name: 'accept', json: false },\n { name: 'x-github-api-version', json: false }\n ],\n failClosed: {\n unrecognised: githubUnrecognisedReason,\n unrecognisedAuthorization: githubUnrecognisedAuthorizationReason\n },\n // No cursors: label pages are page numbers, as GitHub's are.\n clearRuntime: () => undefined,\n runtimeState: () => ({}),\n seedSummary: state => ({\n labels: state.labels.length,\n issues: state.issues.length,\n files: state.files.length\n }),\n inputInvalid\n },\n statefulCoreRuntime({ name: 'github', initial, decodeState, inputInvalid })\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0IA,MAAa,uBACX,gBAAgB,IAAI,aAAa;;;;;;AAOnC,MAAa,mBAAmB;;;;;;;;AAWhC,MAAa,cAAc;;AAW3B,IAAa,6BAAb,cAAgD,KAAK,YAAY,4BAA4B,EAG1F;CACD,IAAa,UAAkB;EAC7B,OAAO,2BAA2B,KAAK,MAAM,IAAI,KAAK;CACxD;AACF;AAgBA,MAAM,gBAAgB,OAA0B,WAC9C,IAAI,2BAA2B;CAAE;CAAO;AAAO,CAAC;;AAGlD,MAAM,2BAA2B;;AAGjC,MAAM,wCACJ;;;;;;;;AASF,MAAa,qBAAqB,OAChC,UAAiC,CAAC,MACN;CAC5B,MAAM,UAAU,eAAe,QAAQ,QAAQ,CAAC,CAAC;CAEjD,IAAI,UAAU,SAAS,OAAO,GAC5B,MAAM,aAAa,QAAQ,OAAO;CAGpC,mBAAmB,QAAQ,QAAQ,0BAA0B,YAAY;CAEzE,MAAM,SAAS,QAAQ,UAAU,CAAC;CAelC,OAAO,qBACL;EACE,QAAQ;EACR,KAAA;GAfF,KAAK,QAAQ,cAAc,KAAK,IAAI;GACpC,QAAQ;IACN,eAAe,OAAO,kBAAkB;IACxC,+BAA+B,OAAO,kCAAkC;IACxE,yBAAyB,OAAO,4BAA4B;IAC5D,iBAAiB,OAAO,oBAAoB;IAC5C,oBAAoB,OAAO,uBAAuB;IAClD,qBAAqB,OAAO,wBAAwB;IACpD,sBAAsB,OAAO,yBAAyB;GACxD;EAMI;EACF;EACA,WAAW;EACX,iBAAgB,WAAU;GAExB,IAAI,OAAO,QAAQ,MAAA,+BACjB,OAAO,qBAAqB,qBAAqB;GAGnD,OAAO,OAAO,sBAAsB,MAAA,eAChC,KAAA,IACA,mCAAmC,yBAAyB;EAClE;EACA,eAAe,CACb;GAAE,MAAM;GAAU,MAAM;EAAM,GAC9B;GAAE,MAAM;GAAwB,MAAM;EAAM,CAC9C;EACA,YAAY;GACV,cAAc;GACd,2BAA2B;EAC7B;EAEA,oBAAoB,KAAA;EACpB,qBAAqB,CAAC;EACtB,cAAa,WAAU;GACrB,QAAQ,MAAM,OAAO;GACrB,QAAQ,MAAM,OAAO;GACrB,OAAO,MAAM,MAAM;EACrB;EACA;CACF,GACA,oBAAoB;EAAE,MAAM;EAAU;EAAS;EAAa;CAAa,CAAC,CAC5E;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GoogleRoute } from "./shared.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/google/calendar.d.ts
|
|
4
|
+
/** Path of the events collection of `{calendarId}`. */
|
|
5
|
+
declare const calendarEventsPath = "/calendar/v3/calendars/{calendarId}/events";
|
|
6
|
+
/** The Calendar routes, in manifest order. */
|
|
7
|
+
declare const calendarRoutes: ReadonlyArray<GoogleRoute>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { calendarEventsPath, calendarRoutes };
|
|
10
|
+
//# sourceMappingURL=calendar.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.d.mts","names":[],"sources":["../../src/google/calendar.ts"],"mappings":";;;;cAgDa,kBAAA;AAAb;AAAA,cA+aa,cAAA,EAAgB,aAAa,CAAC,WAAA"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { exactBodyKeys, exactQuery, isNotEmulated, notEmulated, statefulRoute } from "../stateful-emulator.mjs";
|
|
2
|
+
import { answer, evidence, googleEmulatorApisOrigin, googleErrorBody, googleJson, googleNoContent, isRunText, listPage, pageSize, param, recordedValue, withoutQuery } from "./shared.mjs";
|
|
3
|
+
import { eventFields, mintedEventId, mintedEventIdPattern } from "./state.mjs";
|
|
4
|
+
import { Predicate } from "effect";
|
|
5
|
+
//#region src/google/calendar.ts
|
|
6
|
+
/**
|
|
7
|
+
* Calendar routes of the Google emulator (internal): the time-range event listing with page
|
|
8
|
+
* tokens, and the lifecycle of a run-scoped event without attendees (create, read, PATCH rename,
|
|
9
|
+
* delete to `cancelled`, and the recorded 410 of a repeated delete).
|
|
10
|
+
*
|
|
11
|
+
* Every answer comes from the Calendar fixtures, through the seed or the request, or is minted
|
|
12
|
+
* (event ids, page tokens, `created` / `updated` from the injectable clock). A deleted event stays
|
|
13
|
+
* in the state as `cancelled`, as the fixtures read it back.
|
|
14
|
+
*
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
17
|
+
/** Path of the events collection of `{calendarId}`. */
|
|
18
|
+
const calendarEventsPath = "/calendar/v3/calendars/{calendarId}/events";
|
|
19
|
+
const listCase = "google.calendar.list-range-paging";
|
|
20
|
+
const lifecycleCase = "google.calendar.event-lifecycle";
|
|
21
|
+
const deletedCase = "google.calendar.deleted-event-gone";
|
|
22
|
+
/** A raw calendar id segment: an address-like id, its `@` percent-encoded once (`%40`). */
|
|
23
|
+
const calendarIdSegment = /^[A-Za-z0-9._+-]{1,200}(?:%40[A-Za-z0-9.-]{1,200})?$/;
|
|
24
|
+
const eventIdSegment = /^[A-Za-z0-9_-]{1,1024}$/;
|
|
25
|
+
const calendar = (method, path, write, caseIds, withEvent) => evidence(googleEmulatorApisOrigin, method, `${calendarEventsPath}${path}`, write, caseIds, withEvent ? {
|
|
26
|
+
calendarId: calendarIdSegment,
|
|
27
|
+
eventId: eventIdSegment
|
|
28
|
+
} : { calendarId: calendarIdSegment });
|
|
29
|
+
const createdDescription = "Synthetic conformance event without attendees.";
|
|
30
|
+
const renamedDescription = "Synthetic conformance event, renamed.";
|
|
31
|
+
const eventSummaryRest = "synthetic conformance event, safe to delete";
|
|
32
|
+
/** The fixed half hour every recorded event create sends. */
|
|
33
|
+
const recordedStart = {
|
|
34
|
+
dateTime: "2030-01-07T09:00:00Z",
|
|
35
|
+
timeZone: "UTC"
|
|
36
|
+
};
|
|
37
|
+
const recordedEnd = {
|
|
38
|
+
dateTime: "2030-01-07T09:30:00Z",
|
|
39
|
+
timeZone: "UTC"
|
|
40
|
+
};
|
|
41
|
+
const instantPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(?:Z|[+-](\d{2}):(\d{2}))$/;
|
|
42
|
+
/**
|
|
43
|
+
* True for an RFC 3339 instant with a `Z` or numeric offset whose components are in range (a real
|
|
44
|
+
* calendar date, hour 0-23, minute and second 0-59, offset hour 0-23 and minute 0-59), checked
|
|
45
|
+
* before `Date.parse`, which would roll an out-of-range component over instead of refusing it.
|
|
46
|
+
*/
|
|
47
|
+
const isInstant = (text) => {
|
|
48
|
+
const parts = instantPattern.exec(text);
|
|
49
|
+
if (parts === null) return false;
|
|
50
|
+
const [year, month, day, hour, minute, second, offsetHour, offsetMinute] = parts.slice(1).map((part) => Number(part ?? "0"));
|
|
51
|
+
if (year === void 0 || month === void 0 || day === void 0) return false;
|
|
52
|
+
const days = [
|
|
53
|
+
31,
|
|
54
|
+
year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ? 29 : 28,
|
|
55
|
+
31,
|
|
56
|
+
30,
|
|
57
|
+
31,
|
|
58
|
+
30,
|
|
59
|
+
31,
|
|
60
|
+
31,
|
|
61
|
+
30,
|
|
62
|
+
31,
|
|
63
|
+
30,
|
|
64
|
+
31
|
|
65
|
+
][month - 1] ?? 0;
|
|
66
|
+
return day >= 1 && day <= days && (hour ?? 24) <= 23 && (minute ?? 60) <= 59 && (second ?? 60) <= 59 && (offsetHour ?? 24) <= 23 && (offsetMinute ?? 60) <= 59 && Number.isFinite(Date.parse(text));
|
|
67
|
+
};
|
|
68
|
+
const renderEvent = (state, owner, event) => {
|
|
69
|
+
const head = {
|
|
70
|
+
kind: "calendar#event",
|
|
71
|
+
etag: event.etag,
|
|
72
|
+
id: event.id,
|
|
73
|
+
status: event.status,
|
|
74
|
+
htmlLink: event.htmlLink,
|
|
75
|
+
created: event.created,
|
|
76
|
+
updated: event.updated,
|
|
77
|
+
summary: event.summary
|
|
78
|
+
};
|
|
79
|
+
if (event.description !== void 0) head.description = event.description;
|
|
80
|
+
return {
|
|
81
|
+
...head,
|
|
82
|
+
creator: {
|
|
83
|
+
email: state.practiceAddress,
|
|
84
|
+
self: true
|
|
85
|
+
},
|
|
86
|
+
organizer: {
|
|
87
|
+
email: owner.id,
|
|
88
|
+
displayName: owner.summary,
|
|
89
|
+
self: true
|
|
90
|
+
},
|
|
91
|
+
start: { ...event.start },
|
|
92
|
+
end: { ...event.end },
|
|
93
|
+
iCalUID: event.iCalUID,
|
|
94
|
+
sequence: event.sequence,
|
|
95
|
+
reminders: { useDefault: true },
|
|
96
|
+
eventType: "default"
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/** The instant a boundary starts (an all-day date at midnight UTC), in milliseconds. */
|
|
100
|
+
const instant = (boundary) => "dateTime" in boundary ? Date.parse(boundary.dateTime) : Date.parse(`${boundary.date}T00:00:00Z`);
|
|
101
|
+
const findCalendar = (state, id) => state.calendars.find((candidate) => candidate.id === id);
|
|
102
|
+
const findEvent = (state, calendarId, eventId) => state.events.find((event) => event.calendarId === calendarId && event.id === eventId);
|
|
103
|
+
const isCreatedHere = (event) => mintedEventIdPattern.test(event.id);
|
|
104
|
+
const replaceEvent = (state, next) => state.events.map((event) => event.calendarId === next.calendarId && event.id === next.id ? next : event);
|
|
105
|
+
/** The `{ dateTime, timeZone }` boundary a fixture records, exactly. */
|
|
106
|
+
const isRecordedBoundary = (value, recorded) => {
|
|
107
|
+
const boundary = exactBodyKeys(value, "boundary", ["dateTime", "timeZone"]);
|
|
108
|
+
return !isNotEmulated(boundary) && boundary.dateTime === recorded.dateTime && boundary.timeZone === recorded.timeZone;
|
|
109
|
+
};
|
|
110
|
+
const listEvents = statefulRoute(calendar("GET", "", false, [listCase], false), "none", (request) => {
|
|
111
|
+
const query = exactQuery(request, [
|
|
112
|
+
"timeMin",
|
|
113
|
+
"timeMax",
|
|
114
|
+
"maxResults",
|
|
115
|
+
"singleEvents",
|
|
116
|
+
"orderBy"
|
|
117
|
+
], ["pageToken"]);
|
|
118
|
+
if (isNotEmulated(query)) return query;
|
|
119
|
+
const recorded = recordedValue(query, "singleEvents", "true") ?? recordedValue(query, "orderBy", "startTime");
|
|
120
|
+
if (recorded !== void 0) return recorded;
|
|
121
|
+
const timeMin = query["timeMin"] ?? "";
|
|
122
|
+
const timeMax = query["timeMax"] ?? "";
|
|
123
|
+
if (!isInstant(timeMin) || !isInstant(timeMax) || !(Date.parse(timeMin) < Date.parse(timeMax))) return notEmulated("timeMin and timeMax must be RFC 3339 instants, timeMin first");
|
|
124
|
+
const maxResults = pageSize(query["maxResults"], "maxResults", 1, 2500);
|
|
125
|
+
if (isNotEmulated(maxResults)) return maxResults;
|
|
126
|
+
const pageToken = query["pageToken"];
|
|
127
|
+
return pageToken === "" ? notEmulated("an empty pageToken is not emulated") : {
|
|
128
|
+
calendarId: param(request, "calendarId"),
|
|
129
|
+
timeMin,
|
|
130
|
+
timeMax,
|
|
131
|
+
maxResults,
|
|
132
|
+
pageToken
|
|
133
|
+
};
|
|
134
|
+
}, (state, input, { env }) => {
|
|
135
|
+
const owner = findCalendar(state, input.calendarId);
|
|
136
|
+
if (owner === void 0) return notEmulated("a calendar the state does not hold");
|
|
137
|
+
const from = Date.parse(input.timeMin);
|
|
138
|
+
const to = Date.parse(input.timeMax);
|
|
139
|
+
const overlapping = state.events.filter((event) => event.calendarId === owner.id && instant(event.start) < to && instant(event.end) > from).sort((left, right) => instant(left.start) - instant(right.start));
|
|
140
|
+
if (overlapping.length === 0) return notEmulated("a range without events is not emulated (no fixture records one)");
|
|
141
|
+
if (overlapping.some((event) => event.status === "cancelled")) return notEmulated("a range holding a cancelled event is not emulated");
|
|
142
|
+
return listPage(env, "calendar", `calendar\u0000${owner.id}\u0000${input.timeMin}\u0000${input.timeMax}\u0000${input.maxResults}`, overlapping.map((event) => renderEvent(state, owner, event)), input.maxResults, input.pageToken, env.drills.calendarPageRepeats, (page, nextPageToken) => {
|
|
143
|
+
const body = {
|
|
144
|
+
kind: "calendar#events",
|
|
145
|
+
summary: owner.summary,
|
|
146
|
+
timeZone: owner.timeZone,
|
|
147
|
+
accessRole: owner.accessRole,
|
|
148
|
+
items: [...page]
|
|
149
|
+
};
|
|
150
|
+
if (nextPageToken !== void 0) body.nextPageToken = nextPageToken;
|
|
151
|
+
return googleJson(200, body);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
const createEvent = statefulRoute(calendar("POST", "", true, [lifecycleCase, deletedCase], false), "json", (request) => {
|
|
155
|
+
const body = withoutQuery(request) ?? exactBodyKeys(request.json, "the event body", [
|
|
156
|
+
"summary",
|
|
157
|
+
"description",
|
|
158
|
+
"start",
|
|
159
|
+
"end"
|
|
160
|
+
]);
|
|
161
|
+
if (isNotEmulated(body)) return body;
|
|
162
|
+
const summary = Predicate.isString(body.summary) ? body.summary : "";
|
|
163
|
+
if (!isRunText(summary, "event", eventSummaryRest) && !isRunText(summary, "deleted event", eventSummaryRest)) return notEmulated("an event summary other than the recorded run-scoped ones");
|
|
164
|
+
if (body.description !== createdDescription) return notEmulated("an event description other than the recorded one is not emulated");
|
|
165
|
+
return isRecordedBoundary(body.start, recordedStart) && isRecordedBoundary(body.end, recordedEnd) ? {
|
|
166
|
+
calendarId: param(request, "calendarId"),
|
|
167
|
+
summary
|
|
168
|
+
} : notEmulated("event times other than the recorded half hour are not emulated");
|
|
169
|
+
}, (state, input, { env }) => {
|
|
170
|
+
const owner = findCalendar(state, input.calendarId);
|
|
171
|
+
if (owner === void 0) return notEmulated("a calendar the state does not hold");
|
|
172
|
+
return () => {
|
|
173
|
+
const at = new Date(env.now()).toISOString();
|
|
174
|
+
const number = state.counters.nextEventNumber;
|
|
175
|
+
const id = mintedEventId(number);
|
|
176
|
+
const event = {
|
|
177
|
+
calendarId: owner.id,
|
|
178
|
+
id,
|
|
179
|
+
...eventFields(id),
|
|
180
|
+
status: "confirmed",
|
|
181
|
+
created: at,
|
|
182
|
+
updated: at,
|
|
183
|
+
summary: input.summary,
|
|
184
|
+
description: createdDescription,
|
|
185
|
+
start: { ...recordedStart },
|
|
186
|
+
end: { ...recordedEnd },
|
|
187
|
+
sequence: 0
|
|
188
|
+
};
|
|
189
|
+
state.counters = {
|
|
190
|
+
...state.counters,
|
|
191
|
+
nextEventNumber: number + 1
|
|
192
|
+
};
|
|
193
|
+
state.events = [...state.events, event];
|
|
194
|
+
return googleJson(200, renderEvent(state, owner, event));
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
const eventRef = (request) => ({
|
|
198
|
+
calendarId: param(request, "calendarId"),
|
|
199
|
+
eventId: param(request, "eventId")
|
|
200
|
+
});
|
|
201
|
+
/** The Calendar routes, in manifest order. */
|
|
202
|
+
const calendarRoutes = [
|
|
203
|
+
listEvents,
|
|
204
|
+
createEvent,
|
|
205
|
+
statefulRoute(calendar("GET", "/{eventId}", false, [lifecycleCase, deletedCase], true), "none", (request) => withoutQuery(request) ?? eventRef(request), (state, input) => {
|
|
206
|
+
const owner = findCalendar(state, input.calendarId);
|
|
207
|
+
const event = findEvent(state, input.calendarId, input.eventId);
|
|
208
|
+
return owner === void 0 || event === void 0 ? notEmulated("an event the state does not hold is not emulated") : answer(() => googleJson(200, renderEvent(state, owner, event)));
|
|
209
|
+
}),
|
|
210
|
+
statefulRoute(calendar("PATCH", "/{eventId}", true, [lifecycleCase], true), "json", (request) => {
|
|
211
|
+
const body = withoutQuery(request) ?? exactBodyKeys(request.json, "the event patch", ["summary", "description"]);
|
|
212
|
+
if (isNotEmulated(body)) return body;
|
|
213
|
+
const summary = Predicate.isString(body.summary) ? body.summary : "";
|
|
214
|
+
return isRunText(summary, "event renamed", eventSummaryRest) && body.description === renamedDescription ? {
|
|
215
|
+
...eventRef(request),
|
|
216
|
+
summary
|
|
217
|
+
} : notEmulated("an event patch other than the recorded rename is not emulated");
|
|
218
|
+
}, (state, input, { env }) => {
|
|
219
|
+
const owner = findCalendar(state, input.calendarId);
|
|
220
|
+
const event = findEvent(state, input.calendarId, input.eventId);
|
|
221
|
+
if (owner === void 0 || event === void 0 || !isCreatedHere(event) || event.status !== "confirmed") return notEmulated("renaming anything but a live event created here is not emulated");
|
|
222
|
+
return () => {
|
|
223
|
+
const at = new Date(env.now()).toISOString();
|
|
224
|
+
const next = {
|
|
225
|
+
...event,
|
|
226
|
+
updated: at,
|
|
227
|
+
summary: env.drills.eventPatchKeepsSummary ? event.summary : input.summary,
|
|
228
|
+
description: renamedDescription
|
|
229
|
+
};
|
|
230
|
+
state.events = replaceEvent(state, next);
|
|
231
|
+
return googleJson(200, renderEvent(state, owner, next));
|
|
232
|
+
};
|
|
233
|
+
}),
|
|
234
|
+
statefulRoute(calendar("DELETE", "/{eventId}", true, [lifecycleCase, deletedCase], true), "none", (request) => withoutQuery(request) ?? eventRef(request), (state, input, { env }) => {
|
|
235
|
+
const event = findEvent(state, input.calendarId, input.eventId);
|
|
236
|
+
if (event === void 0 || !isCreatedHere(event)) return notEmulated("deleting anything but an event created here is not emulated");
|
|
237
|
+
if (event.status === "cancelled") return answer(() => googleJson(env.drills.repeatedEventDeleteConflict ? 409 : 410, googleErrorBody(410, "Resource has been deleted", "deleted", "GONE")));
|
|
238
|
+
return () => {
|
|
239
|
+
const at = new Date(env.now()).toISOString();
|
|
240
|
+
state.events = replaceEvent(state, {
|
|
241
|
+
...event,
|
|
242
|
+
status: "cancelled",
|
|
243
|
+
updated: at
|
|
244
|
+
});
|
|
245
|
+
return googleNoContent();
|
|
246
|
+
};
|
|
247
|
+
})
|
|
248
|
+
];
|
|
249
|
+
//#endregion
|
|
250
|
+
export { calendarEventsPath, calendarRoutes };
|
|
251
|
+
|
|
252
|
+
//# sourceMappingURL=calendar.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.mjs","names":[],"sources":["../../src/google/calendar.ts"],"sourcesContent":["/**\n * Calendar routes of the Google emulator (internal): the time-range event listing with page\n * tokens, and the lifecycle of a run-scoped event without attendees (create, read, PATCH rename,\n * delete to `cancelled`, and the recorded 410 of a repeated delete).\n *\n * Every answer comes from the Calendar fixtures, through the seed or the request, or is minted\n * (event ids, page tokens, `created` / `updated` from the injectable clock). A deleted event stays\n * in the state as `cancelled`, as the fixtures read it back.\n *\n * @experimental\n */\nimport { Predicate } from 'effect'\nimport type * as Schema from 'effect/Schema'\nimport {\n exactBodyKeys,\n exactQuery,\n isNotEmulated,\n notEmulated,\n statefulRoute,\n type EmulatedRequest,\n type NotEmulated\n} from '../stateful-emulator.ts'\nimport {\n answer,\n evidence,\n googleEmulatorApisOrigin,\n googleErrorBody,\n googleJson,\n googleNoContent,\n isRunText,\n listPage,\n pageSize,\n param,\n recordedValue,\n withoutQuery,\n type GoogleRoute\n} from './shared.ts'\nimport {\n eventFields,\n mintedEventId,\n mintedEventIdPattern,\n type GoogleEmulatorCalendar,\n type GoogleEmulatorCalendarEvent,\n type GoogleEmulatorEventBoundary,\n type GoogleEmulatorState\n} from './state.ts'\n\n/** Path of the events collection of `{calendarId}`. */\nexport const calendarEventsPath = '/calendar/v3/calendars/{calendarId}/events'\n\nconst listCase = 'google.calendar.list-range-paging'\n\nconst lifecycleCase = 'google.calendar.event-lifecycle'\n\nconst deletedCase = 'google.calendar.deleted-event-gone'\n\n/** A raw calendar id segment: an address-like id, its `@` percent-encoded once (`%40`). */\nconst calendarIdSegment = /^[A-Za-z0-9._+-]{1,200}(?:%40[A-Za-z0-9.-]{1,200})?$/\n\nconst eventIdSegment = /^[A-Za-z0-9_-]{1,1024}$/\n\nconst calendar = (\n method: string,\n path: string,\n write: boolean,\n caseIds: ReadonlyArray<string>,\n withEvent: boolean\n) =>\n evidence(\n googleEmulatorApisOrigin,\n method,\n `${calendarEventsPath}${path}`,\n write,\n caseIds,\n withEvent\n ? { calendarId: calendarIdSegment, eventId: eventIdSegment }\n : { calendarId: calendarIdSegment }\n )\n\n// Recorded values.\n\nconst createdDescription = 'Synthetic conformance event without attendees.'\n\nconst renamedDescription = 'Synthetic conformance event, renamed.'\n\nconst eventSummaryRest = 'synthetic conformance event, safe to delete'\n\n/** The fixed half hour every recorded event create sends. */\nconst recordedStart = { dateTime: '2030-01-07T09:00:00Z', timeZone: 'UTC' }\n\nconst recordedEnd = { dateTime: '2030-01-07T09:30:00Z', timeZone: 'UTC' }\n\nconst instantPattern =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d{1,9})?(?:Z|[+-](\\d{2}):(\\d{2}))$/\n\n/**\n * True for an RFC 3339 instant with a `Z` or numeric offset whose components are in range (a real\n * calendar date, hour 0-23, minute and second 0-59, offset hour 0-23 and minute 0-59), checked\n * before `Date.parse`, which would roll an out-of-range component over instead of refusing it.\n */\nconst isInstant = (text: string): boolean => {\n const parts = instantPattern.exec(text)\n\n if (parts === null) return false\n\n const [year, month, day, hour, minute, second, offsetHour, offsetMinute] = parts\n .slice(1)\n .map(part => Number(part ?? '0'))\n\n if (year === undefined || month === undefined || day === undefined) return false\n\n const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0\n const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] ?? 0\n\n return (\n day >= 1 &&\n day <= days &&\n (hour ?? 24) <= 23 &&\n (minute ?? 60) <= 59 &&\n (second ?? 60) <= 59 &&\n (offsetHour ?? 24) <= 23 &&\n (offsetMinute ?? 60) <= 59 &&\n Number.isFinite(Date.parse(text))\n )\n}\n\n// Rendering, in the fixture key order.\n\ntype EventHead = {\n readonly kind: string\n readonly etag: string\n readonly id: string\n readonly status: string\n readonly htmlLink: string\n readonly created: string\n readonly updated: string\n readonly summary: string\n description?: string\n}\n\ntype EventsPage = {\n readonly kind: string\n readonly summary: string\n readonly timeZone: string\n readonly accessRole: string\n readonly items: ReadonlyArray<Schema.Json>\n nextPageToken?: string\n}\n\nconst renderEvent = (\n state: GoogleEmulatorState,\n owner: GoogleEmulatorCalendar,\n event: GoogleEmulatorCalendarEvent\n): Schema.JsonObject => {\n const head: EventHead = {\n kind: 'calendar#event',\n etag: event.etag,\n id: event.id,\n status: event.status,\n htmlLink: event.htmlLink,\n created: event.created,\n updated: event.updated,\n summary: event.summary\n }\n\n // The fixtures show `description` only on events that have one.\n if (event.description !== undefined) head.description = event.description\n\n return {\n ...head,\n creator: { email: state.practiceAddress, self: true },\n organizer: { email: owner.id, displayName: owner.summary, self: true },\n start: { ...event.start },\n end: { ...event.end },\n iCalUID: event.iCalUID,\n sequence: event.sequence,\n reminders: { useDefault: true },\n eventType: 'default'\n }\n}\n\n/** The instant a boundary starts (an all-day date at midnight UTC), in milliseconds. */\nconst instant = (boundary: GoogleEmulatorEventBoundary): number =>\n 'dateTime' in boundary ? Date.parse(boundary.dateTime) : Date.parse(`${boundary.date}T00:00:00Z`)\n\nconst findCalendar = (state: GoogleEmulatorState, id: string) =>\n state.calendars.find(candidate => candidate.id === id)\n\nconst findEvent = (state: GoogleEmulatorState, calendarId: string, eventId: string) =>\n state.events.find(event => event.calendarId === calendarId && event.id === eventId)\n\nconst isCreatedHere = (event: GoogleEmulatorCalendarEvent): boolean =>\n mintedEventIdPattern.test(event.id)\n\nconst replaceEvent = (state: GoogleEmulatorState, next: GoogleEmulatorCalendarEvent) =>\n state.events.map(event =>\n event.calendarId === next.calendarId && event.id === next.id ? next : event\n )\n\n/** The `{ dateTime, timeZone }` boundary a fixture records, exactly. */\nconst isRecordedBoundary = (\n value: Schema.Json | undefined,\n recorded: { readonly dateTime: string; readonly timeZone: string }\n): boolean => {\n const boundary = exactBodyKeys(value, 'boundary', ['dateTime', 'timeZone'])\n\n return (\n !isNotEmulated(boundary) &&\n boundary.dateTime === recorded.dateTime &&\n boundary.timeZone === recorded.timeZone\n )\n}\n\n// Listing.\n\ntype ListInput = {\n readonly calendarId: string\n readonly timeMin: string\n readonly timeMax: string\n readonly maxResults: number\n readonly pageToken: string | undefined\n}\n\nconst listEvents: GoogleRoute = statefulRoute(\n calendar('GET', '', false, [listCase], false),\n 'none',\n (request): ListInput | NotEmulated => {\n const query = exactQuery(\n request,\n ['timeMin', 'timeMax', 'maxResults', 'singleEvents', 'orderBy'],\n ['pageToken']\n )\n\n if (isNotEmulated(query)) return query\n\n const recorded =\n recordedValue(query, 'singleEvents', 'true') ?? recordedValue(query, 'orderBy', 'startTime')\n\n if (recorded !== undefined) return recorded\n\n const timeMin = query['timeMin'] ?? ''\n const timeMax = query['timeMax'] ?? ''\n\n if (\n !isInstant(timeMin) ||\n !isInstant(timeMax) ||\n !(Date.parse(timeMin) < Date.parse(timeMax))\n ) {\n return notEmulated('timeMin and timeMax must be RFC 3339 instants, timeMin first')\n }\n\n const maxResults = pageSize(query['maxResults'], 'maxResults', 1, 2500)\n\n if (isNotEmulated(maxResults)) return maxResults\n\n const pageToken = query['pageToken']\n\n return pageToken === ''\n ? notEmulated('an empty pageToken is not emulated')\n : { calendarId: param(request, 'calendarId'), timeMin, timeMax, maxResults, pageToken }\n },\n (state, input, { env }) => {\n const owner = findCalendar(state, input.calendarId)\n\n if (owner === undefined) return notEmulated('a calendar the state does not hold')\n\n const from = Date.parse(input.timeMin)\n const to = Date.parse(input.timeMax)\n\n // By start time (`orderBy=startTime`); `sort` is stable, so equal starts keep the state order.\n const overlapping = state.events\n .filter(\n event =>\n event.calendarId === owner.id && instant(event.start) < to && instant(event.end) > from\n )\n .sort((left, right) => instant(left.start) - instant(right.start))\n\n // Every recorded listing has events, and none of them is cancelled.\n if (overlapping.length === 0) {\n return notEmulated('a range without events is not emulated (no fixture records one)')\n }\n\n if (overlapping.some(event => event.status === 'cancelled')) {\n return notEmulated('a range holding a cancelled event is not emulated')\n }\n\n return listPage(\n env,\n 'calendar',\n `calendar\\u0000${owner.id}\\u0000${input.timeMin}\\u0000${input.timeMax}\\u0000${input.maxResults}`,\n overlapping.map(event => renderEvent(state, owner, event)),\n input.maxResults,\n input.pageToken,\n env.drills.calendarPageRepeats,\n (page, nextPageToken) => {\n const body: EventsPage = {\n kind: 'calendar#events',\n summary: owner.summary,\n timeZone: owner.timeZone,\n accessRole: owner.accessRole,\n items: [...page]\n }\n\n // The last page carries no `nextPageToken`.\n if (nextPageToken !== undefined) body.nextPageToken = nextPageToken\n\n return googleJson(200, body)\n }\n )\n }\n)\n\n// The event lifecycle.\n\ntype CreateInput = { readonly calendarId: string; readonly summary: string }\n\nconst createEvent: GoogleRoute = statefulRoute(\n calendar('POST', '', true, [lifecycleCase, deletedCase], false),\n 'json',\n (request): CreateInput | NotEmulated => {\n const body =\n withoutQuery(request) ??\n exactBodyKeys(request.json, 'the event body', ['summary', 'description', 'start', 'end'])\n\n if (isNotEmulated(body)) return body\n\n const summary = Predicate.isString(body.summary) ? body.summary : ''\n\n if (\n !isRunText(summary, 'event', eventSummaryRest) &&\n !isRunText(summary, 'deleted event', eventSummaryRest)\n ) {\n return notEmulated('an event summary other than the recorded run-scoped ones')\n }\n\n if (body.description !== createdDescription) {\n return notEmulated('an event description other than the recorded one is not emulated')\n }\n\n return isRecordedBoundary(body.start, recordedStart) &&\n isRecordedBoundary(body.end, recordedEnd)\n ? { calendarId: param(request, 'calendarId'), summary }\n : notEmulated('event times other than the recorded half hour are not emulated')\n },\n (state, input, { env }) => {\n const owner = findCalendar(state, input.calendarId)\n\n if (owner === undefined) return notEmulated('a calendar the state does not hold')\n\n return () => {\n // Read the clock before any write: a failing clock writes nothing.\n const at = new Date(env.now()).toISOString()\n const number = state.counters.nextEventNumber\n const id = mintedEventId(number)\n\n const event: GoogleEmulatorCalendarEvent = {\n calendarId: owner.id,\n id,\n ...eventFields(id),\n status: 'confirmed',\n created: at,\n updated: at,\n summary: input.summary,\n description: createdDescription,\n start: { ...recordedStart },\n end: { ...recordedEnd },\n sequence: 0\n }\n\n state.counters = { ...state.counters, nextEventNumber: number + 1 }\n state.events = [...state.events, event]\n\n return googleJson(200, renderEvent(state, owner, event))\n }\n }\n)\n\ntype EventRef = { readonly calendarId: string; readonly eventId: string }\n\nconst eventRef = (request: EmulatedRequest): EventRef => ({\n calendarId: param(request, 'calendarId'),\n eventId: param(request, 'eventId')\n})\n\nconst getEvent: GoogleRoute = statefulRoute(\n calendar('GET', '/{eventId}', false, [lifecycleCase, deletedCase], true),\n 'none',\n request => withoutQuery(request) ?? eventRef(request),\n (state, input) => {\n const owner = findCalendar(state, input.calendarId)\n const event = findEvent(state, input.calendarId, input.eventId)\n\n return owner === undefined || event === undefined\n ? notEmulated('an event the state does not hold is not emulated')\n : answer(() => googleJson(200, renderEvent(state, owner, event)))\n }\n)\n\ntype PatchInput = EventRef & { readonly summary: string }\n\nconst patchEvent: GoogleRoute = statefulRoute(\n calendar('PATCH', '/{eventId}', true, [lifecycleCase], true),\n 'json',\n (request): PatchInput | NotEmulated => {\n const body =\n withoutQuery(request) ??\n exactBodyKeys(request.json, 'the event patch', ['summary', 'description'])\n\n if (isNotEmulated(body)) return body\n\n const summary = Predicate.isString(body.summary) ? body.summary : ''\n\n return isRunText(summary, 'event renamed', eventSummaryRest) &&\n body.description === renamedDescription\n ? { ...eventRef(request), summary }\n : notEmulated('an event patch other than the recorded rename is not emulated')\n },\n (state, input, { env }) => {\n const owner = findCalendar(state, input.calendarId)\n const event = findEvent(state, input.calendarId, input.eventId)\n\n if (\n owner === undefined ||\n event === undefined ||\n !isCreatedHere(event) ||\n event.status !== 'confirmed'\n ) {\n return notEmulated('renaming anything but a live event created here is not emulated')\n }\n\n return () => {\n const at = new Date(env.now()).toISOString()\n\n const next: GoogleEmulatorCalendarEvent = {\n ...event,\n updated: at,\n summary: env.drills.eventPatchKeepsSummary ? event.summary : input.summary,\n description: renamedDescription\n }\n\n state.events = replaceEvent(state, next)\n\n return googleJson(200, renderEvent(state, owner, next))\n }\n }\n)\n\nconst deleteEvent: GoogleRoute = statefulRoute(\n calendar('DELETE', '/{eventId}', true, [lifecycleCase, deletedCase], true),\n 'none',\n request => withoutQuery(request) ?? eventRef(request),\n (state, input, { env }) => {\n const event = findEvent(state, input.calendarId, input.eventId)\n\n if (event === undefined || !isCreatedHere(event)) {\n return notEmulated('deleting anything but an event created here is not emulated')\n }\n\n // The recorded repeated delete: 410 Gone (the drill answers it as 409).\n if (event.status === 'cancelled') {\n return answer(() =>\n googleJson(\n env.drills.repeatedEventDeleteConflict ? 409 : 410,\n googleErrorBody(410, 'Resource has been deleted', 'deleted', 'GONE')\n )\n )\n }\n\n return () => {\n const at = new Date(env.now()).toISOString()\n\n state.events = replaceEvent(state, { ...event, status: 'cancelled', updated: at })\n\n return googleNoContent()\n }\n }\n)\n\n/** The Calendar routes, in manifest order. */\nexport const calendarRoutes: ReadonlyArray<GoogleRoute> = [\n listEvents,\n createEvent,\n getEvent,\n patchEvent,\n deleteEvent\n]\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drive.d.mts","names":[],"sources":["../../src/google/drive.ts"],"mappings":";;;;cAgda,WAAA,EAAa,aAAa,CAAC,WAAA"}
|