@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,483 @@
|
|
|
1
|
+
import { EmulatorHeaderRecord, EmulatorResponseStatus, emulatorJobHeader, redactCredentialFields, redactCredentialQuery } from "./emulator-http.mjs";
|
|
2
|
+
import { emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
3
|
+
import { codes, errorContext, graphError, microsoftEmulatorBasePath, microsoftEmulatorErrorCodes, parseJsonText } from "./microsoft/graph.mjs";
|
|
4
|
+
import { MicrosoftEmulatorAttachment, MicrosoftEmulatorAttachmentSeed, MicrosoftEmulatorCalendar, MicrosoftEmulatorDrive, MicrosoftEmulatorDriveItem, MicrosoftEmulatorDriveItemSeed, MicrosoftEmulatorEvent, MicrosoftEmulatorEventSeed, MicrosoftEmulatorMailFolder, MicrosoftEmulatorMessage, MicrosoftEmulatorMessageSeed, MicrosoftEmulatorProfile, MicrosoftEmulatorRecipient, MicrosoftEmulatorSeed, MicrosoftEmulatorStateSchema, MicrosoftEmulatorUser, buildSeedState, decodeState } from "./microsoft/state.mjs";
|
|
5
|
+
import { matchMicrosoftRoute, microsoftApiRoutes, registerMicrosoftApi, requestSeqHeader } from "./microsoft/api.mjs";
|
|
6
|
+
import { Data, Predicate, Result } from "effect";
|
|
7
|
+
import * as Schema from "effect/Schema";
|
|
8
|
+
//#region src/microsoft.ts
|
|
9
|
+
/** Origin the connector calls; also the origin of `@odata.nextLink` values. */
|
|
10
|
+
const microsoftEmulatorDefaultOrigin = "https://graph.microsoft.com";
|
|
11
|
+
/**
|
|
12
|
+
* Origin of OneDrive `webUrl` values and copy monitor URLs (the fixtures' synthetic SharePoint
|
|
13
|
+
* host). Route it to the same emulator as the Graph origin.
|
|
14
|
+
*/
|
|
15
|
+
const microsoftEmulatorDefaultSharePointOrigin = "https://synthetic-my.sharepoint.com";
|
|
16
|
+
/**
|
|
17
|
+
* Route evidence manifest: every emulated route, whether it writes, and the conformance cases
|
|
18
|
+
* whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
|
|
19
|
+
* by construction (both come from one route table). The monitor route lives on the SharePoint
|
|
20
|
+
* origin; every other route on the Graph origin.
|
|
21
|
+
*/
|
|
22
|
+
const microsoftEmulatorRoutes = microsoftApiRoutes.map(({ handler: _handler, queryKeys: _queryKeys, auth: _auth, ...evidence }) => evidence);
|
|
23
|
+
const FaultCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
24
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
25
|
+
const MicrosoftFaultMatch = Schema.Struct({
|
|
26
|
+
method: Schema.optionalKey(Schema.String),
|
|
27
|
+
path: Schema.optionalKey(Schema.String)
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* A wire fault: answer matching requests with this status, headers, and body (for example 429
|
|
31
|
+
* with `retry-after`) instead of the route's successful answer, so nothing is written. A request
|
|
32
|
+
* the route refuses is answered by the route and uses up no fault. The body defaults to a Graph
|
|
33
|
+
* error envelope. `count` limits how many requests it answers (omitted: all). `match.path` is
|
|
34
|
+
* the raw request path (`/v1.0/users/ada%40example.test/...`).
|
|
35
|
+
*
|
|
36
|
+
* The shared emulator rules apply: statuses that cannot carry a body (1xx, 204, 205) and
|
|
37
|
+
* redirects (3xx) are rejected, as are invalid header names or values, a `location` header, and
|
|
38
|
+
* framing headers (`content-length`, `transfer-encoding`, `connection`, `keep-alive`, `upgrade`).
|
|
39
|
+
*/
|
|
40
|
+
const MicrosoftFault = Schema.Struct({
|
|
41
|
+
kind: Schema.Literal("status"),
|
|
42
|
+
status: EmulatorResponseStatus,
|
|
43
|
+
headers: Schema.optionalKey(EmulatorHeaderRecord),
|
|
44
|
+
body: Schema.optionalKey(Schema.Json),
|
|
45
|
+
match: Schema.optionalKey(MicrosoftFaultMatch),
|
|
46
|
+
count: Schema.optionalKey(FaultCount)
|
|
47
|
+
});
|
|
48
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
49
|
+
var MicrosoftEmulatorInputInvalid = class extends Data.TaggedError("MicrosoftEmulatorInputInvalid") {
|
|
50
|
+
get message() {
|
|
51
|
+
return `Invalid Microsoft emulator ${this.input}: ${this.reason}`;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const strict = { onExcessProperty: "error" };
|
|
55
|
+
const decodeFault = Schema.decodeUnknownResult(MicrosoftFault, strict);
|
|
56
|
+
const decodeFaultList = Schema.decodeUnknownResult(Schema.Union([MicrosoftFault, Schema.Struct({ faults: Schema.Array(MicrosoftFault) })]), strict);
|
|
57
|
+
const issueMessage = (issue) => new Schema.SchemaError(issue).message;
|
|
58
|
+
const jsonResponse = (status, body, headers = {}) => {
|
|
59
|
+
const responseHeaders = new Headers(headers);
|
|
60
|
+
responseHeaders.set("content-type", "application/json");
|
|
61
|
+
return new Response(JSON.stringify(body), {
|
|
62
|
+
status,
|
|
63
|
+
headers: responseHeaders
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
const controlError = (status, message, headers = {}) => jsonResponse(status, { error: {
|
|
67
|
+
message,
|
|
68
|
+
type: "emulator_error"
|
|
69
|
+
} }, headers);
|
|
70
|
+
const defaultFaultBody = (context, status) => ({ error: {
|
|
71
|
+
code: status === 429 ? codes.rateLimited : codes.upstreamError,
|
|
72
|
+
message: status === 429 ? "Synthetic: too many requests." : `Synthetic upstream error (${status}).`,
|
|
73
|
+
innerError: {
|
|
74
|
+
date: context.date,
|
|
75
|
+
"request-id": context.requestId,
|
|
76
|
+
"client-request-id": context.clientRequestId
|
|
77
|
+
}
|
|
78
|
+
} });
|
|
79
|
+
const bearerPattern = /^bearer\s+\S+/i;
|
|
80
|
+
const pathMatches = (pattern, path) => pattern.endsWith("*") ? path.startsWith(pattern.slice(0, -1)) : pattern === path;
|
|
81
|
+
const faultMatches = (fault, method, path) => (fault.match?.method === void 0 || fault.match.method.toUpperCase() === method) && (fault.match?.path === void 0 || pathMatches(fault.match.path, path));
|
|
82
|
+
const readText = (request) => request.text().then((text) => text, () => void 0);
|
|
83
|
+
const validOrigin = (input) => {
|
|
84
|
+
if (!URL.canParse(input)) return;
|
|
85
|
+
const url = new URL(input);
|
|
86
|
+
const bare = (url.pathname === "/" || url.pathname === "") && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
87
|
+
return (url.protocol === "http:" || url.protocol === "https:") && bare ? url.origin : void 0;
|
|
88
|
+
};
|
|
89
|
+
const integerOption = (name, value, fallback, maximum) => {
|
|
90
|
+
const resolved = value ?? fallback;
|
|
91
|
+
if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > maximum) throw new MicrosoftEmulatorInputInvalid({
|
|
92
|
+
input: "option",
|
|
93
|
+
reason: `${name} must be an integer from 0 to ${maximum}`
|
|
94
|
+
});
|
|
95
|
+
return resolved;
|
|
96
|
+
};
|
|
97
|
+
const withEvidence = (response, evidence) => {
|
|
98
|
+
const headers = new Headers(response.headers);
|
|
99
|
+
if (evidence === "unverified") headers.set(emulatorEvidenceHeader, "unverified");
|
|
100
|
+
return new Response(response.body, {
|
|
101
|
+
status: response.status,
|
|
102
|
+
statusText: response.statusText,
|
|
103
|
+
headers
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const isControlPath = (path) => path === "/_emulate" || path.startsWith("/_emulate/");
|
|
107
|
+
/** The largest instant a JS `Date` holds (ms from the epoch, either way). */
|
|
108
|
+
const maxDateMs = 864e13;
|
|
109
|
+
/** Fixed synthetic `innerError.date` for error recovery when the clock fails (the epoch). */
|
|
110
|
+
const recoveryDateMs = 0;
|
|
111
|
+
const monitorState = (monitor) => {
|
|
112
|
+
const base = {
|
|
113
|
+
id: monitor.id,
|
|
114
|
+
sourceId: monitor.sourceId,
|
|
115
|
+
destinationParentId: monitor.destinationParentId,
|
|
116
|
+
pollsLeft: monitor.pollsLeft,
|
|
117
|
+
status: monitor.resourceId === void 0 ? "inProgress" : "completed"
|
|
118
|
+
};
|
|
119
|
+
return monitor.resourceId === void 0 ? base : {
|
|
120
|
+
...base,
|
|
121
|
+
resourceId: monitor.resourceId
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Create a stateful Microsoft Graph emulator on the `@emulators/core` custom runtime. Each call
|
|
126
|
+
* has its own state, ledger, faults, and copy monitors. Rejects with
|
|
127
|
+
* `MicrosoftEmulatorInputInvalid` for an invalid seed or option.
|
|
128
|
+
*
|
|
129
|
+
* Graph routes need `Authorization: Bearer <non-empty>` (missing: 401 Graph error envelope); the
|
|
130
|
+
* token is never checked, stored, forwarded to the core, or ledgered. The copy monitor needs no
|
|
131
|
+
* credential. Precedence per request: route match (unknown routes fail closed with a 404 Graph
|
|
132
|
+
* error envelope), authorization, JSON body parsing, then the stateful route's plan (its query
|
|
133
|
+
* allowlist and `If-Match` refusal, then its handler on drafts of the state and copy monitors),
|
|
134
|
+
* then the first matching fault, then the commit. A request the route refuses (any answer that is
|
|
135
|
+
* not 2xx, such as a 404 for an unknown id, a 400 not emulated, or a 409 conflict) uses up no
|
|
136
|
+
* fault; a faulted request writes nothing and holds no message. A handler that throws answers a 500
|
|
137
|
+
* Graph error envelope with `responseError` in the ledger, even when the clock throws; unknown
|
|
138
|
+
* routes (404) and a closed emulator (503) keep their status when the clock throws too. Every
|
|
139
|
+
* response from an unverified route carries `x-emulator-evidence: unverified`. Ledgered bodies and
|
|
140
|
+
* query parameters have credential-named keys redacted.
|
|
141
|
+
*/
|
|
142
|
+
const makeMicrosoftEmulator = async (options = {}) => {
|
|
143
|
+
const initial = buildSeedState(options.seed ?? {});
|
|
144
|
+
if (Predicate.isString(initial)) throw new MicrosoftEmulatorInputInvalid({
|
|
145
|
+
input: "seed",
|
|
146
|
+
reason: initial
|
|
147
|
+
});
|
|
148
|
+
const graphOrigin = validOrigin(options.baseUrl ?? "https://graph.microsoft.com");
|
|
149
|
+
const sharePointOrigin = validOrigin(options.sharePointOrigin ?? "https://synthetic-my.sharepoint.com");
|
|
150
|
+
if (graphOrigin === void 0 || sharePointOrigin === void 0) throw new MicrosoftEmulatorInputInvalid({
|
|
151
|
+
input: "option",
|
|
152
|
+
reason: "baseUrl and sharePointOrigin must be http(s) origins without path, query, hash, or credentials"
|
|
153
|
+
});
|
|
154
|
+
const now = options.now ?? (() => Date.now());
|
|
155
|
+
const env = {
|
|
156
|
+
now,
|
|
157
|
+
graphOrigin,
|
|
158
|
+
sharePointOrigin,
|
|
159
|
+
drills: {
|
|
160
|
+
calendarRangeEmpty: options.drills?.calendarRangeEmpty ?? false,
|
|
161
|
+
createOmitsId: options.drills?.createOmitsId ?? false,
|
|
162
|
+
timestampPrecisionDigits: integerOption("drills.timestampPrecisionDigits", options.drills?.timestampPrecisionDigits, 7, 7),
|
|
163
|
+
omitNextLink: options.drills?.omitNextLink ?? false
|
|
164
|
+
},
|
|
165
|
+
copyInProgressPolls: integerOption("copyInProgressPolls", options.copyInProgressPolls, 0, 100),
|
|
166
|
+
conflictWindowMs: integerOption("conflictWindowMs", options.conflictWindowMs, 25, 1e4),
|
|
167
|
+
messageLocks: /* @__PURE__ */ new Set(),
|
|
168
|
+
monitors: /* @__PURE__ */ new Map(),
|
|
169
|
+
monitorCounter: { next: 1 }
|
|
170
|
+
};
|
|
171
|
+
const core = await import("@emulators/core");
|
|
172
|
+
/**
|
|
173
|
+
* Fault decisions of the core requests in flight, by job id (asked by the route once it would
|
|
174
|
+
* answer successfully). Job ids never reset, so a ledger clear never makes two jobs share one.
|
|
175
|
+
*/
|
|
176
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
177
|
+
let nextJobId = 1;
|
|
178
|
+
const definition = core.defineEmulator({
|
|
179
|
+
name: "microsoft",
|
|
180
|
+
cors: false,
|
|
181
|
+
state: () => initial,
|
|
182
|
+
validateSeed: (value) => {
|
|
183
|
+
const decoded = decodeState(value);
|
|
184
|
+
if (Predicate.isString(decoded)) throw new MicrosoftEmulatorInputInvalid({
|
|
185
|
+
input: "seed",
|
|
186
|
+
reason: decoded
|
|
187
|
+
});
|
|
188
|
+
return decoded;
|
|
189
|
+
},
|
|
190
|
+
setup: ({ app, state }) => registerMicrosoftApi(app, state, env, (jobId) => jobId === null ? false : jobs.get(jobId)?.() ?? false)
|
|
191
|
+
});
|
|
192
|
+
const runtime = await core.createCustomRuntime(definition, { seed: initial });
|
|
193
|
+
let baseline = initial;
|
|
194
|
+
let entries = [];
|
|
195
|
+
let faultStates = [];
|
|
196
|
+
let nextSeq = 1;
|
|
197
|
+
let nextFaultId = 1;
|
|
198
|
+
let closed = false;
|
|
199
|
+
const snapshotEntry = (entry) => ({
|
|
200
|
+
...entry,
|
|
201
|
+
query: { ...entry.query }
|
|
202
|
+
});
|
|
203
|
+
const snapshotFault = (state) => ({ ...state });
|
|
204
|
+
const clearLedger = () => {
|
|
205
|
+
entries = [];
|
|
206
|
+
nextSeq = 1;
|
|
207
|
+
};
|
|
208
|
+
const clearMonitors = () => {
|
|
209
|
+
env.monitors.clear();
|
|
210
|
+
env.monitorCounter.next = 1;
|
|
211
|
+
};
|
|
212
|
+
const addFault = (input) => {
|
|
213
|
+
const decoded = decodeFault(input);
|
|
214
|
+
if (Result.isFailure(decoded)) return issueMessage(decoded.failure.issue);
|
|
215
|
+
const state = {
|
|
216
|
+
id: nextFaultId++,
|
|
217
|
+
fault: decoded.success,
|
|
218
|
+
remaining: decoded.success.count,
|
|
219
|
+
applied: 0
|
|
220
|
+
};
|
|
221
|
+
faultStates.push(state);
|
|
222
|
+
return snapshotFault(state);
|
|
223
|
+
};
|
|
224
|
+
const takeFault = (method, path) => faultStates.find((state) => (state.remaining === void 0 || state.remaining > 0) && faultMatches(state.fault, method, path));
|
|
225
|
+
/** Build the fault's response first: a response that cannot be built must not consume it. */
|
|
226
|
+
const applyFault = (state, context) => {
|
|
227
|
+
const headers = new Headers(state.fault.headers ?? {});
|
|
228
|
+
const body = state.fault.body ?? defaultFaultBody(context, state.fault.status);
|
|
229
|
+
if (!headers.has("content-type")) headers.set("content-type", Predicate.isString(body) ? "text/plain" : "application/json");
|
|
230
|
+
const response = new Response(Predicate.isString(body) ? body : JSON.stringify(body), {
|
|
231
|
+
status: state.fault.status,
|
|
232
|
+
headers
|
|
233
|
+
});
|
|
234
|
+
state.applied += 1;
|
|
235
|
+
if (state.remaining !== void 0) state.remaining -= 1;
|
|
236
|
+
return response;
|
|
237
|
+
};
|
|
238
|
+
const snapshot = () => runtime.snapshot().state;
|
|
239
|
+
const restore = (state) => {
|
|
240
|
+
const current = runtime.snapshot();
|
|
241
|
+
return runtime.restore({
|
|
242
|
+
...current,
|
|
243
|
+
state
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
const reset = async () => {
|
|
247
|
+
clearLedger();
|
|
248
|
+
faultStates = [];
|
|
249
|
+
clearMonitors();
|
|
250
|
+
await restore(baseline);
|
|
251
|
+
};
|
|
252
|
+
const reseed = async (input) => {
|
|
253
|
+
const next = buildSeedState(input);
|
|
254
|
+
if (Predicate.isString(next)) return next;
|
|
255
|
+
await restore(next);
|
|
256
|
+
clearMonitors();
|
|
257
|
+
baseline = next;
|
|
258
|
+
return next;
|
|
259
|
+
};
|
|
260
|
+
const coverage = () => ({
|
|
261
|
+
routes: microsoftEmulatorRoutes.map((route) => ({
|
|
262
|
+
...route,
|
|
263
|
+
requests: entries.filter((entry) => entry.route === route.path && entry.method.toUpperCase() === route.method).length
|
|
264
|
+
})),
|
|
265
|
+
unknownRouteRequests: entries.filter((entry) => entry.evidence === "unknown-route").length
|
|
266
|
+
});
|
|
267
|
+
const contextOf = (request, seq) => errorContext(now(), seq, request.headers.get("client-request-id"));
|
|
268
|
+
/**
|
|
269
|
+
* The clock for error recovery: read once, and a clock that throws (or answers a value that is
|
|
270
|
+
* not a finite date) falls back to a fixed synthetic date, so recovery never fails on it.
|
|
271
|
+
*/
|
|
272
|
+
const recoveryNow = () => {
|
|
273
|
+
try {
|
|
274
|
+
const value = now();
|
|
275
|
+
return Number.isFinite(value) && Math.abs(value) <= maxDateMs ? value : recoveryDateMs;
|
|
276
|
+
} catch {
|
|
277
|
+
return recoveryDateMs;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* The evidence-tagged fallback when a matched route cannot build or produce its response.
|
|
282
|
+
* Clock-independent: see `recoveryNow`.
|
|
283
|
+
*/
|
|
284
|
+
const responseFailed = (request, seq) => graphError(errorContext(recoveryNow(), seq, request.headers.get("client-request-id")), 500, codes.upstreamError, "Synthetic: the emulator could not build the response.");
|
|
285
|
+
/**
|
|
286
|
+
* Authorization, body parsing, then the core route (without the credential), which asks for the
|
|
287
|
+
* fault only once it would answer successfully.
|
|
288
|
+
*/
|
|
289
|
+
const routed = async (request, url, entry, auth) => {
|
|
290
|
+
const context = () => contextOf(request, entry.seq);
|
|
291
|
+
if (auth && !bearerPattern.test(request.headers.get("authorization") ?? "")) return graphError(context(), 401, codes.unauthenticated, "Access token is empty.");
|
|
292
|
+
const text = await readText(request);
|
|
293
|
+
if (text === void 0) return graphError(context(), 400, codes.invalidBody, "Synthetic: the request body is unreadable.");
|
|
294
|
+
if (text !== "") {
|
|
295
|
+
const json = parseJsonText(text);
|
|
296
|
+
if (json === void 0) return graphError(context(), 400, codes.invalidBody, "Synthetic: the request body is not valid JSON.");
|
|
297
|
+
entry.body = redactCredentialFields(json);
|
|
298
|
+
}
|
|
299
|
+
const method = request.method.toUpperCase();
|
|
300
|
+
const hasBody = text !== "" && method !== "GET" && method !== "HEAD";
|
|
301
|
+
const jobId = String(nextJobId++);
|
|
302
|
+
const headers = new Headers({
|
|
303
|
+
accept: "application/json",
|
|
304
|
+
[requestSeqHeader]: String(entry.seq),
|
|
305
|
+
[emulatorJobHeader]: jobId
|
|
306
|
+
});
|
|
307
|
+
for (const name of [
|
|
308
|
+
"prefer",
|
|
309
|
+
"if-match",
|
|
310
|
+
"client-request-id"
|
|
311
|
+
]) {
|
|
312
|
+
const value = request.headers.get(name);
|
|
313
|
+
if (value !== null) headers.set(name, value);
|
|
314
|
+
}
|
|
315
|
+
if (hasBody) headers.set("content-type", "application/json");
|
|
316
|
+
const init = {
|
|
317
|
+
method,
|
|
318
|
+
headers
|
|
319
|
+
};
|
|
320
|
+
if (hasBody) init.body = text;
|
|
321
|
+
const job = {};
|
|
322
|
+
jobs.set(jobId, () => {
|
|
323
|
+
const fault = takeFault(method, url.pathname);
|
|
324
|
+
if (fault === void 0) return false;
|
|
325
|
+
try {
|
|
326
|
+
job.faultAnswer = applyFault(fault, context());
|
|
327
|
+
} catch (error) {
|
|
328
|
+
entry.responseError = "the emulator could not build or produce the response";
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
entry.fault = "status";
|
|
332
|
+
return true;
|
|
333
|
+
});
|
|
334
|
+
const response = await runtime.fetch(new Request(new URL(`${url.pathname}${url.search}`, runtime.baseUrl), init)).finally(() => jobs.delete(jobId));
|
|
335
|
+
if (job.faultAnswer !== void 0) return job.faultAnswer;
|
|
336
|
+
if (response.headers.has("x-emulator-handler-failed")) {
|
|
337
|
+
entry.responseError ??= "the route handler failed";
|
|
338
|
+
return responseFailed(request, entry.seq);
|
|
339
|
+
}
|
|
340
|
+
return response;
|
|
341
|
+
};
|
|
342
|
+
const emulatedApi = async (request, url) => {
|
|
343
|
+
const matched = matchMicrosoftRoute(request.method, url.pathname);
|
|
344
|
+
const prefer = request.headers.get("prefer");
|
|
345
|
+
const entry = {
|
|
346
|
+
seq: nextSeq++,
|
|
347
|
+
method: request.method,
|
|
348
|
+
path: url.pathname,
|
|
349
|
+
query: redactCredentialQuery(url.searchParams),
|
|
350
|
+
status: 0,
|
|
351
|
+
evidence: matched?.route.evidence ?? "unknown-route"
|
|
352
|
+
};
|
|
353
|
+
if (prefer !== null) entry.prefer = prefer;
|
|
354
|
+
entries.push(entry);
|
|
355
|
+
if (matched === void 0) {
|
|
356
|
+
entry.status = 404;
|
|
357
|
+
return graphError(errorContext(recoveryNow(), entry.seq, request.headers.get("client-request-id")), 404, codes.unknownRoute, "Synthetic: no emulated Microsoft Graph route.");
|
|
358
|
+
}
|
|
359
|
+
entry.route = matched.route.path;
|
|
360
|
+
const tagged = withEvidence(await routed(request, url, entry, matched.route.auth).catch(() => {
|
|
361
|
+
entry.responseError = "the emulator could not build or produce the response";
|
|
362
|
+
return responseFailed(request, entry.seq);
|
|
363
|
+
}), matched.route.evidence);
|
|
364
|
+
entry.status = tagged.status;
|
|
365
|
+
return tagged;
|
|
366
|
+
};
|
|
367
|
+
const controlPlane = async (request, path) => {
|
|
368
|
+
const method = request.method;
|
|
369
|
+
const allow = (methods) => controlError(405, "method not allowed", { allow: methods });
|
|
370
|
+
const jsonBody = async () => {
|
|
371
|
+
const text = await readText(request);
|
|
372
|
+
return text === void 0 ? void 0 : parseJsonText(text);
|
|
373
|
+
};
|
|
374
|
+
switch (path) {
|
|
375
|
+
case "/_emulate/ledger":
|
|
376
|
+
if (method === "GET") return jsonResponse(200, { entries: entries.map(snapshotEntry) });
|
|
377
|
+
if (method === "DELETE") {
|
|
378
|
+
const cleared = entries.length;
|
|
379
|
+
clearLedger();
|
|
380
|
+
return jsonResponse(200, { cleared });
|
|
381
|
+
}
|
|
382
|
+
return allow("GET, DELETE");
|
|
383
|
+
case "/_emulate/faults": {
|
|
384
|
+
if (method === "GET") return jsonResponse(200, { faults: faultStates.map(snapshotFault) });
|
|
385
|
+
if (method === "DELETE") {
|
|
386
|
+
const cleared = faultStates.length;
|
|
387
|
+
faultStates = [];
|
|
388
|
+
return jsonResponse(200, { cleared });
|
|
389
|
+
}
|
|
390
|
+
if (method !== "POST") return allow("GET, POST, DELETE");
|
|
391
|
+
const decoded = decodeFaultList(await jsonBody());
|
|
392
|
+
if (Result.isFailure(decoded)) return controlError(400, `invalid fault: ${issueMessage(decoded.failure.issue)}`);
|
|
393
|
+
return jsonResponse(201, { faults: ("faults" in decoded.success ? decoded.success.faults : [decoded.success]).map((fault) => addFault(fault)) });
|
|
394
|
+
}
|
|
395
|
+
case "/_emulate/reset":
|
|
396
|
+
if (method !== "POST") return allow("POST");
|
|
397
|
+
await reset();
|
|
398
|
+
return jsonResponse(200, { reset: true });
|
|
399
|
+
case "/_emulate/state":
|
|
400
|
+
if (method !== "GET") return allow("GET");
|
|
401
|
+
return jsonResponse(200, {
|
|
402
|
+
state: snapshot(),
|
|
403
|
+
monitors: [...env.monitors.values()].map(monitorState),
|
|
404
|
+
faults: faultStates.map(snapshotFault),
|
|
405
|
+
ledgerEntries: entries.length
|
|
406
|
+
});
|
|
407
|
+
case "/_emulate/seed": {
|
|
408
|
+
if (method !== "POST") return allow("POST");
|
|
409
|
+
const next = await reseed(await jsonBody() ?? null);
|
|
410
|
+
if (Predicate.isString(next)) return controlError(400, `invalid seed: ${next}`);
|
|
411
|
+
return jsonResponse(200, {
|
|
412
|
+
seeded: true,
|
|
413
|
+
messages: next.messages.length,
|
|
414
|
+
events: next.events.length,
|
|
415
|
+
driveItems: next.driveItems.length
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
case "/_emulate/coverage":
|
|
419
|
+
if (method !== "GET") return allow("GET");
|
|
420
|
+
return jsonResponse(200, coverage());
|
|
421
|
+
default: return controlError(404, "unknown control-plane route");
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Last resort when handling itself fails (for example an unparseable request URL): API
|
|
426
|
+
* requests answer a Graph error 500, tagged with the matched route's evidence; control-plane
|
|
427
|
+
* requests answer an emulator error.
|
|
428
|
+
*/
|
|
429
|
+
const lastResort = (request) => {
|
|
430
|
+
const path = URL.canParse(request.url) ? new URL(request.url).pathname : void 0;
|
|
431
|
+
if (path !== void 0 && isControlPath(path)) return controlError(500, "emulator failed to handle the request");
|
|
432
|
+
const matched = path === void 0 ? void 0 : matchMicrosoftRoute(request.method, path);
|
|
433
|
+
const failed = responseFailed(request, 0);
|
|
434
|
+
return matched === void 0 ? failed : withEvidence(failed, matched.route.evidence);
|
|
435
|
+
};
|
|
436
|
+
const handle = async (request) => {
|
|
437
|
+
if (closed) return graphError(errorContext(recoveryNow(), 0, request.headers.get("client-request-id")), 503, codes.upstreamError, "Synthetic: the emulator is closed.");
|
|
438
|
+
const url = new URL(request.url);
|
|
439
|
+
return isControlPath(url.pathname) ? controlPlane(request, url.pathname) : emulatedApi(request, url);
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
fetch: (request) => handle(request).catch(() => lastResort(request)),
|
|
443
|
+
baseUrl: graphOrigin,
|
|
444
|
+
sharePointOrigin,
|
|
445
|
+
ledger: {
|
|
446
|
+
entries: () => entries.map(snapshotEntry),
|
|
447
|
+
clear: clearLedger
|
|
448
|
+
},
|
|
449
|
+
faults: {
|
|
450
|
+
add: (fault) => {
|
|
451
|
+
const added = addFault(fault);
|
|
452
|
+
if (Predicate.isString(added)) throw new MicrosoftEmulatorInputInvalid({
|
|
453
|
+
input: "fault",
|
|
454
|
+
reason: added
|
|
455
|
+
});
|
|
456
|
+
return added;
|
|
457
|
+
},
|
|
458
|
+
list: () => faultStates.map(snapshotFault),
|
|
459
|
+
clear: () => {
|
|
460
|
+
faultStates = [];
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
monitors: () => [...env.monitors.values()].map(monitorState),
|
|
464
|
+
reset,
|
|
465
|
+
seed: async (input) => {
|
|
466
|
+
const next = await reseed(input);
|
|
467
|
+
if (Predicate.isString(next)) throw new MicrosoftEmulatorInputInvalid({
|
|
468
|
+
input: "seed",
|
|
469
|
+
reason: next
|
|
470
|
+
});
|
|
471
|
+
},
|
|
472
|
+
snapshot,
|
|
473
|
+
coverage,
|
|
474
|
+
close: () => {
|
|
475
|
+
closed = true;
|
|
476
|
+
return runtime.close();
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
};
|
|
480
|
+
//#endregion
|
|
481
|
+
export { MicrosoftEmulatorAttachment, MicrosoftEmulatorAttachmentSeed, MicrosoftEmulatorCalendar, MicrosoftEmulatorDrive, MicrosoftEmulatorDriveItem, MicrosoftEmulatorDriveItemSeed, MicrosoftEmulatorEvent, MicrosoftEmulatorEventSeed, MicrosoftEmulatorInputInvalid, MicrosoftEmulatorMailFolder, MicrosoftEmulatorMessage, MicrosoftEmulatorMessageSeed, MicrosoftEmulatorProfile, MicrosoftEmulatorRecipient, MicrosoftEmulatorSeed, MicrosoftEmulatorStateSchema, MicrosoftEmulatorUser, MicrosoftFault, MicrosoftFaultMatch, emulatorEvidenceHeader, makeMicrosoftEmulator, microsoftEmulatorBasePath, microsoftEmulatorDefaultOrigin, microsoftEmulatorDefaultSharePointOrigin, microsoftEmulatorErrorCodes, microsoftEmulatorRoutes };
|
|
482
|
+
|
|
483
|
+
//# sourceMappingURL=microsoft.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"microsoft.mjs","names":[],"sources":["../src/microsoft.ts"],"sourcesContent":["/**\n * Stateful Microsoft Graph emulator (`/v1.0` on `https://graph.microsoft.com`, plus the OneDrive\n * copy monitor URL on the SharePoint origin), built on the upstream `@emulators/core` custom\n * runtime, with a request ledger, wire faults, and an `/_emulate/*` control plane.\n *\n * It never imports SDK code: its wire shapes and default seed are copied as data from the\n * synthetic Microsoft conformance fixtures, and every route names the conformance cases it\n * follows in `microsoftEmulatorRoutes`. Only the routes those eleven cases need are emulated;\n * everything else fails closed with the Graph error envelope.\n *\n * Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by\n * `makeMicrosoftEmulator` (importing this module has no side effects).\n *\n * @experimental\n */\nimport type { EmulatorSnapshot } from '@emulators/core'\nimport { Data, Predicate, Result } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n EmulatorHeaderRecord,\n EmulatorResponseStatus,\n emulatorJobHeader,\n handlerFailedHeader,\n redactCredentialFields,\n redactCredentialQuery\n} from './emulator-http.ts'\nimport {\n matchMicrosoftRoute,\n microsoftApiRoutes,\n registerMicrosoftApi,\n requestSeqHeader\n} from './microsoft/api.ts'\nimport {\n codes,\n errorContext,\n graphError,\n parseJsonText,\n type CopyMonitor,\n type ErrorContext,\n type MicrosoftApiEnv,\n type MicrosoftEmulatorDrills\n} from './microsoft/graph.ts'\nimport {\n buildSeedState,\n decodeState,\n type MicrosoftEmulatorSeed,\n type MicrosoftEmulatorState\n} from './microsoft/state.ts'\nimport {\n emulatorEvidenceHeader,\n type EmulatorEvidence,\n type EmulatorRouteEvidence\n} from './route-evidence.ts'\n\nexport type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'\n\nexport { emulatorEvidenceHeader } from './route-evidence.ts'\n\nexport { microsoftEmulatorBasePath } from './microsoft/api.ts'\n\nexport { microsoftEmulatorErrorCodes, type MicrosoftEmulatorDrills } from './microsoft/graph.ts'\n\nexport {\n MicrosoftEmulatorAttachment,\n MicrosoftEmulatorAttachmentSeed,\n MicrosoftEmulatorCalendar,\n MicrosoftEmulatorDrive,\n MicrosoftEmulatorDriveItem,\n MicrosoftEmulatorDriveItemSeed,\n MicrosoftEmulatorEvent,\n MicrosoftEmulatorEventSeed,\n MicrosoftEmulatorMailFolder,\n MicrosoftEmulatorMessage,\n MicrosoftEmulatorMessageSeed,\n MicrosoftEmulatorProfile,\n MicrosoftEmulatorRecipient,\n MicrosoftEmulatorSeed,\n MicrosoftEmulatorStateSchema,\n MicrosoftEmulatorUser,\n type MicrosoftEmulatorState\n} from './microsoft/state.ts'\n\n/** Origin the connector calls; also the origin of `@odata.nextLink` values. */\nexport const microsoftEmulatorDefaultOrigin = 'https://graph.microsoft.com'\n\n/**\n * Origin of OneDrive `webUrl` values and copy monitor URLs (the fixtures' synthetic SharePoint\n * host). Route it to the same emulator as the Graph origin.\n */\nexport const microsoftEmulatorDefaultSharePointOrigin = 'https://synthetic-my.sharepoint.com'\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). The monitor route lives on the SharePoint\n * origin; every other route on the Graph origin.\n */\nexport const microsoftEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = microsoftApiRoutes.map(\n ({ handler: _handler, queryKeys: _queryKeys, auth: _auth, ...evidence }) => evidence\n)\n\nconst FaultCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\n/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */\nexport const MicrosoftFaultMatch = Schema.Struct({\n method: Schema.optionalKey(Schema.String),\n path: Schema.optionalKey(Schema.String)\n})\n\nexport type MicrosoftFaultMatch = typeof MicrosoftFaultMatch.Type\n\n/**\n * A wire fault: answer matching requests with this status, headers, and body (for example 429\n * with `retry-after`) instead of the route's successful answer, so nothing is written. A request\n * the route refuses is answered by the route and uses up no fault. The body defaults to a Graph\n * error envelope. `count` limits how many requests it answers (omitted: all). `match.path` is\n * the raw request path (`/v1.0/users/ada%40example.test/...`).\n *\n * The shared emulator rules apply: statuses that cannot carry a body (1xx, 204, 205) and\n * redirects (3xx) are rejected, as are invalid header names or values, a `location` header, and\n * framing headers (`content-length`, `transfer-encoding`, `connection`, `keep-alive`, `upgrade`).\n */\nexport const MicrosoftFault = Schema.Struct({\n kind: Schema.Literal('status'),\n status: EmulatorResponseStatus,\n headers: Schema.optionalKey(EmulatorHeaderRecord),\n body: Schema.optionalKey(Schema.Json),\n match: Schema.optionalKey(MicrosoftFaultMatch),\n count: Schema.optionalKey(FaultCount)\n})\n\nexport type MicrosoftFault = typeof MicrosoftFault.Type\n\n/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */\nexport class MicrosoftEmulatorInputInvalid extends Data.TaggedError(\n 'MicrosoftEmulatorInputInvalid'\n)<{\n readonly input: 'seed' | 'fault' | 'option'\n readonly reason: string\n}> {\n override get message(): string {\n return `Invalid Microsoft emulator ${this.input}: ${this.reason}`\n }\n}\n\nexport type MicrosoftLedgerEntry = {\n /** 1-based arrival order since the last ledger clear or reset. */\n readonly seq: number\n readonly method: string\n /** Raw request path, for example `/v1.0/users/ada%40example.test/messages`. */\n readonly path: string\n /** Path template of the matched route, for example `/v1.0/users/{userId}/messages`. */\n readonly route?: string\n /** Query parameters; the values of credential-named keys (`access_token`) are `<redacted>`. */\n readonly query: Readonly<Record<string, string>>\n /**\n * Parsed JSON request body, when there was one, with every credential-named key's value\n * (for example a `$batch` subrequest's `Authorization` header) replaced by `<redacted>`.\n */\n readonly body?: Schema.Json\n /** The `Prefer` header, when sent (never a credential). */\n readonly prefer?: string\n readonly status: number\n /** Evidence of the matched route; `unknown-route` for requests that failed closed. */\n readonly evidence: EmulatorEvidence | 'unknown-route'\n /** Set when a fault answered the request. */\n readonly fault?: 'status'\n /**\n * Set when the emulator could not build or produce the response, or a route handler threw; the\n * request was answered with a 500 Graph error envelope (still evidence-tagged) and no fault was\n * used up.\n */\n readonly responseError?: string\n}\n\nexport type MicrosoftFaultState = {\n readonly id: number\n readonly fault: MicrosoftFault\n /** Remaining matching requests; `undefined` for an unlimited fault. */\n readonly remaining: number | undefined\n readonly applied: number\n}\n\nexport type MicrosoftRouteCoverage = EmulatorRouteEvidence & {\n /** Ledger requests answered by this route since the last ledger clear or reset. */\n readonly requests: number\n}\n\nexport type MicrosoftCoverage = {\n readonly routes: ReadonlyArray<MicrosoftRouteCoverage>\n /** Ledger requests to unknown routes (failed closed). */\n readonly unknownRouteRequests: number\n}\n\n/** A copy monitor as reported by `/_emulate/state` and `monitors()`. */\nexport type MicrosoftCopyMonitorState = {\n readonly id: string\n readonly sourceId: string\n readonly destinationParentId: string\n readonly pollsLeft: number\n readonly status: 'inProgress' | 'completed'\n readonly resourceId?: string\n}\n\nexport type MicrosoftEmulatorOptions = {\n /** Typed seed; defaults to the fixture entities (`profile: 'default'`). */\n readonly seed?: MicrosoftEmulatorSeed\n /** Clock in epoch milliseconds. Defaults to `Date.now`. */\n readonly now?: () => number\n /** Origin of `@odata.nextLink` values. Defaults to `https://graph.microsoft.com`. */\n readonly baseUrl?: string\n /** Origin of `webUrl` values and monitor URLs. Defaults to `https://synthetic-my.sharepoint.com`. */\n readonly sharePointOrigin?: string\n /**\n * In-progress monitor answers before a copy completes (integer 0-100, default 0: the first poll\n * completes, as the copy fixture records).\n */\n readonly copyInProgressPolls?: number\n /**\n * How long (ms, integer 0-10000, default 25) the first committed message write holds its\n * message (a refused or faulted write holds nothing); an overlapping write to that message gets\n * 409. Non-overlapping writes both apply (an emulator extrapolation). `0` only rejects writes\n * that overlap the handler itself.\n */\n readonly conflictWindowMs?: number\n /** Drill knobs (tests only): make the emulator disagree with one conformance claim. */\n readonly drills?: MicrosoftEmulatorDrills\n}\n\nexport type MicrosoftEmulator = {\n /** The fetch handler (Graph routes, the copy monitor, and `/_emulate/*`). Never rejects. */\n readonly fetch: (request: Request) => Promise<Response>\n /** Origin of `@odata.nextLink` values. */\n readonly baseUrl: string\n /** Origin of `webUrl` values and monitor URLs; route it to this emulator too. */\n readonly sharePointOrigin: string\n readonly ledger: {\n readonly entries: () => ReadonlyArray<MicrosoftLedgerEntry>\n readonly clear: () => void\n }\n readonly faults: {\n /** Add a fault; throws `MicrosoftEmulatorInputInvalid` for an invalid fault. */\n readonly add: (fault: MicrosoftFault) => MicrosoftFaultState\n readonly list: () => ReadonlyArray<MicrosoftFaultState>\n readonly clear: () => void\n }\n /** The copy monitors created since the last reset or seed. */\n readonly monitors: () => ReadonlyArray<MicrosoftCopyMonitorState>\n /** Restore the current seed and clear the ledger, faults, and copy monitors. */\n readonly reset: () => Promise<void>\n /**\n * Replace the state with a new seed, which becomes what `reset` restores (monitors are\n * cleared). Rejects with `MicrosoftEmulatorInputInvalid` for an invalid seed.\n */\n readonly seed: (seed: MicrosoftEmulatorSeed) => Promise<void>\n /** A deep copy of the current state (entities and counters). */\n readonly snapshot: () => MicrosoftEmulatorState\n readonly coverage: () => MicrosoftCoverage\n /** Close the core runtime. Later requests answer 503. Idempotent. */\n readonly close: () => Promise<void>\n}\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeFault = Schema.decodeUnknownResult(MicrosoftFault, strict)\n\nconst decodeFaultList = Schema.decodeUnknownResult(\n Schema.Union([MicrosoftFault, Schema.Struct({ faults: Schema.Array(MicrosoftFault) })]),\n strict\n)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\nconst jsonResponse = (status: number, body: unknown, headers: HeadersInit = {}): Response => {\n const responseHeaders = new Headers(headers)\n\n responseHeaders.set('content-type', 'application/json')\n\n return new Response(JSON.stringify(body), { status, headers: responseHeaders })\n}\n\nconst controlError = (status: number, message: string, headers: HeadersInit = {}): Response =>\n jsonResponse(status, { error: { message, type: 'emulator_error' } }, headers)\n\nconst defaultFaultBody = (context: ErrorContext, status: number): Schema.Json => ({\n error: {\n code: status === 429 ? codes.rateLimited : codes.upstreamError,\n message:\n status === 429 ? 'Synthetic: too many requests.' : `Synthetic upstream error (${status}).`,\n innerError: {\n date: context.date,\n 'request-id': context.requestId,\n 'client-request-id': context.clientRequestId\n }\n }\n})\n\n// A non-empty bearer credential. The value is never checked, stored, forwarded, or ledgered.\nconst bearerPattern = /^bearer\\s+\\S+/i\n\nconst pathMatches = (pattern: string, path: string): boolean =>\n pattern.endsWith('*') ? path.startsWith(pattern.slice(0, -1)) : pattern === path\n\nconst faultMatches = (fault: MicrosoftFault, method: string, path: string): boolean =>\n (fault.match?.method === undefined || fault.match.method.toUpperCase() === method) &&\n (fault.match?.path === undefined || pathMatches(fault.match.path, path))\n\nconst readText = (request: Request): Promise<string | undefined> =>\n request.text().then(\n text => text,\n () => undefined\n )\n\nconst validOrigin = (input: string): string | undefined => {\n if (!URL.canParse(input)) {\n return undefined\n }\n\n const url = new URL(input)\n\n const bare =\n (url.pathname === '/' || url.pathname === '') &&\n url.search === '' &&\n url.hash === '' &&\n url.username === '' &&\n url.password === ''\n\n return (url.protocol === 'http:' || url.protocol === 'https:') && bare ? url.origin : undefined\n}\n\nconst integerOption = (\n name: string,\n value: number | undefined,\n fallback: number,\n maximum: number\n): number => {\n const resolved = value ?? fallback\n\n if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > maximum) {\n throw new MicrosoftEmulatorInputInvalid({\n input: 'option',\n reason: `${name} must be an integer from 0 to ${maximum}`\n })\n }\n\n return resolved\n}\n\ntype MutableLedgerEntry = {\n seq: number\n method: string\n path: string\n route?: string\n query: Readonly<Record<string, string>>\n body?: Schema.Json\n prefer?: string\n status: number\n evidence: EmulatorEvidence | 'unknown-route'\n fault?: 'status'\n responseError?: string\n}\n\n/** One core request's fault outcome: the fault's answer, set when a fault answered it. */\ntype FaultJob = { faultAnswer?: Response }\n\ntype MutableFaultState = {\n readonly id: number\n readonly fault: MicrosoftFault\n remaining: number | undefined\n applied: number\n}\n\nconst withEvidence = (response: Response, evidence: EmulatorEvidence): Response => {\n const headers = new Headers(response.headers)\n\n if (evidence === 'unverified') {\n headers.set(emulatorEvidenceHeader, 'unverified')\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers\n })\n}\n\nconst isControlPath = (path: string): boolean =>\n path === '/_emulate' || path.startsWith('/_emulate/')\n\n/** The largest instant a JS `Date` holds (ms from the epoch, either way). */\nconst maxDateMs = 8_640_000_000_000_000\n\n/** Fixed synthetic `innerError.date` for error recovery when the clock fails (the epoch). */\nconst recoveryDateMs = 0\n\nconst monitorState = (monitor: CopyMonitor): MicrosoftCopyMonitorState => {\n const base: MicrosoftCopyMonitorState = {\n id: monitor.id,\n sourceId: monitor.sourceId,\n destinationParentId: monitor.destinationParentId,\n pollsLeft: monitor.pollsLeft,\n status: monitor.resourceId === undefined ? 'inProgress' : 'completed'\n }\n\n return monitor.resourceId === undefined ? base : { ...base, resourceId: monitor.resourceId }\n}\n\n/**\n * Create a stateful Microsoft Graph emulator on the `@emulators/core` custom runtime. Each call\n * has its own state, ledger, faults, and copy monitors. Rejects with\n * `MicrosoftEmulatorInputInvalid` for an invalid seed or option.\n *\n * Graph routes need `Authorization: Bearer <non-empty>` (missing: 401 Graph error envelope); the\n * token is never checked, stored, forwarded to the core, or ledgered. The copy monitor needs no\n * credential. Precedence per request: route match (unknown routes fail closed with a 404 Graph\n * error envelope), authorization, JSON body parsing, then the stateful route's plan (its query\n * allowlist and `If-Match` refusal, then its handler on drafts of the state and copy monitors),\n * then the first matching fault, then the commit. A request the route refuses (any answer that is\n * not 2xx, such as a 404 for an unknown id, a 400 not emulated, or a 409 conflict) uses up no\n * fault; a faulted request writes nothing and holds no message. A handler that throws answers a 500\n * Graph error envelope with `responseError` in the ledger, even when the clock throws; unknown\n * routes (404) and a closed emulator (503) keep their status when the clock throws too. Every\n * response from an unverified route carries `x-emulator-evidence: unverified`. Ledgered bodies and\n * query parameters have credential-named keys redacted.\n */\nexport const makeMicrosoftEmulator = async (\n options: MicrosoftEmulatorOptions = {}\n): Promise<MicrosoftEmulator> => {\n const initial = buildSeedState(options.seed ?? {})\n\n if (Predicate.isString(initial)) {\n throw new MicrosoftEmulatorInputInvalid({ input: 'seed', reason: initial })\n }\n\n const graphOrigin = validOrigin(options.baseUrl ?? microsoftEmulatorDefaultOrigin)\n\n const sharePointOrigin = validOrigin(\n options.sharePointOrigin ?? microsoftEmulatorDefaultSharePointOrigin\n )\n\n if (graphOrigin === undefined || sharePointOrigin === undefined) {\n throw new MicrosoftEmulatorInputInvalid({\n input: 'option',\n reason:\n 'baseUrl and sharePointOrigin must be http(s) origins without path, query, hash, or credentials'\n })\n }\n\n const now = options.now ?? (() => Date.now())\n\n const env: MicrosoftApiEnv = {\n now,\n graphOrigin,\n sharePointOrigin,\n drills: {\n calendarRangeEmpty: options.drills?.calendarRangeEmpty ?? false,\n createOmitsId: options.drills?.createOmitsId ?? false,\n timestampPrecisionDigits: integerOption(\n 'drills.timestampPrecisionDigits',\n options.drills?.timestampPrecisionDigits,\n 7,\n 7\n ),\n omitNextLink: options.drills?.omitNextLink ?? false\n },\n copyInProgressPolls: integerOption('copyInProgressPolls', options.copyInProgressPolls, 0, 100),\n conflictWindowMs: integerOption('conflictWindowMs', options.conflictWindowMs, 25, 10_000),\n messageLocks: new Set(),\n monitors: new Map(),\n monitorCounter: { next: 1 }\n }\n\n // Loaded lazily: the core imports Node builtins and reads files at import time.\n const core = await import('@emulators/core')\n\n /**\n * Fault decisions of the core requests in flight, by job id (asked by the route once it would\n * answer successfully). Job ids never reset, so a ledger clear never makes two jobs share one.\n */\n const jobs = new Map<string, () => boolean>()\n let nextJobId = 1\n\n const definition = core.defineEmulator<MicrosoftEmulatorState>({\n name: 'microsoft',\n cors: false,\n state: () => initial,\n validateSeed: value => {\n const decoded = decodeState(value)\n\n if (Predicate.isString(decoded)) {\n throw new MicrosoftEmulatorInputInvalid({ input: 'seed', reason: decoded })\n }\n\n return decoded\n },\n setup: ({ app, state }) =>\n registerMicrosoftApi(app, state, env, jobId =>\n jobId === null ? false : (jobs.get(jobId)?.() ?? false)\n )\n })\n\n const runtime = await core.createCustomRuntime(definition, { seed: initial })\n\n let baseline: MicrosoftEmulatorState = initial\n let entries: Array<MutableLedgerEntry> = []\n let faultStates: Array<MutableFaultState> = []\n let nextSeq = 1\n let nextFaultId = 1\n let closed = false\n\n const snapshotEntry = (entry: MutableLedgerEntry): MicrosoftLedgerEntry => ({\n ...entry,\n query: { ...entry.query }\n })\n\n const snapshotFault = (state: MutableFaultState): MicrosoftFaultState => ({ ...state })\n\n const clearLedger = () => {\n entries = []\n nextSeq = 1\n }\n\n const clearMonitors = () => {\n env.monitors.clear()\n env.monitorCounter.next = 1\n }\n\n const addFault = (input: unknown): MicrosoftFaultState | string => {\n const decoded = decodeFault(input)\n\n if (Result.isFailure(decoded)) {\n return issueMessage(decoded.failure.issue)\n }\n\n const state: MutableFaultState = {\n id: nextFaultId++,\n fault: decoded.success,\n remaining: decoded.success.count,\n applied: 0\n }\n\n faultStates.push(state)\n\n return snapshotFault(state)\n }\n\n const takeFault = (method: string, path: string): MutableFaultState | undefined =>\n faultStates.find(\n state =>\n (state.remaining === undefined || state.remaining > 0) &&\n faultMatches(state.fault, method, path)\n )\n\n /** Build the fault's response first: a response that cannot be built must not consume it. */\n const applyFault = (state: MutableFaultState, context: ErrorContext): Response => {\n const headers = new Headers(state.fault.headers ?? {})\n const body = state.fault.body ?? defaultFaultBody(context, state.fault.status)\n\n if (!headers.has('content-type')) {\n headers.set('content-type', Predicate.isString(body) ? 'text/plain' : 'application/json')\n }\n\n const response = new Response(Predicate.isString(body) ? body : JSON.stringify(body), {\n status: state.fault.status,\n headers\n })\n\n state.applied += 1\n\n if (state.remaining !== undefined) {\n state.remaining -= 1\n }\n\n return response\n }\n\n const snapshot = (): MicrosoftEmulatorState => runtime.snapshot().state\n\n const restore = (state: MicrosoftEmulatorState): Promise<void> => {\n const current: EmulatorSnapshot<MicrosoftEmulatorState> = runtime.snapshot()\n\n return runtime.restore({ ...current, state })\n }\n\n const reset = async () => {\n clearLedger()\n faultStates = []\n clearMonitors()\n await restore(baseline)\n }\n\n const reseed = async (input: unknown): Promise<MicrosoftEmulatorState | string> => {\n const next = buildSeedState(input)\n\n if (Predicate.isString(next)) {\n return next\n }\n\n await restore(next)\n clearMonitors()\n baseline = next\n\n return next\n }\n\n const coverage = (): MicrosoftCoverage => ({\n routes: microsoftEmulatorRoutes.map(route => ({\n ...route,\n requests: entries.filter(\n entry => entry.route === route.path && entry.method.toUpperCase() === route.method\n ).length\n })),\n unknownRouteRequests: entries.filter(entry => entry.evidence === 'unknown-route').length\n })\n\n const contextOf = (request: Request, seq: number): ErrorContext =>\n errorContext(now(), seq, request.headers.get('client-request-id'))\n\n /**\n * The clock for error recovery: read once, and a clock that throws (or answers a value that is\n * not a finite date) falls back to a fixed synthetic date, so recovery never fails on it.\n */\n const recoveryNow = (): number => {\n try {\n const value = now()\n\n return Number.isFinite(value) && Math.abs(value) <= maxDateMs ? value : recoveryDateMs\n } catch {\n return recoveryDateMs\n }\n }\n\n /**\n * The evidence-tagged fallback when a matched route cannot build or produce its response.\n * Clock-independent: see `recoveryNow`.\n */\n const responseFailed = (request: Request, seq: number): Response =>\n graphError(\n errorContext(recoveryNow(), seq, request.headers.get('client-request-id')),\n 500,\n codes.upstreamError,\n 'Synthetic: the emulator could not build the response.'\n )\n\n /**\n * Authorization, body parsing, then the core route (without the credential), which asks for the\n * fault only once it would answer successfully.\n */\n const routed = async (\n request: Request,\n url: URL,\n entry: MutableLedgerEntry,\n auth: boolean\n ): Promise<Response> => {\n // Built on demand, so the clock is read only when the wrapper answers an error itself.\n const context = () => contextOf(request, entry.seq)\n\n if (auth && !bearerPattern.test(request.headers.get('authorization') ?? '')) {\n return graphError(context(), 401, codes.unauthenticated, 'Access token is empty.')\n }\n\n const text = await readText(request)\n\n if (text === undefined) {\n return graphError(\n context(),\n 400,\n codes.invalidBody,\n 'Synthetic: the request body is unreadable.'\n )\n }\n\n if (text !== '') {\n const json = parseJsonText(text)\n\n if (json === undefined) {\n return graphError(\n context(),\n 400,\n codes.invalidBody,\n 'Synthetic: the request body is not valid JSON.'\n )\n }\n\n // Redacted before it is stored: a rejected request's body stays in the ledger too.\n entry.body = redactCredentialFields(json)\n }\n\n const method = request.method.toUpperCase()\n const hasBody = text !== '' && method !== 'GET' && method !== 'HEAD'\n const jobId = String(nextJobId++)\n\n const headers = new Headers({\n accept: 'application/json',\n [requestSeqHeader]: String(entry.seq),\n [emulatorJobHeader]: jobId\n })\n\n // Only non-credential headers the routes read are forwarded.\n for (const name of ['prefer', 'if-match', 'client-request-id']) {\n const value = request.headers.get(name)\n\n if (value !== null) headers.set(name, value)\n }\n\n if (hasBody) headers.set('content-type', 'application/json')\n\n const init: RequestInit = { method, headers }\n\n if (hasBody) {\n init.body = text\n }\n\n const job: FaultJob = {}\n\n // Asked by the route only for an answer it would send successfully, before it commits.\n jobs.set(jobId, () => {\n const fault = takeFault(method, url.pathname)\n\n if (fault === undefined) return false\n\n try {\n job.faultAnswer = applyFault(fault, context())\n } catch (error) {\n // The fault's answer could not be built (the route did not fail): the core answers its\n // handler failure, and the fault is not used up.\n entry.responseError = 'the emulator could not build or produce the response'\n\n throw error\n }\n\n entry.fault = 'status'\n\n return true\n })\n\n const response = await runtime\n .fetch(new Request(new URL(`${url.pathname}${url.search}`, runtime.baseUrl), init))\n .finally(() => jobs.delete(jobId))\n\n // The fault's answer is sent from here, never through the core.\n if (job.faultAnswer !== undefined) return job.faultAnswer\n\n if (response.headers.has(handlerFailedHeader)) {\n entry.responseError ??= 'the route handler failed'\n\n return responseFailed(request, entry.seq)\n }\n\n return response\n }\n\n const emulatedApi = async (request: Request, url: URL): Promise<Response> => {\n const matched = matchMicrosoftRoute(request.method, url.pathname)\n const prefer = request.headers.get('prefer')\n\n const entry: MutableLedgerEntry = {\n seq: nextSeq++,\n method: request.method,\n path: url.pathname,\n query: redactCredentialQuery(url.searchParams),\n status: 0,\n evidence: matched?.route.evidence ?? 'unknown-route'\n }\n\n if (prefer !== null) entry.prefer = prefer\n\n entries.push(entry)\n\n if (matched === undefined) {\n entry.status = 404\n\n // Clock-independent (see `recoveryNow`), so the answer is the 404 the ledger records.\n return graphError(\n errorContext(recoveryNow(), entry.seq, request.headers.get('client-request-id')),\n 404,\n codes.unknownRoute,\n 'Synthetic: no emulated Microsoft Graph route.'\n )\n }\n\n entry.route = matched.route.path\n\n // Error recovery still answers through the route: the fallback 500 is evidence-tagged and\n // the ledger records the status actually sent.\n const response = await routed(request, url, entry, matched.route.auth).catch(() => {\n entry.responseError = 'the emulator could not build or produce the response'\n\n return responseFailed(request, entry.seq)\n })\n\n const tagged = withEvidence(response, matched.route.evidence)\n\n entry.status = tagged.status\n\n return tagged\n }\n\n const controlPlane = async (request: Request, path: string): Promise<Response> => {\n const method = request.method\n const allow = (methods: string) => controlError(405, 'method not allowed', { allow: methods })\n\n const jsonBody = async (): Promise<Schema.Json | undefined> => {\n const text = await readText(request)\n\n return text === undefined ? undefined : parseJsonText(text)\n }\n\n switch (path) {\n case '/_emulate/ledger':\n if (method === 'GET') {\n return jsonResponse(200, { entries: entries.map(snapshotEntry) })\n }\n\n if (method === 'DELETE') {\n const cleared = entries.length\n\n clearLedger()\n\n return jsonResponse(200, { cleared })\n }\n\n return allow('GET, DELETE')\n\n case '/_emulate/faults': {\n if (method === 'GET') {\n return jsonResponse(200, { faults: faultStates.map(snapshotFault) })\n }\n\n if (method === 'DELETE') {\n const cleared = faultStates.length\n\n faultStates = []\n\n return jsonResponse(200, { cleared })\n }\n\n if (method !== 'POST') {\n return allow('GET, POST, DELETE')\n }\n\n const decoded = decodeFaultList(await jsonBody())\n\n if (Result.isFailure(decoded)) {\n return controlError(400, `invalid fault: ${issueMessage(decoded.failure.issue)}`)\n }\n\n const faults = 'faults' in decoded.success ? decoded.success.faults : [decoded.success]\n\n return jsonResponse(201, { faults: faults.map(fault => addFault(fault)) })\n }\n\n case '/_emulate/reset':\n if (method !== 'POST') {\n return allow('POST')\n }\n\n await reset()\n\n return jsonResponse(200, { reset: true })\n\n case '/_emulate/state':\n if (method !== 'GET') {\n return allow('GET')\n }\n\n return jsonResponse(200, {\n state: snapshot(),\n monitors: [...env.monitors.values()].map(monitorState),\n faults: faultStates.map(snapshotFault),\n ledgerEntries: entries.length\n })\n\n case '/_emulate/seed': {\n if (method !== 'POST') {\n return allow('POST')\n }\n\n const next = await reseed((await jsonBody()) ?? null)\n\n if (Predicate.isString(next)) {\n return controlError(400, `invalid seed: ${next}`)\n }\n\n return jsonResponse(200, {\n seeded: true,\n messages: next.messages.length,\n events: next.events.length,\n driveItems: next.driveItems.length\n })\n }\n\n case '/_emulate/coverage':\n if (method !== 'GET') {\n return allow('GET')\n }\n\n return jsonResponse(200, coverage())\n\n default:\n return controlError(404, 'unknown control-plane route')\n }\n }\n\n /**\n * Last resort when handling itself fails (for example an unparseable request URL): API\n * requests answer a Graph error 500, tagged with the matched route's evidence; control-plane\n * requests answer an emulator error.\n */\n const lastResort = (request: Request): Response => {\n const path = URL.canParse(request.url) ? new URL(request.url).pathname : undefined\n\n if (path !== undefined && isControlPath(path)) {\n return controlError(500, 'emulator failed to handle the request')\n }\n\n const matched = path === undefined ? undefined : matchMicrosoftRoute(request.method, path)\n const failed = responseFailed(request, 0)\n\n return matched === undefined ? failed : withEvidence(failed, matched.route.evidence)\n }\n\n const handle = async (request: Request): Promise<Response> => {\n if (closed) {\n // Clock-independent (see `recoveryNow`): a closed emulator always answers 503.\n return graphError(\n errorContext(recoveryNow(), 0, request.headers.get('client-request-id')),\n 503,\n codes.upstreamError,\n 'Synthetic: the emulator is closed.'\n )\n }\n\n const url = new URL(request.url)\n\n return isControlPath(url.pathname)\n ? controlPlane(request, url.pathname)\n : emulatedApi(request, url)\n }\n\n return {\n fetch: request => handle(request).catch(() => lastResort(request)),\n baseUrl: graphOrigin,\n sharePointOrigin,\n ledger: {\n entries: () => entries.map(snapshotEntry),\n clear: clearLedger\n },\n faults: {\n add: fault => {\n const added = addFault(fault)\n\n if (Predicate.isString(added)) {\n throw new MicrosoftEmulatorInputInvalid({ input: 'fault', reason: added })\n }\n\n return added\n },\n list: () => faultStates.map(snapshotFault),\n clear: () => {\n faultStates = []\n }\n },\n monitors: () => [...env.monitors.values()].map(monitorState),\n reset,\n seed: async input => {\n const next = await reseed(input)\n\n if (Predicate.isString(next)) {\n throw new MicrosoftEmulatorInputInvalid({ input: 'seed', reason: next })\n }\n },\n snapshot,\n coverage,\n close: () => {\n closed = true\n\n return runtime.close()\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/node.d.mts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Effect, Scope } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/node.d.ts
|
|
4
|
+
/** A fetch handler, such as the one returned by an emulator factory. */
|
|
5
|
+
type FetchHandler = (request: Request) => Promise<Response>;
|
|
6
|
+
type ServeFetchHandlerOptions = {
|
|
7
|
+
/** TCP port; `0` (default) picks a free port. */readonly port?: number; /** Only `127.0.0.1` is accepted (the default); any other host is refused. */
|
|
8
|
+
readonly host?: string;
|
|
9
|
+
};
|
|
10
|
+
/** The only host emulator servers bind to. */
|
|
11
|
+
declare const emulatorServerHost = "127.0.0.1";
|
|
12
|
+
declare const EmulatorServeError_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 & {
|
|
13
|
+
readonly _tag: "EmulatorServeError";
|
|
14
|
+
} & Readonly<A>;
|
|
15
|
+
/** Serving failed: a refused host, an invalid port, or a listen error. */
|
|
16
|
+
declare class EmulatorServeError extends EmulatorServeError_base<{
|
|
17
|
+
readonly reason: 'host-not-allowed' | 'invalid-port' | 'listen-failed';
|
|
18
|
+
readonly cause?: unknown;
|
|
19
|
+
}> {
|
|
20
|
+
get message(): string;
|
|
21
|
+
}
|
|
22
|
+
/** A running loopback server (Promise variant). `close` is idempotent. */
|
|
23
|
+
type RunningFetchHandlerServer = {
|
|
24
|
+
/** Base URL, for example `http://127.0.0.1:43123`. */readonly url: string;
|
|
25
|
+
readonly close: () => Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
/** A running loopback server (Effect variant). `close` is idempotent. */
|
|
28
|
+
type ServedFetchHandler = {
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly close: Effect.Effect<void>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Serve a fetch handler on `127.0.0.1` (Promise variant, for hosts without an
|
|
34
|
+
* Effect runtime). Rejects with `EmulatorServeError` for a non-loopback host,
|
|
35
|
+
* an invalid port, or a listen failure.
|
|
36
|
+
*/
|
|
37
|
+
declare const startFetchHandlerServer: (handler: FetchHandler, options?: ServeFetchHandlerOptions) => Promise<RunningFetchHandlerServer>;
|
|
38
|
+
/**
|
|
39
|
+
* Serve a fetch handler on `127.0.0.1` as a scoped resource: the server
|
|
40
|
+
* closes when the scope closes (or earlier via `close`). Fails with
|
|
41
|
+
* `EmulatorServeError` for a non-loopback host, an invalid port, or a listen
|
|
42
|
+
* failure.
|
|
43
|
+
*/
|
|
44
|
+
declare const serveFetchHandler: (handler: FetchHandler, options?: ServeFetchHandlerOptions) => Effect.Effect<ServedFetchHandler, EmulatorServeError, Scope.Scope>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { EmulatorServeError, FetchHandler, RunningFetchHandlerServer, ServeFetchHandlerOptions, ServedFetchHandler, emulatorServerHost, serveFetchHandler, startFetchHandlerServer };
|
|
47
|
+
//# sourceMappingURL=node.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.mts","names":[],"sources":["../src/node.ts"],"mappings":";;;;KAiBY,YAAA,IAAgB,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA,KAE7C,wBAAA;EAFY,0DAIb,IAAA,WAJ0B;EAAA,SAM1B,IAAI;AAAA;;cAIF,kBAAA;AAAA,cAAgC,uBAAA;;;;cAGhC,kBAAA,SAA2B,uBAAA;EAAA,SAC7B,MAAA;EAAA,SACA,KAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAXA;AAAA,KAwBH,yBAAA;EApBmB,+DAsBpB,GAAA;EAAA,SACA,KAAA,QAAa,OAAO;AAAA;AAvBc;AAAA,KA2BjC,kBAAA;EAAA,SACD,GAAA;EAAA,SACA,KAAA,EAAO,MAAA,CAAO,MAAM;AAAA;;;;;;cA2JlB,uBAAA,GACX,OAAA,EAAS,YAAA,EACT,OAAA,GAAS,wBAAA,KACR,OAAA,CAAQ,yBAAA;;;;;;;cA2DE,iBAAA,GACX,OAAA,EAAS,YAAA,EACT,OAAA,GAAS,wBAAA,KACR,MAAA,CAAO,MAAA,CAAO,kBAAA,EAAoB,kBAAA,EAAoB,KAAA,CAAM,KAAA"}
|