@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 @@
|
|
|
1
|
+
{"version":3,"file":"gmail.mjs","names":[],"sources":["../../src/google/gmail.ts"],"sourcesContent":["/**\n * Gmail routes of the Google emulator (internal): message listing by label with page tokens,\n * message reads in the recorded formats, attachments, the label lifecycle, label changes, trash and\n * untrash, the draft lifecycle with its thread, and the multipart practice send.\n *\n * Every answer comes from the Gmail fixtures, through the seed or the request, or is minted. The\n * practice send is irreversible on the real service: here it only records the sent message in the\n * state (nothing is delivered anywhere), and only a message whose sole recipient is the seeded\n * practice address, exactly as the send fixture records it, is accepted.\n *\n * @experimental\n */\nimport { Predicate } from 'effect'\nimport type * as Schema from 'effect/Schema'\nimport {\n DecodedViewRefusal,\n exactBodyKeys,\n exactQuery,\n isJsonObject,\n isNotEmulated,\n mediaType,\n notEmulated,\n parseJsonText,\n statefulRoute,\n type NotEmulated\n} from '../stateful-emulator.ts'\nimport {\n answer,\n evidence,\n gmailIdSegment,\n googleEmulatorGmailOrigin,\n googleErrorBody,\n googleJson,\n googleNoContent,\n isRunText,\n listPage,\n pageSize,\n param,\n recordedInternalDate,\n recordedRunIdLength,\n recordedValue,\n runIdOf,\n withoutQuery,\n type GoogleApiEnv,\n type GoogleRoute\n} from './shared.ts'\nimport {\n base64UrlOfText,\n lastLabelNumber,\n mintedLabelIdPattern,\n mintedDraftId,\n mintedMessageId,\n mintedMessageIdPattern,\n pad,\n type GoogleEmulatorGmailMessage,\n type GoogleEmulatorImpliedMessage,\n type GoogleEmulatorState\n} from './state.ts'\n\n/** Path prefix of every Gmail API route (`/gmail/v1/users/me`). */\nexport const gmailApiPath = '/gmail/v1/users/me'\n\nconst listCase = 'google.gmail.list-page-token'\n\nconst attachmentCase = 'google.gmail.attachment-base64url'\n\nconst notFoundCase = 'google.gmail.not-found-envelope'\n\nconst labelCase = 'google.gmail.label-create-apply-delete'\n\nconst draftCase = 'google.gmail.draft-compose-update-delete'\n\nconst trashCase = 'google.gmail.trash-untrash'\n\nconst sendCase = 'google.gmail.send-practice-address'\n\nconst gmail = (\n method: string,\n path: string,\n write: boolean,\n caseIds: ReadonlyArray<string>,\n params: ReadonlyArray<string> = []\n) =>\n evidence(\n googleEmulatorGmailOrigin,\n method,\n `${gmailApiPath}${path}`,\n write,\n caseIds,\n Object.fromEntries(params.map(name => [name, gmailIdSegment]))\n )\n\n// Recorded constants and renderings.\n\n/** The Gmail not-found envelope (`messages`, `labels`, and `drafts` reads and deletes). */\nconst notFound = (env: GoogleApiEnv): Response =>\n googleJson(\n 404,\n env.drills.notFoundWithoutMessage\n ? { error: { code: 404, status: 'NOT_FOUND' } }\n : googleErrorBody(404, 'Requested entity was not found.', 'notFound', 'NOT_FOUND')\n )\n\n/** A message id of the recorded absent form (16 lower-case hex digits). */\nconst absentMessageIdPattern = /^[0-9a-f]{16}$/\n\nconst draftIdPattern = /^r-\\d{1,20}$/\n\n/** The draft texts the draft fixture records (non-ASCII on purpose). */\nconst draftText = 'Synthetic conformance draft, safe to delete: grüße ✓'\n\nconst updatedDraftText = 'Updated synthetic conformance draft: ¡hola! ✓'\n\nconst draftSubjectRest = 'synthetic conformance draft, safe to delete'\n\nconst sendSubjectRest = 'synthetic conformance message, safe to delete'\n\n/** The practice address the send fixture records (the sender and the only recipient). */\nconst recordedPracticeAddress = 'practice@example.test'\n\n/** Why a draft or send subject with a run id of another length is not emulated. */\nconst sizedRunIdReason = (what: 'draft' | 'send'): string =>\n `a ${what} subject whose run id is not ${recordedRunIdLength} characters long (the fixtures' run-synthetic) is not emulated: the recorded sizeEstimate covers the subject`\n\n/** The 7-bit MIME the send fixture records for `practiceAddress` and `subject`. */\nconst practiceMime = (practiceAddress: string, subject: string): string =>\n [\n `To: ${practiceAddress}`,\n `Subject: ${subject}`,\n 'MIME-Version: 1.0',\n 'Content-Type: text/plain; charset=us-ascii',\n '',\n 'Synthetic conformance message sent to the seeded practice address only. Safe to delete.',\n ''\n ].join('\\r\\n')\n\n/** The `multipart/related` upload body the send fixture records. */\nconst uploadBody = (boundary: string, mime: string): string =>\n [\n `--${boundary}`,\n 'Content-Type: application/json; charset=UTF-8',\n '',\n '{}',\n `--${boundary}`,\n 'Content-Type: message/rfc822',\n '',\n mime,\n `--${boundary}--`\n ].join('\\r\\n')\n\ntype MessageFormat = 'minimal' | 'metadata' | 'full'\n\nconst head = (message: GoogleEmulatorGmailMessage): Schema.JsonObject => ({\n id: message.id,\n threadId: message.threadId,\n labelIds: [...message.labelIds],\n snippet: message.snippet,\n sizeEstimate: message.sizeEstimate,\n historyId: message.historyId,\n internalDate: message.internalDate\n})\n\n/** The recorded rendering of `message` in `format`, or `undefined` when none is recorded. */\nconst renderMessage = (\n message: GoogleEmulatorGmailMessage,\n format: MessageFormat\n): Schema.JsonObject | undefined => {\n switch (format) {\n case 'minimal':\n return message.minimal ? head(message) : undefined\n case 'metadata':\n return message.metadataPayload === null\n ? undefined\n : { ...head(message), payload: message.metadataPayload }\n case 'full':\n return message.fullPayload === null\n ? undefined\n : { ...head(message), payload: message.fullPayload }\n }\n}\n\n/** The `{ id, threadId, labelIds }` answer of a label change, trash, or untrash. */\nconst labelsAnswer = (\n message: GoogleEmulatorGmailMessage,\n labelIds: ReadonlyArray<string>\n): Response =>\n googleJson(200, { id: message.id, threadId: message.threadId, labelIds: [...labelIds] })\n\nconst findMessage = (state: GoogleEmulatorState, id: string) =>\n state.messages.find(message => message.id === id)\n\nconst isImpliedMessage = (state: GoogleEmulatorState, id: string): boolean =>\n state.impliedMessages.some(message => message.id === id)\n\nconst isDraftMessage = (message: GoogleEmulatorGmailMessage): boolean =>\n message.labelIds.includes('DRAFT')\n\nconst replaceMessage = (\n state: GoogleEmulatorState,\n next: GoogleEmulatorGmailMessage\n): ReadonlyArray<GoogleEmulatorGmailMessage> =>\n state.messages.map(message => (message.id === next.id ? next : message))\n\n// Listing by label.\n\ntype MessagesPageHead = {\n readonly messages: ReadonlyArray<Schema.Json>\n nextPageToken?: string\n}\n\ntype ListInput = {\n readonly labelId: string\n readonly maxResults: number\n readonly pageToken: string | undefined\n}\n\nconst listMessages: GoogleRoute = statefulRoute(\n gmail('GET', '/messages', false, [listCase]),\n 'none',\n (request): ListInput | NotEmulated => {\n const query = exactQuery(request, ['labelIds', 'maxResults'], ['pageToken'])\n\n if (isNotEmulated(query)) return query\n\n const labelId = query['labelIds'] ?? ''\n\n if (!gmailIdSegment.test(labelId)) return notEmulated('labelIds must be one label id')\n\n const maxResults = pageSize(query['maxResults'], 'maxResults', 1, 500)\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 : { labelId, maxResults, pageToken }\n },\n (state, input, { env }) => {\n const known =\n state.impliedLabelIds.includes(input.labelId) ||\n state.labels.some(label => label.id === input.labelId)\n\n if (!known) return notEmulated('listing a label the state does not hold is not emulated')\n\n const matches: ReadonlyArray<GoogleEmulatorGmailMessage | GoogleEmulatorImpliedMessage> = [\n ...state.messages,\n ...state.impliedMessages\n ].filter(message => message.labelIds.includes(input.labelId))\n\n // Every recorded listing has results; an empty one is no fixture's answer.\n if (matches.length === 0) {\n return notEmulated('a listing without messages is not emulated (no fixture records one)')\n }\n\n if (matches.some(message => message.labelIds.some(id => id === 'TRASH' || id === 'SPAM'))) {\n return notEmulated('a listing that would leave out messages in Trash or Spam is not emulated')\n }\n\n return listPage(\n env,\n 'gmail',\n `gmail\\u0000${input.labelId}\\u0000${input.maxResults}`,\n matches.map(message => ({ id: message.id, threadId: message.threadId })),\n input.maxResults,\n input.pageToken,\n env.drills.gmailPageRepeats,\n (page, nextPageToken) => {\n const head: MessagesPageHead = { messages: [...page] }\n\n // The last page carries no `nextPageToken`.\n if (nextPageToken !== undefined) head.nextPageToken = nextPageToken\n\n return googleJson(200, { ...head, resultSizeEstimate: matches.length })\n }\n )\n }\n)\n\n// Message reads.\n\ntype MessageRead = { readonly id: string; readonly format: MessageFormat }\n\nconst formats: ReadonlyArray<string> = ['minimal', 'metadata', 'full']\n\nconst isFormat = (value: string | undefined): value is MessageFormat =>\n value !== undefined && formats.includes(value)\n\nconst getMessage: GoogleRoute = statefulRoute(\n gmail(\n 'GET',\n '/messages/{messageId}',\n false,\n [attachmentCase, notFoundCase, labelCase, draftCase, trashCase, sendCase],\n ['messageId']\n ),\n 'none',\n (request): MessageRead | NotEmulated => {\n const query = exactQuery(request, ['format'])\n\n if (isNotEmulated(query)) return query\n\n const format = query['format']\n\n return isFormat(format)\n ? { id: param(request, 'messageId'), format }\n : notEmulated('format must be minimal, metadata, or full')\n },\n (state, input, { env }) => {\n const message = findMessage(state, input.id)\n\n if (message !== undefined) {\n const rendered = renderMessage(message, input.format)\n\n return rendered === undefined\n ? notEmulated(`no fixture records this message with format=${input.format}`)\n : answer(() => googleJson(200, rendered))\n }\n\n if (isImpliedMessage(state, input.id)) {\n return notEmulated('reading a message a fixture only names in a listing is not emulated')\n }\n\n // The recorded not-found answer: a minimal read of an id the mailbox does not hold.\n return input.format === 'minimal' && absentMessageIdPattern.test(input.id)\n ? answer(() => notFound(env))\n : notEmulated('only a format=minimal read of an absent 16-hex-digit id is recorded')\n }\n)\n\ntype AttachmentRead = { readonly messageId: string; readonly attachmentId: string }\n\nconst getAttachment: GoogleRoute = statefulRoute(\n gmail(\n 'GET',\n '/messages/{messageId}/attachments/{attachmentId}',\n false,\n [attachmentCase],\n ['messageId', 'attachmentId']\n ),\n 'none',\n (request): AttachmentRead | NotEmulated =>\n withoutQuery(request) ?? {\n messageId: param(request, 'messageId'),\n attachmentId: param(request, 'attachmentId')\n },\n (state, input, { env }) => {\n const attachment = state.attachments.find(\n item => item.messageId === input.messageId && item.attachmentId === input.attachmentId\n )\n\n if (attachment === undefined || findMessage(state, input.messageId) === undefined) {\n return notEmulated('an attachment the state does not hold is not emulated')\n }\n\n const data = env.drills.attachmentStandardBase64\n ? attachment.data.replaceAll('-', '+').replaceAll('_', '/')\n : attachment.data\n\n return answer(() => googleJson(200, { size: attachment.size, data }))\n }\n)\n\n// Labels.\n\nconst createLabel: GoogleRoute = statefulRoute(\n gmail('POST', '/labels', true, [labelCase]),\n 'json',\n (request): string | NotEmulated => {\n const body = withoutQuery(request) ?? exactBodyKeys(request.json, 'the label body', ['name'])\n\n if (isNotEmulated(body)) return body\n\n return Predicate.isString(body.name) && isRunText(body.name, 'label')\n ? body.name\n : notEmulated('a label name other than \"yolk-conformance <runId> label\" is not emulated')\n },\n (state, name) => {\n if (state.labels.some(label => label.name === name)) {\n return notEmulated('creating a label whose name exists is not emulated')\n }\n\n // Refused before any fault: no minted label id is left.\n if (state.counters.nextLabelNumber > lastLabelNumber) {\n return notEmulated('creating a label when no Label_<n> id is left is not emulated')\n }\n\n return () => {\n const number = state.counters.nextLabelNumber\n\n const label = {\n id: `Label_${number}`,\n name,\n messageListVisibility: 'show',\n labelListVisibility: 'labelShow',\n type: 'user'\n }\n\n state.counters = { ...state.counters, nextLabelNumber: number + 1 }\n state.labels = [...state.labels, label]\n\n return googleJson(200, { ...label })\n }\n }\n)\n\nconst getLabel: GoogleRoute = statefulRoute(\n gmail('GET', '/labels/{labelId}', false, [labelCase], ['labelId']),\n 'none',\n request => withoutQuery(request) ?? param(request, 'labelId'),\n (state, id, { env }) => {\n if (state.labels.some(label => label.id === id) || state.impliedLabelIds.includes(id)) {\n return notEmulated('no fixture records a label read of an existing label')\n }\n\n return mintedLabelIdPattern.test(id)\n ? answer(() => notFound(env))\n : notEmulated('only a read of an absent Label_<n> id is recorded')\n }\n)\n\nconst deleteLabel: GoogleRoute = statefulRoute(\n gmail('DELETE', '/labels/{labelId}', true, [labelCase], ['labelId']),\n 'none',\n request => withoutQuery(request) ?? param(request, 'labelId'),\n (state, id, { env }) => {\n if (!state.labels.some(label => label.id === id)) {\n return notEmulated('only deleting a label created here is recorded')\n }\n\n return () => {\n const without = <M extends { readonly labelIds: ReadonlyArray<string> }>(message: M): M =>\n message.labelIds.includes(id)\n ? { ...message, labelIds: message.labelIds.filter(label => label !== id) }\n : message\n\n state.labels = state.labels.filter(label => label.id !== id)\n\n // Deleting a label removes it from every message (the label fixture's last read).\n if (!env.drills.labelDeleteKeepsOnMessages) {\n state.messages = state.messages.map(without)\n state.impliedMessages = state.impliedMessages.map(without)\n }\n\n return googleNoContent()\n }\n }\n)\n\n// Label changes, trash, and untrash.\n\ntype ModifyInput = { readonly messageId: string; readonly labelId: string }\n\nconst modifyMessage: GoogleRoute = statefulRoute(\n gmail('POST', '/messages/{messageId}/modify', true, [labelCase], ['messageId']),\n 'json',\n (request): ModifyInput | NotEmulated => {\n const body =\n withoutQuery(request) ?? exactBodyKeys(request.json, 'the modify body', ['addLabelIds'])\n\n if (isNotEmulated(body)) return body\n\n const added = body.addLabelIds\n\n if (!Array.isArray(added) || added.length !== 1 || !Predicate.isString(added[0])) {\n return notEmulated('a modify adding other than one label is not emulated')\n }\n\n return { messageId: param(request, 'messageId'), labelId: added[0] }\n },\n (state, input) => {\n const message = findMessage(state, input.messageId)\n\n if (message === undefined || isDraftMessage(message)) {\n return notEmulated('changing the labels of a message the state does not hold is not emulated')\n }\n\n if (!state.labels.some(label => label.id === input.labelId)) {\n return notEmulated('adding a label other than one created here is not emulated')\n }\n\n if (message.labelIds.includes(input.labelId)) {\n return notEmulated('adding a label the message carries already is not emulated')\n }\n\n return () => {\n const next = { ...message, labelIds: [...message.labelIds, input.labelId] }\n\n state.messages = replaceMessage(state, next)\n\n return labelsAnswer(next, next.labelIds)\n }\n }\n)\n\nconst trashRoute = (action: 'trash' | 'untrash'): GoogleRoute =>\n statefulRoute(\n gmail('POST', `/messages/{messageId}/${action}`, true, [trashCase], ['messageId']),\n 'none',\n request => withoutQuery(request) ?? param(request, 'messageId'),\n (state, id, { env }) => {\n const message = findMessage(state, id)\n\n if (message === undefined || isDraftMessage(message)) {\n return notEmulated(`${action} of a message the state does not hold is not emulated`)\n }\n\n const inTrash = message.labelIds.includes('TRASH')\n\n if (action === 'trash' ? inTrash : !inTrash) {\n return notEmulated(\n action === 'trash'\n ? 'trashing a message already in Trash is not emulated'\n : 'untrashing a message not in Trash is not emulated'\n )\n }\n\n return () => {\n const labelIds =\n action === 'trash'\n ? [...message.labelIds, 'TRASH']\n : message.labelIds.filter(label => label !== 'TRASH')\n\n const next = { ...message, labelIds }\n\n state.messages = replaceMessage(state, next)\n\n return labelsAnswer(\n next,\n action === 'trash' && env.drills.trashAnswerOmitsTrash\n ? labelIds.filter(label => label !== 'TRASH')\n : labelIds\n )\n }\n }\n )\n\n// Drafts.\n\nconst base64UrlPattern = /^[A-Za-z0-9_-]*$/\n\n/** The UTF-8 text of canonical unpadded base64url, or `undefined`. */\nconst decodeBase64UrlText = (raw: string): string | undefined => {\n if (!base64UrlPattern.test(raw) || raw.length % 4 === 1) return undefined\n\n try {\n const binary = atob(raw.replaceAll('-', '+').replaceAll('_', '/'))\n const bytes = Uint8Array.from(binary, char => char.charCodeAt(0))\n const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n\n // Canonical only: the encoding the connector writes, byte for byte.\n return base64UrlOfText(text) === raw ? text : undefined\n } catch {\n return undefined\n }\n}\n\nconst draftMimePattern =\n /^Subject: ([^\\r\\n]*)\\r\\nContent-Type: text\\/plain; charset=utf-8\\r\\n\\r\\n([\\s\\S]*)$/\n\ntype DraftContent = { readonly subject: string; readonly body: string }\n\n/** The reasons a draft `message.raw` is refused with (constants; never derived from a request). */\nconst draftKeysReason = 'message has a key this route does not take'\n\nconst canonicalRawReason = 'message.raw must be canonical base64url UTF-8 MIME'\n\nconst otherDraftReason = (kind: 'compose' | 'update'): string =>\n `a draft ${kind} other than the recorded run draft is not emulated`\n\n/**\n * Every reason `readDraftRaw` refuses a draft of `kind` with: the route's declared\n * `viewRefusalReasons`, so the wrapper ledgers exactly these (and nothing derived from a request).\n */\nconst draftRefusalReasons = (kind: 'compose' | 'update'): ReadonlyArray<string> => [\n draftKeysReason,\n canonicalRawReason,\n otherDraftReason(kind),\n sizedRunIdReason('draft')\n]\n\ntype DraftRaw = {\n /** The decoded MIME when `message.raw` is canonical base64url UTF-8 (decoded once). */\n readonly decoded: string | undefined\n readonly content: DraftContent | NotEmulated\n}\n\n/** The recorded draft MIME (no recipient) of a compose or an update, decoded once. */\nconst readDraftRaw = (message: Schema.Json | undefined, kind: 'compose' | 'update'): DraftRaw => {\n // Decode `raw` before the key check, so a cleanly decoded raw is always available to the\n // credential guard (a bearer inside it is a repeat, whatever else is wrong with `message`).\n const raw =\n Predicate.hasProperty(message, 'raw') && !Array.isArray(message) ? message.raw : undefined\n\n const decoded = Predicate.isString(raw) ? decodeBase64UrlText(raw) : undefined\n\n const fields = exactBodyKeys(message, 'message', ['raw'])\n\n if (isNotEmulated(fields)) return { decoded, content: fields }\n\n if (decoded === undefined) return { decoded, content: notEmulated(canonicalRawReason) }\n\n const parts = draftMimePattern.exec(decoded)\n const subject = parts?.[1] ?? ''\n const body = parts?.[2] ?? ''\n\n const runId =\n kind === 'compose'\n ? runIdOf(subject, 'draft', draftSubjectRest)\n : runIdOf(subject, 'draft updated', draftSubjectRest)\n\n const recordedBody = kind === 'compose' ? draftText : updatedDraftText\n\n if (parts === null || runId === undefined || body !== recordedBody) {\n return { decoded, content: notEmulated(otherDraftReason(kind)) }\n }\n\n return {\n decoded,\n content:\n runId.length === recordedRunIdLength\n ? { subject, body }\n : notEmulated(sizedRunIdReason('draft'))\n }\n}\n\n/**\n * The decoded view of a draft body for the credential guard (Gmail's own wire format wraps the\n * draft MIME in base64url `message.raw`). Fail closed: when the body is a JSON object whose\n * `message` object carries `raw`, the view returns the decoded MIME only when it is exactly the\n * recorded draft MIME of this route (`kind`), the run id aside. Any other raw makes it throw a\n * `DecodedViewRefusal` with the route's own constant reason (one of `draftRefusalReasons`) and\n * whatever it decoded cleanly: the wrapper ledgers a repeat when that text holds the bearer, and\n * the declared reason otherwise, before anything is recorded, so a refused raw (line-wrapped, the\n * standard alphabet, a stray character, RFC 2047 words, quoted-printable, UTF-16, a 12-character\n * run id, ...) never reaches the ledger. A body without `message.raw` yields no view (the route's\n * own shape check refuses it).\n */\nconst draftRawViews =\n (kind: 'compose' | 'update') =>\n (body: string): ReadonlyArray<string> => {\n const parsed = parseJsonText(body)\n const message = isJsonObject(parsed) ? parsed.message : undefined\n\n if (!isJsonObject(message) || !('raw' in message)) return []\n\n const { decoded, content } = readDraftRaw(message, kind)\n\n if (isNotEmulated(content)) {\n throw new DecodedViewRefusal({\n reason: content.reason,\n decoded: decoded === undefined ? [] : [decoded]\n })\n }\n\n return decoded === undefined ? [] : [decoded]\n }\n\nconst textPayload = (subject: string, body: Schema.JsonObject): Schema.JsonObject => ({\n partId: '',\n mimeType: 'text/plain',\n filename: '',\n headers: [\n { name: 'Subject', value: subject },\n { name: 'Content-Type', value: 'text/plain; charset=utf-8' }\n ],\n body\n})\n\n/** A draft message as the draft fixture records it (the metadata rendering only on compose). */\nconst draftMessage = (\n id: string,\n threadId: string,\n content: DraftContent,\n withMetadata: boolean\n): GoogleEmulatorGmailMessage => ({\n id,\n threadId,\n labelIds: ['DRAFT'],\n snippet: content.body,\n sizeEstimate: 512,\n historyId: '900020',\n internalDate: recordedInternalDate,\n minimal: false,\n metadataPayload: withMetadata ? textPayload(content.subject, { size: 64 }) : null,\n fullPayload: textPayload(content.subject, {\n size: new TextEncoder().encode(content.body).byteLength,\n data: base64UrlOfText(content.body)\n })\n})\n\nconst draftAnswer = (draftId: string, message: GoogleEmulatorGmailMessage): Response =>\n googleJson(200, {\n id: draftId,\n message: { id: message.id, threadId: message.threadId, labelIds: [...message.labelIds] }\n })\n\nconst composeDraft: GoogleRoute = statefulRoute(\n {\n ...gmail('POST', '/drafts', true, [draftCase]),\n decodedViews: draftRawViews('compose'),\n viewRefusalReasons: draftRefusalReasons('compose')\n },\n 'json',\n (request): DraftContent | NotEmulated => {\n const body = withoutQuery(request) ?? exactBodyKeys(request.json, 'the draft body', ['message'])\n\n return isNotEmulated(body) ? body : readDraftRaw(body.message, 'compose').content\n },\n (state, content) => () => {\n const draftNumber = state.counters.nextDraftNumber\n const messageNumber = state.counters.nextDraftMessageNumber\n const id = mintedMessageId('d', messageNumber)\n const message = draftMessage(id, id, content, true)\n const draftId = mintedDraftId(draftNumber)\n\n state.counters = {\n ...state.counters,\n nextDraftNumber: draftNumber + 1,\n nextDraftMessageNumber: messageNumber + 1\n }\n state.messages = [...state.messages, message]\n state.drafts = [...state.drafts, { id: draftId, messageId: id }]\n\n return draftAnswer(draftId, message)\n }\n)\n\ntype UpdateInput = { readonly draftId: string; readonly content: DraftContent }\n\nconst updateDraft: GoogleRoute = statefulRoute(\n {\n ...gmail('PUT', '/drafts/{draftId}', true, [draftCase], ['draftId']),\n decodedViews: draftRawViews('update'),\n viewRefusalReasons: draftRefusalReasons('update')\n },\n 'json',\n (request): UpdateInput | NotEmulated => {\n const draftId = param(request, 'draftId')\n\n const body =\n withoutQuery(request) ?? exactBodyKeys(request.json, 'the draft body', ['id', 'message'])\n\n if (isNotEmulated(body)) return body\n\n if (body.id !== draftId) return notEmulated('a body id other than the path draft id')\n\n const content = readDraftRaw(body.message, 'update').content\n\n return isNotEmulated(content) ? content : { draftId, content }\n },\n (state, input, { env }) => {\n const draft = state.drafts.find(candidate => candidate.id === input.draftId)\n const previous = draft === undefined ? undefined : findMessage(state, draft.messageId)\n\n if (draft === undefined || previous === undefined) {\n return notEmulated('updating a draft the state does not hold is not emulated')\n }\n\n return () => {\n const number = state.counters.nextDraftMessageNumber\n const id = mintedMessageId('d', number)\n\n const message: GoogleEmulatorGmailMessage = env.drills.draftUpdateKeepsContent\n ? { ...previous, id, metadataPayload: null }\n : draftMessage(id, previous.threadId, input.content, false)\n\n state.counters = { ...state.counters, nextDraftMessageNumber: number + 1 }\n state.messages = [\n ...state.messages.filter(candidate => candidate.id !== previous.id),\n message\n ]\n state.drafts = state.drafts.map(candidate =>\n candidate.id === draft.id ? { ...candidate, messageId: id } : candidate\n )\n\n return draftAnswer(draft.id, message)\n }\n }\n)\n\nconst deleteDraft: GoogleRoute = statefulRoute(\n gmail('DELETE', '/drafts/{draftId}', true, [draftCase], ['draftId']),\n 'none',\n request => withoutQuery(request) ?? param(request, 'draftId'),\n (state, id, { env }) => {\n const draft = state.drafts.find(candidate => candidate.id === id)\n\n if (draft === undefined) {\n // The recorded repeated delete: an absent draft answers the not-found envelope.\n return draftIdPattern.test(id)\n ? answer(() => notFound(env))\n : notEmulated('only deleting an absent r-<digits> draft id is recorded')\n }\n\n return () => {\n state.drafts = state.drafts.filter(candidate => candidate.id !== id)\n state.messages = state.messages.filter(message => message.id !== draft.messageId)\n\n return googleNoContent()\n }\n }\n)\n\nconst getThread: GoogleRoute = statefulRoute(\n gmail('GET', '/threads/{threadId}', false, [draftCase], ['threadId']),\n 'none',\n (request): string | NotEmulated => {\n const query = exactQuery(request, ['format'])\n\n if (isNotEmulated(query)) return query\n\n return recordedValue(query, 'format', 'full') ?? param(request, 'threadId')\n },\n (state, threadId) => {\n const messages = state.messages.filter(message => message.threadId === threadId)\n\n // Only draft threads created here are recorded (a seeded message's thread is not).\n const createdDrafts = messages.every(\n message => isDraftMessage(message) && mintedMessageIdPattern.test(message.id)\n )\n\n if (messages.length === 0 || !createdDrafts) {\n return notEmulated('a thread other than a draft thread created here is not emulated')\n }\n\n const rendered = messages.map(message => renderMessage(message, 'full'))\n\n if (rendered.some(item => item === undefined)) {\n return notEmulated('a thread with a message without a full rendering is not emulated')\n }\n\n return answer(() =>\n googleJson(200, {\n id: threadId,\n historyId: messages.at(-1)?.historyId ?? '',\n messages: rendered.flatMap(item => (item === undefined ? [] : [item]))\n })\n )\n }\n)\n\n// The practice send (upload endpoint).\n\nconst boundaryPattern = /^[A-Za-z0-9_]{1,70}$/\n\ntype SendInput = { readonly boundary: string; readonly subject: string; readonly text: string }\n\n/**\n * The boundary of `multipart/related; boundary=<b>`, or `undefined`. The parameter list is parsed\n * whole: each parameter is split on its first `=`, and the content type must carry exactly one\n * parameter, `boundary`, whose whole value is 1 to 70 of `[A-Za-z0-9_]` (unquoted). Any other,\n * repeated, empty, or malformed parameter is refused (the send records no other parameter).\n */\nconst multipartBoundary = (contentType: string | undefined): string | undefined => {\n if (mediaType(contentType) !== 'multipart/related') return undefined\n\n const params = (contentType ?? '')\n .split(';')\n .slice(1)\n .map(param => {\n const separator = param.indexOf('=')\n\n return separator === -1\n ? undefined\n : {\n name: param.slice(0, separator).trim().toLowerCase(),\n value: param.slice(separator + 1)\n }\n })\n\n const [only] = params\n\n return params.length === 1 &&\n only !== undefined &&\n only.name === 'boundary' &&\n boundaryPattern.test(only.value)\n ? only.value\n : undefined\n}\n\nconst sendMessage: GoogleRoute = statefulRoute(\n evidence(googleEmulatorGmailOrigin, 'POST', '/upload/gmail/v1/users/me/messages/send', true, [\n sendCase\n ]),\n 'bytes',\n (request): SendInput | NotEmulated => {\n const query = exactQuery(request, ['uploadType'])\n\n if (isNotEmulated(query)) return query\n\n const problem = recordedValue(query, 'uploadType', 'multipart')\n\n if (problem !== undefined) return problem\n\n const boundary = multipartBoundary(request.header('content-type'))\n\n if (boundary === undefined) {\n return notEmulated(\n 'a content-type other than multipart/related; boundary=<b> is not emulated'\n )\n }\n\n let text: string\n\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(request.bytes)\n } catch {\n return notEmulated('an upload body that is not 7-bit text is not emulated')\n }\n\n const subject = /\\r\\nSubject: ([^\\r\\n]*)\\r\\n/.exec(text)?.[1] ?? ''\n\n const runId = runIdOf(subject, 'send', sendSubjectRest)\n\n if (runId === undefined) {\n return notEmulated(\n 'a send other than the recorded run-scoped practice message is not emulated'\n )\n }\n\n return runId.length === recordedRunIdLength\n ? { boundary, subject, text }\n : notEmulated(sizedRunIdReason('send'))\n },\n (state, input, { env }) => {\n // The recorded sizeEstimate covers the To and From headers: only the recorded address.\n if (state.practiceAddress !== recordedPracticeAddress) {\n return notEmulated(\n `a send while the seeded practiceAddress is not the recorded ${recordedPracticeAddress} is not emulated (the recorded sizeEstimate covers the address)`\n )\n }\n\n // Only the recorded message, whose ONLY recipient is the seeded practice address.\n if (\n input.text !== uploadBody(input.boundary, practiceMime(state.practiceAddress, input.subject))\n ) {\n return notEmulated(\n 'a send other than the recorded 7-bit message to the seeded practice address only is not emulated'\n )\n }\n\n return () => {\n // Read the clock before any write: a failing clock, or one answering an instant outside the\n // Date range (non-finite or not), writes nothing.\n const instant = new Date(env.now())\n\n if (!Number.isFinite(instant.getTime())) {\n throw new Error('the clock answered an instant outside the Date range')\n }\n\n const date = instant.toUTCString().replace(/GMT$/, '+0000')\n const number = state.counters.nextSentNumber\n const id = mintedMessageId('e', number)\n const address = state.practiceAddress\n\n const headers = [\n { name: 'MIME-Version', value: '1.0' },\n { name: 'Date', value: date },\n { name: 'Message-ID', value: `<synthetic-send-${pad(number, 4)}@example.test>` },\n { name: 'Subject', value: input.subject },\n { name: 'From', value: address },\n { name: 'To', value: address },\n { name: 'Content-Type', value: 'text/plain; charset=us-ascii' }\n ].filter(header => !(env.drills.sentMessageWithoutTo && header.name === 'To'))\n\n // Recorded in the state only: nothing is delivered anywhere.\n const sent: GoogleEmulatorGmailMessage = {\n id,\n threadId: id,\n labelIds: ['SENT', 'INBOX', 'UNREAD'],\n snippet: 'Synthetic conformance message sent to the seeded practice address only.',\n sizeEstimate: 640,\n historyId: '900030',\n internalDate: recordedInternalDate,\n minimal: false,\n metadataPayload: {\n partId: '',\n mimeType: 'text/plain',\n filename: '',\n headers,\n body: { size: 88 }\n },\n fullPayload: null\n }\n\n state.counters = { ...state.counters, nextSentNumber: number + 1 }\n state.messages = [...state.messages, sent]\n\n return googleJson(200, { id, threadId: id, labelIds: ['SENT'] })\n }\n }\n)\n\n/** The Gmail routes, in manifest order. */\nexport const gmailRoutes: ReadonlyArray<GoogleRoute> = [\n listMessages,\n getMessage,\n getAttachment,\n modifyMessage,\n trashRoute('trash'),\n trashRoute('untrash'),\n createLabel,\n getLabel,\n deleteLabel,\n composeDraft,\n updateDraft,\n deleteDraft,\n getThread,\n sendMessage\n]\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4DA,MAAa,eAAe;AAE5B,MAAM,WAAW;AAEjB,MAAM,iBAAiB;AAEvB,MAAM,eAAe;AAErB,MAAM,YAAY;AAElB,MAAM,YAAY;AAElB,MAAM,YAAY;AAElB,MAAM,WAAW;AAEjB,MAAM,SACJ,QACA,MACA,OACA,SACA,SAAgC,CAAC,MAEjC,SACE,2BACA,QACA,GAAG,eAAe,QAClB,OACA,SACA,OAAO,YAAY,OAAO,KAAI,SAAQ,CAAC,MAAM,cAAc,CAAC,CAAC,CAC/D;;AAKF,MAAM,YAAY,QAChB,WACE,KACA,IAAI,OAAO,yBACP,EAAE,OAAO;CAAE,MAAM;CAAK,QAAQ;AAAY,EAAE,IAC5C,gBAAgB,KAAK,mCAAmC,YAAY,WAAW,CACrF;;AAGF,MAAM,yBAAyB;AAE/B,MAAM,iBAAiB;;AAGvB,MAAM,YAAY;AAElB,MAAM,mBAAmB;AAEzB,MAAM,mBAAmB;AAEzB,MAAM,kBAAkB;;AAGxB,MAAM,0BAA0B;;AAGhC,MAAM,oBAAoB,SACxB,KAAK,KAAK;;AAGZ,MAAM,gBAAgB,iBAAyB,YAC7C;CACE,OAAO;CACP,YAAY;CACZ;CACA;CACA;CACA;CACA;AACF,EAAE,KAAK,MAAM;;AAGf,MAAM,cAAc,UAAkB,SACpC;CACE,KAAK;CACL;CACA;CACA;CACA,KAAK;CACL;CACA;CACA;CACA,KAAK,SAAS;AAChB,EAAE,KAAK,MAAM;AAIf,MAAM,QAAQ,aAA4D;CACxE,IAAI,QAAQ;CACZ,UAAU,QAAQ;CAClB,UAAU,CAAC,GAAG,QAAQ,QAAQ;CAC9B,SAAS,QAAQ;CACjB,cAAc,QAAQ;CACtB,WAAW,QAAQ;CACnB,cAAc,QAAQ;AACxB;;AAGA,MAAM,iBACJ,SACA,WACkC;CAClC,QAAQ,QAAR;EACE,KAAK,WACH,OAAO,QAAQ,UAAU,KAAK,OAAO,IAAI,KAAA;EAC3C,KAAK,YACH,OAAO,QAAQ,oBAAoB,OAC/B,KAAA,IACA;GAAE,GAAG,KAAK,OAAO;GAAG,SAAS,QAAQ;EAAgB;EAC3D,KAAK,QACH,OAAO,QAAQ,gBAAgB,OAC3B,KAAA,IACA;GAAE,GAAG,KAAK,OAAO;GAAG,SAAS,QAAQ;EAAY;CACzD;AACF;;AAGA,MAAM,gBACJ,SACA,aAEA,WAAW,KAAK;CAAE,IAAI,QAAQ;CAAI,UAAU,QAAQ;CAAU,UAAU,CAAC,GAAG,QAAQ;AAAE,CAAC;AAEzF,MAAM,eAAe,OAA4B,OAC/C,MAAM,SAAS,MAAK,YAAW,QAAQ,OAAO,EAAE;AAElD,MAAM,oBAAoB,OAA4B,OACpD,MAAM,gBAAgB,MAAK,YAAW,QAAQ,OAAO,EAAE;AAEzD,MAAM,kBAAkB,YACtB,QAAQ,SAAS,SAAS,OAAO;AAEnC,MAAM,kBACJ,OACA,SAEA,MAAM,SAAS,KAAI,YAAY,QAAQ,OAAO,KAAK,KAAK,OAAO,OAAQ;AAezE,MAAM,eAA4B,cAChC,MAAM,OAAO,aAAa,OAAO,CAAC,QAAQ,CAAC,GAC3C,SACC,YAAqC;CACpC,MAAM,QAAQ,WAAW,SAAS,CAAC,YAAY,YAAY,GAAG,CAAC,WAAW,CAAC;CAE3E,IAAI,cAAc,KAAK,GAAG,OAAO;CAEjC,MAAM,UAAU,MAAM,eAAe;CAErC,IAAI,CAAC,eAAe,KAAK,OAAO,GAAG,OAAO,YAAY,+BAA+B;CAErF,MAAM,aAAa,SAAS,MAAM,eAAe,cAAc,GAAG,GAAG;CAErE,IAAI,cAAc,UAAU,GAAG,OAAO;CAEtC,MAAM,YAAY,MAAM;CAExB,OAAO,cAAc,KACjB,YAAY,oCAAoC,IAChD;EAAE;EAAS;EAAY;CAAU;AACvC,IACC,OAAO,OAAO,EAAE,UAAU;CAKzB,IAAI,EAHF,MAAM,gBAAgB,SAAS,MAAM,OAAO,KAC5C,MAAM,OAAO,MAAK,UAAS,MAAM,OAAO,MAAM,OAAO,IAE3C,OAAO,YAAY,yDAAyD;CAExF,MAAM,UAAoF,CACxF,GAAG,MAAM,UACT,GAAG,MAAM,eACX,EAAE,QAAO,YAAW,QAAQ,SAAS,SAAS,MAAM,OAAO,CAAC;CAG5D,IAAI,QAAQ,WAAW,GACrB,OAAO,YAAY,qEAAqE;CAG1F,IAAI,QAAQ,MAAK,YAAW,QAAQ,SAAS,MAAK,OAAM,OAAO,WAAW,OAAO,MAAM,CAAC,GACtF,OAAO,YAAY,0EAA0E;CAG/F,OAAO,SACL,KACA,SACA,cAAc,MAAM,QAAQ,QAAQ,MAAM,cAC1C,QAAQ,KAAI,aAAY;EAAE,IAAI,QAAQ;EAAI,UAAU,QAAQ;CAAS,EAAE,GACvE,MAAM,YACN,MAAM,WACN,IAAI,OAAO,mBACV,MAAM,kBAAkB;EACvB,MAAM,OAAyB,EAAE,UAAU,CAAC,GAAG,IAAI,EAAE;EAGrD,IAAI,kBAAkB,KAAA,GAAW,KAAK,gBAAgB;EAEtD,OAAO,WAAW,KAAK;GAAE,GAAG;GAAM,oBAAoB,QAAQ;EAAO,CAAC;CACxE,CACF;AACF,CACF;AAMA,MAAM,UAAiC;CAAC;CAAW;CAAY;AAAM;AAErE,MAAM,YAAY,UAChB,UAAU,KAAA,KAAa,QAAQ,SAAS,KAAK;AAE/C,MAAM,aAA0B,cAC9B,MACE,OACA,yBACA,OACA;CAAC;CAAgB;CAAc;CAAW;CAAW;CAAW;AAAQ,GACxE,CAAC,WAAW,CACd,GACA,SACC,YAAuC;CACtC,MAAM,QAAQ,WAAW,SAAS,CAAC,QAAQ,CAAC;CAE5C,IAAI,cAAc,KAAK,GAAG,OAAO;CAEjC,MAAM,SAAS,MAAM;CAErB,OAAO,SAAS,MAAM,IAClB;EAAE,IAAI,MAAM,SAAS,WAAW;EAAG;CAAO,IAC1C,YAAY,2CAA2C;AAC7D,IACC,OAAO,OAAO,EAAE,UAAU;CACzB,MAAM,UAAU,YAAY,OAAO,MAAM,EAAE;CAE3C,IAAI,YAAY,KAAA,GAAW;EACzB,MAAM,WAAW,cAAc,SAAS,MAAM,MAAM;EAEpD,OAAO,aAAa,KAAA,IAChB,YAAY,+CAA+C,MAAM,QAAQ,IACzE,aAAa,WAAW,KAAK,QAAQ,CAAC;CAC5C;CAEA,IAAI,iBAAiB,OAAO,MAAM,EAAE,GAClC,OAAO,YAAY,qEAAqE;CAI1F,OAAO,MAAM,WAAW,aAAa,uBAAuB,KAAK,MAAM,EAAE,IACrE,aAAa,SAAS,GAAG,CAAC,IAC1B,YAAY,qEAAqE;AACvF,CACF;AAIA,MAAM,gBAA6B,cACjC,MACE,OACA,oDACA,OACA,CAAC,cAAc,GACf,CAAC,aAAa,cAAc,CAC9B,GACA,SACC,YACC,aAAa,OAAO,KAAK;CACvB,WAAW,MAAM,SAAS,WAAW;CACrC,cAAc,MAAM,SAAS,cAAc;AAC7C,IACD,OAAO,OAAO,EAAE,UAAU;CACzB,MAAM,aAAa,MAAM,YAAY,MACnC,SAAQ,KAAK,cAAc,MAAM,aAAa,KAAK,iBAAiB,MAAM,YAC5E;CAEA,IAAI,eAAe,KAAA,KAAa,YAAY,OAAO,MAAM,SAAS,MAAM,KAAA,GACtE,OAAO,YAAY,uDAAuD;CAG5E,MAAM,OAAO,IAAI,OAAO,2BACpB,WAAW,KAAK,WAAW,KAAK,GAAG,EAAE,WAAW,KAAK,GAAG,IACxD,WAAW;CAEf,OAAO,aAAa,WAAW,KAAK;EAAE,MAAM,WAAW;EAAM;CAAK,CAAC,CAAC;AACtE,CACF;AAIA,MAAM,cAA2B,cAC/B,MAAM,QAAQ,WAAW,MAAM,CAAC,SAAS,CAAC,GAC1C,SACC,YAAkC;CACjC,MAAM,OAAO,aAAa,OAAO,KAAK,cAAc,QAAQ,MAAM,kBAAkB,CAAC,MAAM,CAAC;CAE5F,IAAI,cAAc,IAAI,GAAG,OAAO;CAEhC,OAAO,UAAU,SAAS,KAAK,IAAI,KAAK,UAAU,KAAK,MAAM,OAAO,IAChE,KAAK,OACL,YAAY,4EAA0E;AAC5F,IACC,OAAO,SAAS;CACf,IAAI,MAAM,OAAO,MAAK,UAAS,MAAM,SAAS,IAAI,GAChD,OAAO,YAAY,oDAAoD;CAIzE,IAAI,MAAM,SAAS,kBAAA,WACjB,OAAO,YAAY,+DAA+D;CAGpF,aAAa;EACX,MAAM,SAAS,MAAM,SAAS;EAE9B,MAAM,QAAQ;GACZ,IAAI,SAAS;GACb;GACA,uBAAuB;GACvB,qBAAqB;GACrB,MAAM;EACR;EAEA,MAAM,WAAW;GAAE,GAAG,MAAM;GAAU,iBAAiB,SAAS;EAAE;EAClE,MAAM,SAAS,CAAC,GAAG,MAAM,QAAQ,KAAK;EAEtC,OAAO,WAAW,KAAK,EAAE,GAAG,MAAM,CAAC;CACrC;AACF,CACF;AAEA,MAAM,WAAwB,cAC5B,MAAM,OAAO,qBAAqB,OAAO,CAAC,SAAS,GAAG,CAAC,SAAS,CAAC,GACjE,SACA,YAAW,aAAa,OAAO,KAAK,MAAM,SAAS,SAAS,IAC3D,OAAO,IAAI,EAAE,UAAU;CACtB,IAAI,MAAM,OAAO,MAAK,UAAS,MAAM,OAAO,EAAE,KAAK,MAAM,gBAAgB,SAAS,EAAE,GAClF,OAAO,YAAY,sDAAsD;CAG3E,OAAO,qBAAqB,KAAK,EAAE,IAC/B,aAAa,SAAS,GAAG,CAAC,IAC1B,YAAY,mDAAmD;AACrE,CACF;AAEA,MAAM,cAA2B,cAC/B,MAAM,UAAU,qBAAqB,MAAM,CAAC,SAAS,GAAG,CAAC,SAAS,CAAC,GACnE,SACA,YAAW,aAAa,OAAO,KAAK,MAAM,SAAS,SAAS,IAC3D,OAAO,IAAI,EAAE,UAAU;CACtB,IAAI,CAAC,MAAM,OAAO,MAAK,UAAS,MAAM,OAAO,EAAE,GAC7C,OAAO,YAAY,gDAAgD;CAGrE,aAAa;EACX,MAAM,WAAmE,YACvE,QAAQ,SAAS,SAAS,EAAE,IACxB;GAAE,GAAG;GAAS,UAAU,QAAQ,SAAS,QAAO,UAAS,UAAU,EAAE;EAAE,IACvE;EAEN,MAAM,SAAS,MAAM,OAAO,QAAO,UAAS,MAAM,OAAO,EAAE;EAG3D,IAAI,CAAC,IAAI,OAAO,4BAA4B;GAC1C,MAAM,WAAW,MAAM,SAAS,IAAI,OAAO;GAC3C,MAAM,kBAAkB,MAAM,gBAAgB,IAAI,OAAO;EAC3D;EAEA,OAAO,gBAAgB;CACzB;AACF,CACF;AAMA,MAAM,gBAA6B,cACjC,MAAM,QAAQ,gCAAgC,MAAM,CAAC,SAAS,GAAG,CAAC,WAAW,CAAC,GAC9E,SACC,YAAuC;CACtC,MAAM,OACJ,aAAa,OAAO,KAAK,cAAc,QAAQ,MAAM,mBAAmB,CAAC,aAAa,CAAC;CAEzF,IAAI,cAAc,IAAI,GAAG,OAAO;CAEhC,MAAM,QAAQ,KAAK;CAEnB,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,CAAC,UAAU,SAAS,MAAM,EAAE,GAC7E,OAAO,YAAY,sDAAsD;CAG3E,OAAO;EAAE,WAAW,MAAM,SAAS,WAAW;EAAG,SAAS,MAAM;CAAG;AACrE,IACC,OAAO,UAAU;CAChB,MAAM,UAAU,YAAY,OAAO,MAAM,SAAS;CAElD,IAAI,YAAY,KAAA,KAAa,eAAe,OAAO,GACjD,OAAO,YAAY,0EAA0E;CAG/F,IAAI,CAAC,MAAM,OAAO,MAAK,UAAS,MAAM,OAAO,MAAM,OAAO,GACxD,OAAO,YAAY,4DAA4D;CAGjF,IAAI,QAAQ,SAAS,SAAS,MAAM,OAAO,GACzC,OAAO,YAAY,4DAA4D;CAGjF,aAAa;EACX,MAAM,OAAO;GAAE,GAAG;GAAS,UAAU,CAAC,GAAG,QAAQ,UAAU,MAAM,OAAO;EAAE;EAE1E,MAAM,WAAW,eAAe,OAAO,IAAI;EAE3C,OAAO,aAAa,MAAM,KAAK,QAAQ;CACzC;AACF,CACF;AAEA,MAAM,cAAc,WAClB,cACE,MAAM,QAAQ,yBAAyB,UAAU,MAAM,CAAC,SAAS,GAAG,CAAC,WAAW,CAAC,GACjF,SACA,YAAW,aAAa,OAAO,KAAK,MAAM,SAAS,WAAW,IAC7D,OAAO,IAAI,EAAE,UAAU;CACtB,MAAM,UAAU,YAAY,OAAO,EAAE;CAErC,IAAI,YAAY,KAAA,KAAa,eAAe,OAAO,GACjD,OAAO,YAAY,GAAG,OAAO,sDAAsD;CAGrF,MAAM,UAAU,QAAQ,SAAS,SAAS,OAAO;CAEjD,IAAI,WAAW,UAAU,UAAU,CAAC,SAClC,OAAO,YACL,WAAW,UACP,wDACA,mDACN;CAGF,aAAa;EACX,MAAM,WACJ,WAAW,UACP,CAAC,GAAG,QAAQ,UAAU,OAAO,IAC7B,QAAQ,SAAS,QAAO,UAAS,UAAU,OAAO;EAExD,MAAM,OAAO;GAAE,GAAG;GAAS;EAAS;EAEpC,MAAM,WAAW,eAAe,OAAO,IAAI;EAE3C,OAAO,aACL,MACA,WAAW,WAAW,IAAI,OAAO,wBAC7B,SAAS,QAAO,UAAS,UAAU,OAAO,IAC1C,QACN;CACF;AACF,CACF;AAIF,MAAM,mBAAmB;;AAGzB,MAAM,uBAAuB,QAAoC;CAC/D,IAAI,CAAC,iBAAiB,KAAK,GAAG,KAAK,IAAI,SAAS,MAAM,GAAG,OAAO,KAAA;CAEhE,IAAI;EACF,MAAM,SAAS,KAAK,IAAI,WAAW,KAAK,GAAG,EAAE,WAAW,KAAK,GAAG,CAAC;EACjE,MAAM,QAAQ,WAAW,KAAK,SAAQ,SAAQ,KAAK,WAAW,CAAC,CAAC;EAChE,MAAM,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;EAGnE,OAAO,gBAAgB,IAAI,MAAM,MAAM,OAAO,KAAA;CAChD,QAAQ;EACN;CACF;AACF;AAEA,MAAM,mBACJ;;AAKF,MAAM,kBAAkB;AAExB,MAAM,qBAAqB;AAE3B,MAAM,oBAAoB,SACxB,WAAW,KAAK;;;;;AAMlB,MAAM,uBAAuB,SAAsD;CACjF;CACA;CACA,iBAAiB,IAAI;CACrB,iBAAiB,OAAO;AAC1B;;AASA,MAAM,gBAAgB,SAAkC,SAAyC;CAG/F,MAAM,MACJ,UAAU,YAAY,SAAS,KAAK,KAAK,CAAC,MAAM,QAAQ,OAAO,IAAI,QAAQ,MAAM,KAAA;CAEnF,MAAM,UAAU,UAAU,SAAS,GAAG,IAAI,oBAAoB,GAAG,IAAI,KAAA;CAErE,MAAM,SAAS,cAAc,SAAS,WAAW,CAAC,KAAK,CAAC;CAExD,IAAI,cAAc,MAAM,GAAG,OAAO;EAAE;EAAS,SAAS;CAAO;CAE7D,IAAI,YAAY,KAAA,GAAW,OAAO;EAAE;EAAS,SAAS,YAAY,kBAAkB;CAAE;CAEtF,MAAM,QAAQ,iBAAiB,KAAK,OAAO;CAC3C,MAAM,UAAU,QAAQ,MAAM;CAC9B,MAAM,OAAO,QAAQ,MAAM;CAE3B,MAAM,QACJ,SAAS,YACL,QAAQ,SAAS,SAAS,gBAAgB,IAC1C,QAAQ,SAAS,iBAAiB,gBAAgB;CAIxD,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAFxB,SAAS,YAAY,YAAY,mBAGpD,OAAO;EAAE;EAAS,SAAS,YAAY,iBAAiB,IAAI,CAAC;CAAE;CAGjE,OAAO;EACL;EACA,SACE,MAAM,WAAA,KACF;GAAE;GAAS;EAAK,IAChB,YAAY,iBAAiB,OAAO,CAAC;CAC7C;AACF;;;;;;;;;;;;;AAcA,MAAM,iBACH,UACA,SAAwC;CACvC,MAAM,SAAS,cAAc,IAAI;CACjC,MAAM,UAAU,aAAa,MAAM,IAAI,OAAO,UAAU,KAAA;CAExD,IAAI,CAAC,aAAa,OAAO,KAAK,EAAE,SAAS,UAAU,OAAO,CAAC;CAE3D,MAAM,EAAE,SAAS,YAAY,aAAa,SAAS,IAAI;CAEvD,IAAI,cAAc,OAAO,GACvB,MAAM,IAAI,mBAAmB;EAC3B,QAAQ,QAAQ;EAChB,SAAS,YAAY,KAAA,IAAY,CAAC,IAAI,CAAC,OAAO;CAChD,CAAC;CAGH,OAAO,YAAY,KAAA,IAAY,CAAC,IAAI,CAAC,OAAO;AAC9C;AAEF,MAAM,eAAe,SAAiB,UAAgD;CACpF,QAAQ;CACR,UAAU;CACV,UAAU;CACV,SAAS,CACP;EAAE,MAAM;EAAW,OAAO;CAAQ,GAClC;EAAE,MAAM;EAAgB,OAAO;CAA4B,CAC7D;CACA;AACF;;AAGA,MAAM,gBACJ,IACA,UACA,SACA,kBACgC;CAChC;CACA;CACA,UAAU,CAAC,OAAO;CAClB,SAAS,QAAQ;CACjB,cAAc;CACd,WAAW;CACX,cAAc;CACd,SAAS;CACT,iBAAiB,eAAe,YAAY,QAAQ,SAAS,EAAE,MAAM,GAAG,CAAC,IAAI;CAC7E,aAAa,YAAY,QAAQ,SAAS;EACxC,MAAM,IAAI,YAAY,EAAE,OAAO,QAAQ,IAAI,EAAE;EAC7C,MAAM,gBAAgB,QAAQ,IAAI;CACpC,CAAC;AACH;AAEA,MAAM,eAAe,SAAiB,YACpC,WAAW,KAAK;CACd,IAAI;CACJ,SAAS;EAAE,IAAI,QAAQ;EAAI,UAAU,QAAQ;EAAU,UAAU,CAAC,GAAG,QAAQ,QAAQ;CAAE;AACzF,CAAC;AAEH,MAAM,eAA4B,cAChC;CACE,GAAG,MAAM,QAAQ,WAAW,MAAM,CAAC,SAAS,CAAC;CAC7C,cAAc,cAAc,SAAS;CACrC,oBAAoB,oBAAoB,SAAS;AACnD,GACA,SACC,YAAwC;CACvC,MAAM,OAAO,aAAa,OAAO,KAAK,cAAc,QAAQ,MAAM,kBAAkB,CAAC,SAAS,CAAC;CAE/F,OAAO,cAAc,IAAI,IAAI,OAAO,aAAa,KAAK,SAAS,SAAS,EAAE;AAC5E,IACC,OAAO,kBAAkB;CACxB,MAAM,cAAc,MAAM,SAAS;CACnC,MAAM,gBAAgB,MAAM,SAAS;CACrC,MAAM,KAAK,gBAAgB,KAAK,aAAa;CAC7C,MAAM,UAAU,aAAa,IAAI,IAAI,SAAS,IAAI;CAClD,MAAM,UAAU,cAAc,WAAW;CAEzC,MAAM,WAAW;EACf,GAAG,MAAM;EACT,iBAAiB,cAAc;EAC/B,wBAAwB,gBAAgB;CAC1C;CACA,MAAM,WAAW,CAAC,GAAG,MAAM,UAAU,OAAO;CAC5C,MAAM,SAAS,CAAC,GAAG,MAAM,QAAQ;EAAE,IAAI;EAAS,WAAW;CAAG,CAAC;CAE/D,OAAO,YAAY,SAAS,OAAO;AACrC,CACF;AAIA,MAAM,cAA2B,cAC/B;CACE,GAAG,MAAM,OAAO,qBAAqB,MAAM,CAAC,SAAS,GAAG,CAAC,SAAS,CAAC;CACnE,cAAc,cAAc,QAAQ;CACpC,oBAAoB,oBAAoB,QAAQ;AAClD,GACA,SACC,YAAuC;CACtC,MAAM,UAAU,MAAM,SAAS,SAAS;CAExC,MAAM,OACJ,aAAa,OAAO,KAAK,cAAc,QAAQ,MAAM,kBAAkB,CAAC,MAAM,SAAS,CAAC;CAE1F,IAAI,cAAc,IAAI,GAAG,OAAO;CAEhC,IAAI,KAAK,OAAO,SAAS,OAAO,YAAY,wCAAwC;CAEpF,MAAM,UAAU,aAAa,KAAK,SAAS,QAAQ,EAAE;CAErD,OAAO,cAAc,OAAO,IAAI,UAAU;EAAE;EAAS;CAAQ;AAC/D,IACC,OAAO,OAAO,EAAE,UAAU;CACzB,MAAM,QAAQ,MAAM,OAAO,MAAK,cAAa,UAAU,OAAO,MAAM,OAAO;CAC3E,MAAM,WAAW,UAAU,KAAA,IAAY,KAAA,IAAY,YAAY,OAAO,MAAM,SAAS;CAErF,IAAI,UAAU,KAAA,KAAa,aAAa,KAAA,GACtC,OAAO,YAAY,0DAA0D;CAG/E,aAAa;EACX,MAAM,SAAS,MAAM,SAAS;EAC9B,MAAM,KAAK,gBAAgB,KAAK,MAAM;EAEtC,MAAM,UAAsC,IAAI,OAAO,0BACnD;GAAE,GAAG;GAAU;GAAI,iBAAiB;EAAK,IACzC,aAAa,IAAI,SAAS,UAAU,MAAM,SAAS,KAAK;EAE5D,MAAM,WAAW;GAAE,GAAG,MAAM;GAAU,wBAAwB,SAAS;EAAE;EACzE,MAAM,WAAW,CACf,GAAG,MAAM,SAAS,QAAO,cAAa,UAAU,OAAO,SAAS,EAAE,GAClE,OACF;EACA,MAAM,SAAS,MAAM,OAAO,KAAI,cAC9B,UAAU,OAAO,MAAM,KAAK;GAAE,GAAG;GAAW,WAAW;EAAG,IAAI,SAChE;EAEA,OAAO,YAAY,MAAM,IAAI,OAAO;CACtC;AACF,CACF;AAEA,MAAM,cAA2B,cAC/B,MAAM,UAAU,qBAAqB,MAAM,CAAC,SAAS,GAAG,CAAC,SAAS,CAAC,GACnE,SACA,YAAW,aAAa,OAAO,KAAK,MAAM,SAAS,SAAS,IAC3D,OAAO,IAAI,EAAE,UAAU;CACtB,MAAM,QAAQ,MAAM,OAAO,MAAK,cAAa,UAAU,OAAO,EAAE;CAEhE,IAAI,UAAU,KAAA,GAEZ,OAAO,eAAe,KAAK,EAAE,IACzB,aAAa,SAAS,GAAG,CAAC,IAC1B,YAAY,yDAAyD;CAG3E,aAAa;EACX,MAAM,SAAS,MAAM,OAAO,QAAO,cAAa,UAAU,OAAO,EAAE;EACnE,MAAM,WAAW,MAAM,SAAS,QAAO,YAAW,QAAQ,OAAO,MAAM,SAAS;EAEhF,OAAO,gBAAgB;CACzB;AACF,CACF;AAEA,MAAM,YAAyB,cAC7B,MAAM,OAAO,uBAAuB,OAAO,CAAC,SAAS,GAAG,CAAC,UAAU,CAAC,GACpE,SACC,YAAkC;CACjC,MAAM,QAAQ,WAAW,SAAS,CAAC,QAAQ,CAAC;CAE5C,IAAI,cAAc,KAAK,GAAG,OAAO;CAEjC,OAAO,cAAc,OAAO,UAAU,MAAM,KAAK,MAAM,SAAS,UAAU;AAC5E,IACC,OAAO,aAAa;CACnB,MAAM,WAAW,MAAM,SAAS,QAAO,YAAW,QAAQ,aAAa,QAAQ;CAG/E,MAAM,gBAAgB,SAAS,OAC7B,YAAW,eAAe,OAAO,KAAK,uBAAuB,KAAK,QAAQ,EAAE,CAC9E;CAEA,IAAI,SAAS,WAAW,KAAK,CAAC,eAC5B,OAAO,YAAY,iEAAiE;CAGtF,MAAM,WAAW,SAAS,KAAI,YAAW,cAAc,SAAS,MAAM,CAAC;CAEvE,IAAI,SAAS,MAAK,SAAQ,SAAS,KAAA,CAAS,GAC1C,OAAO,YAAY,kEAAkE;CAGvF,OAAO,aACL,WAAW,KAAK;EACd,IAAI;EACJ,WAAW,SAAS,GAAG,EAAE,GAAG,aAAa;EACzC,UAAU,SAAS,SAAQ,SAAS,SAAS,KAAA,IAAY,CAAC,IAAI,CAAC,IAAI,CAAE;CACvE,CAAC,CACH;AACF,CACF;AAIA,MAAM,kBAAkB;;;;;;;AAUxB,MAAM,qBAAqB,gBAAwD;CACjF,IAAI,UAAU,WAAW,MAAM,qBAAqB,OAAO,KAAA;CAE3D,MAAM,UAAU,eAAe,IAC5B,MAAM,GAAG,EACT,MAAM,CAAC,EACP,KAAI,UAAS;EACZ,MAAM,YAAY,MAAM,QAAQ,GAAG;EAEnC,OAAO,cAAc,KACjB,KAAA,IACA;GACE,MAAM,MAAM,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,YAAY;GACnD,OAAO,MAAM,MAAM,YAAY,CAAC;EAClC;CACN,CAAC;CAEH,MAAM,CAAC,QAAQ;CAEf,OAAO,OAAO,WAAW,KACvB,SAAS,KAAA,KACT,KAAK,SAAS,cACd,gBAAgB,KAAK,KAAK,KAAK,IAC7B,KAAK,QACL,KAAA;AACN;AAEA,MAAM,cAA2B,cAC/B,SAAS,2BAA2B,QAAQ,2CAA2C,MAAM,CAC3F,QACF,CAAC,GACD,UACC,YAAqC;CACpC,MAAM,QAAQ,WAAW,SAAS,CAAC,YAAY,CAAC;CAEhD,IAAI,cAAc,KAAK,GAAG,OAAO;CAEjC,MAAM,UAAU,cAAc,OAAO,cAAc,WAAW;CAE9D,IAAI,YAAY,KAAA,GAAW,OAAO;CAElC,MAAM,WAAW,kBAAkB,QAAQ,OAAO,cAAc,CAAC;CAEjE,IAAI,aAAa,KAAA,GACf,OAAO,YACL,2EACF;CAGF,IAAI;CAEJ,IAAI;EACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,QAAQ,KAAK;CACvE,QAAQ;EACN,OAAO,YAAY,uDAAuD;CAC5E;CAEA,MAAM,UAAU,8BAA8B,KAAK,IAAI,IAAI,MAAM;CAEjE,MAAM,QAAQ,QAAQ,SAAS,QAAQ,eAAe;CAEtD,IAAI,UAAU,KAAA,GACZ,OAAO,YACL,4EACF;CAGF,OAAO,MAAM,WAAA,KACT;EAAE;EAAU;EAAS;CAAK,IAC1B,YAAY,iBAAiB,MAAM,CAAC;AAC1C,IACC,OAAO,OAAO,EAAE,UAAU;CAEzB,IAAI,MAAM,oBAAoB,yBAC5B,OAAO,YACL,+DAA+D,wBAAwB,gEACzF;CAIF,IACE,MAAM,SAAS,WAAW,MAAM,UAAU,aAAa,MAAM,iBAAiB,MAAM,OAAO,CAAC,GAE5F,OAAO,YACL,kGACF;CAGF,aAAa;EAGX,MAAM,UAAU,IAAI,KAAK,IAAI,IAAI,CAAC;EAElC,IAAI,CAAC,OAAO,SAAS,QAAQ,QAAQ,CAAC,GACpC,MAAM,IAAI,MAAM,sDAAsD;EAGxE,MAAM,OAAO,QAAQ,YAAY,EAAE,QAAQ,QAAQ,OAAO;EAC1D,MAAM,SAAS,MAAM,SAAS;EAC9B,MAAM,KAAK,gBAAgB,KAAK,MAAM;EACtC,MAAM,UAAU,MAAM;EAatB,MAAM,OAAmC;GACvC;GACA,UAAU;GACV,UAAU;IAAC;IAAQ;IAAS;GAAQ;GACpC,SAAS;GACT,cAAc;GACd,WAAW;GACX,cAAc;GACd,SAAS;GACT,iBAAiB;IACf,QAAQ;IACR,UAAU;IACV,UAAU;IACV,SAxBY;KACd;MAAE,MAAM;MAAgB,OAAO;KAAM;KACrC;MAAE,MAAM;MAAQ,OAAO;KAAK;KAC5B;MAAE,MAAM;MAAc,OAAO,mBAAmB,IAAI,QAAQ,CAAC,EAAE;KAAgB;KAC/E;MAAE,MAAM;MAAW,OAAO,MAAM;KAAQ;KACxC;MAAE,MAAM;MAAQ,OAAO;KAAQ;KAC/B;MAAE,MAAM;MAAM,OAAO;KAAQ;KAC7B;MAAE,MAAM;MAAgB,OAAO;KAA+B;IAChE,EAAE,QAAO,WAAU,EAAE,IAAI,OAAO,wBAAwB,OAAO,SAAS,KAgB9D;IACN,MAAM,EAAE,MAAM,GAAG;GACnB;GACA,aAAa;EACf;EAEA,MAAM,WAAW;GAAE,GAAG,MAAM;GAAU,gBAAgB,SAAS;EAAE;EACjE,MAAM,WAAW,CAAC,GAAG,MAAM,UAAU,IAAI;EAEzC,OAAO,WAAW,KAAK;GAAE;GAAI,UAAU;GAAI,UAAU,CAAC,MAAM;EAAE,CAAC;CACjE;AACF,CACF;;AAGA,MAAa,cAA0C;CACrD;CACA;CACA;CACA;CACA,WAAW,OAAO;CAClB,WAAW,SAAS;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Commit, EmulatedRequest, NotEmulated, StatefulRoute } from "../stateful-emulator.mjs";
|
|
2
|
+
import { GoogleEmulatorState } from "./state.mjs";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
|
|
5
|
+
//#region src/google/shared.d.ts
|
|
6
|
+
/** The origin of every Gmail route (the API and the upload endpoint), as the fixtures record. */
|
|
7
|
+
declare const googleEmulatorGmailOrigin = "https://gmail.googleapis.com";
|
|
8
|
+
/** The origin of every Calendar and Drive route, as the fixtures record. */
|
|
9
|
+
declare const googleEmulatorApisOrigin = "https://www.googleapis.com";
|
|
10
|
+
/** Drill knobs (tests only): each makes the emulator disagree with exactly one conformance case. */
|
|
11
|
+
type GoogleEmulatorDrills = {
|
|
12
|
+
/** A later `gmail.list` page starts one message early, repeating the previous page's last. */readonly gmailPageRepeats?: boolean; /** Attachment `data` is answered in the standard base64 alphabet instead of base64url. */
|
|
13
|
+
readonly attachmentStandardBase64?: boolean; /** Gmail 404 envelopes carry no `error.message`. */
|
|
14
|
+
readonly notFoundWithoutMessage?: boolean; /** Deleting a label leaves it on the messages that carry it. */
|
|
15
|
+
readonly labelDeleteKeepsOnMessages?: boolean; /** A draft update keeps the earlier subject and body (it ignores the new message). */
|
|
16
|
+
readonly draftUpdateKeepsContent?: boolean; /** The trash answer omits `TRASH` from `labelIds` (the state still carries it). */
|
|
17
|
+
readonly trashAnswerOmitsTrash?: boolean; /** The sent message reads back without its `To` header. */
|
|
18
|
+
readonly sentMessageWithoutTo?: boolean; /** A later `calendar.list_events` page starts one event early. */
|
|
19
|
+
readonly calendarPageRepeats?: boolean; /** An event PATCH answers (and keeps) the earlier summary. */
|
|
20
|
+
readonly eventPatchKeepsSummary?: boolean; /** Deleting a cancelled event answers 409 instead of the recorded 410. */
|
|
21
|
+
readonly repeatedEventDeleteConflict?: boolean; /** A later `drive.list_files` page starts one file early. */
|
|
22
|
+
readonly drivePageRepeats?: boolean; /** `drive.get_file` answers no `parents`. */
|
|
23
|
+
readonly getFileWithoutParents?: boolean; /** Folder listings include trashed children. */
|
|
24
|
+
readonly listIncludesTrashed?: boolean;
|
|
25
|
+
};
|
|
26
|
+
declare const googleEmulatorDrillKnobs: ReadonlyArray<keyof GoogleEmulatorDrills>;
|
|
27
|
+
/** A page token this emulator issued: where its list continues, and the list it was issued for. */
|
|
28
|
+
type GoogleIssuedPageToken = {
|
|
29
|
+
/** The list it continues (its key names the list and its page size). */readonly listKey: string;
|
|
30
|
+
readonly offset: number; /** The whole list as rendered when the token was issued; any change is not emulated. */
|
|
31
|
+
readonly fingerprint: string;
|
|
32
|
+
};
|
|
33
|
+
type GoogleApiEnv = {
|
|
34
|
+
/** Clock in epoch milliseconds (created and updated timestamps, the sent `Date` header). */readonly now: () => number;
|
|
35
|
+
readonly drills: Readonly<Record<keyof GoogleEmulatorDrills, boolean>>;
|
|
36
|
+
/**
|
|
37
|
+
* Issued page tokens by value (runtime-only; cleared by reset and seed). A value is bound to
|
|
38
|
+
* exactly one list, position, and rendering, and never rebound.
|
|
39
|
+
*/
|
|
40
|
+
readonly pageTokens: Map<string, GoogleIssuedPageToken>;
|
|
41
|
+
/**
|
|
42
|
+
* The token generation: advanced by every reset and seed (never rewound). A page token is the
|
|
43
|
+
* fixture's value (`synthetic-<api>-page-<n>`) only in the generation that first issued that
|
|
44
|
+
* value; a later generation issues `<value>.g<generation>`, so no token value ever crosses a
|
|
45
|
+
* reset or seed.
|
|
46
|
+
*/
|
|
47
|
+
readonly generation: {
|
|
48
|
+
current: number;
|
|
49
|
+
}; /** The generation that first issued each token value (never cleared). */
|
|
50
|
+
readonly firstIssued: Map<string, number>;
|
|
51
|
+
};
|
|
52
|
+
type GoogleRoute = StatefulRoute<GoogleEmulatorState, GoogleApiEnv>;
|
|
53
|
+
/** Route evidence of an unverified connector route on `origin`, citing `caseIds`. */
|
|
54
|
+
declare const evidence: (origin: string, method: string, path: string, write: boolean, caseIds: ReadonlyArray<string>, params?: Readonly<Record<string, RegExp>>) => {
|
|
55
|
+
method: string;
|
|
56
|
+
path: string;
|
|
57
|
+
kind: "connector";
|
|
58
|
+
write: boolean;
|
|
59
|
+
caseIds: readonly string[];
|
|
60
|
+
evidence: "unverified";
|
|
61
|
+
origin: string;
|
|
62
|
+
params: Readonly<Record<string, RegExp>>;
|
|
63
|
+
};
|
|
64
|
+
/** A path parameter, decoded once by the wrapper (empty when absent). */
|
|
65
|
+
declare const param: (request: EmulatedRequest, name: string) => string;
|
|
66
|
+
/** The `internalDate` every recorded Gmail message carries. */
|
|
67
|
+
declare const recordedInternalDate = "1790000000000";
|
|
68
|
+
/** The raw path segment of a Gmail message, thread, label, attachment, or draft id. */
|
|
69
|
+
declare const gmailIdSegment: RegExp;
|
|
70
|
+
declare const googleJson: (status: number, body: Schema.Json) => Response;
|
|
71
|
+
/** The bodiless 204 every recorded delete answers. */
|
|
72
|
+
declare const googleNoContent: () => Response;
|
|
73
|
+
/** The Google JSON error envelope, as the fixtures write it. */
|
|
74
|
+
declare const googleErrorBody: (code: number, message: string, reason: string, status: string) => Schema.JsonObject;
|
|
75
|
+
/** A read-only commit. */
|
|
76
|
+
declare const answer: (response: () => Response) => Commit;
|
|
77
|
+
/** A query value that must equal the recorded one. */
|
|
78
|
+
declare const recordedValue: (query: Readonly<Record<string, string>>, key: string, value: string) => NotEmulated | undefined;
|
|
79
|
+
/** A decimal page size from `minimum` to `maximum`, or not emulated. */
|
|
80
|
+
declare const pageSize: (raw: string | undefined, label: string, minimum: number, maximum: number) => number | NotEmulated;
|
|
81
|
+
/** A request without query parameters, or not emulated. */
|
|
82
|
+
declare const withoutQuery: (request: EmulatedRequest) => NotEmulated | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* The well-formed run id of `yolk-conformance <runId> <kind>` (or `... <kind>: <rest>`), or
|
|
85
|
+
* `undefined` when `text` has another form.
|
|
86
|
+
*/
|
|
87
|
+
declare const runIdOf: (text: string, kind: string, rest?: string) => string | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* True when `text` is `yolk-conformance <runId> <kind>` (or `... <kind>: <rest>`) for a
|
|
90
|
+
* well-formed run id: the only latitude in the run-scoped names, subjects, and summaries.
|
|
91
|
+
*/
|
|
92
|
+
declare const isRunText: (text: string, kind: string, rest?: string) => boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The length of the fixtures' run id (`run-synthetic`). The draft and sent messages the Gmail
|
|
95
|
+
* fixtures record carry a `sizeEstimate` that covers their subject, so a draft or send subject
|
|
96
|
+
* must carry a run id of exactly this length for that recorded value to stay true.
|
|
97
|
+
*/
|
|
98
|
+
declare const recordedRunIdLength = 13;
|
|
99
|
+
/**
|
|
100
|
+
* The commit answering one page of a list: `render(page, nextPageToken)` for the page's rendered
|
|
101
|
+
* items, with a next token (registered for this list) while items remain. `repeats` (a drill)
|
|
102
|
+
* starts a later page one item early.
|
|
103
|
+
*/
|
|
104
|
+
declare const listPage: (env: GoogleApiEnv, prefix: string, listKey: string, all: ReadonlyArray<Schema.Json>, size: number, token: string | undefined, repeats: boolean, render: (page: ReadonlyArray<Schema.Json>, nextPageToken: string | undefined) => Response) => Commit | NotEmulated;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { GoogleApiEnv, GoogleEmulatorDrills, GoogleIssuedPageToken, GoogleRoute, answer, evidence, gmailIdSegment, googleEmulatorApisOrigin, googleEmulatorDrillKnobs, googleEmulatorGmailOrigin, googleErrorBody, googleJson, googleNoContent, isRunText, listPage, pageSize, param, recordedInternalDate, recordedRunIdLength, recordedValue, runIdOf, withoutQuery };
|
|
107
|
+
//# sourceMappingURL=shared.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.mts","names":[],"sources":["../../src/google/shared.ts"],"mappings":";;;;;AAmBsC;AAAA,cAAzB,yBAAA;;cAGA,wBAAA;;KAGD,oBAAA;EAAA,uGAED,gBAAA;WAEA,wBAAA,YAFA;EAAA,SAIA,sBAAA;EAAA,SAEA,0BAAA,YAEA;EAAA,SAAA,uBAAA,YAIA;EAAA,SAFA,qBAAA,YAMA;EAAA,SAJA,oBAAA,YAQA;EAAA,SANA,mBAAA,YAUA;EAAA,SARA,sBAAA,YAQmB;EAAA,SANnB,2BAAA,YAuBV;EAAA,SArBU,gBAAA,YAO4B;EAAA,SAL5B,qBAAA,YAsBC;EAAA,SApBD,mBAAA;AAAA;AAAA,cAGE,wBAAA,EAA0B,aAAa,OAAO,oBAAA;;KAiB/C,qBAAA;EAKD,iFAHA,OAAA;EAAA,SACA,MAAA,UAKC;EAAA,SAHD,WAAA;AAAA;AAAA,KAGC,YAAA;EAGgB,qGADjB,GAAA;EAAA,SACA,MAAA,EAAQ,QAAA,CAAS,MAAA,OAAa,oBAAA;EAKlB;;;;EAAA,SAAZ,UAAA,EAAY,GAAA,SAAY,qBAAA;EALxB;;;;;;EAAA,SAYA,UAAA;IAAc,OAAA;EAAA,GAEd;EAAA,SAAA,WAAA,EAAa,GAAA;AAAA;AAAA,KAGZ,WAAA,GAAc,aAAA,CAAc,mBAAA,EAAqB,YAAA;AAA7D;AAAA,cAGa,QAAA,GACX,MAAA,UACA,MAAA,UACA,IAAA,UACA,KAAA,WACA,OAAA,EAAS,aAAA,UACT,MAAA,GAAQ,QAAA,CAAS,MAAA,SAAe,MAAA;;;;;;;;;;;cAarB,KAAA,GAAS,OAAA,EAAS,eAAe,EAAE,IAAA;AAnBhD;AAAA,cAsBa,oBAAA;;cAGA,cAAA,EAAc,MAA2B;AAAA,cAMzC,UAAA,GAAc,MAAA,UAAgB,IAAA,EAAM,MAAA,CAAO,IAAA,KAAO,QACiC;;cAGnF,eAAA,QAAsB,QAA+C;;cAGrE,eAAA,GACX,IAAA,UACA,OAAA,UACA,MAAA,UACA,MAAA,aACC,MAAA,CAAO,UAER;;cAGW,MAAA,GACV,QAAA,QAAgB,QAAA,KAAW,MAEhB;;cAKD,aAAA,GACX,KAAA,EAAO,QAAA,CAAS,MAAA,mBAChB,GAAA,UACA,KAAA,aACC,WAAA;;cAIU,QAAA,GACX,GAAA,sBACA,KAAA,UACA,OAAA,UACA,OAAA,sBACU,WAMT;;cAGU,YAAA,GAAgB,OAAA,EAAS,eAAA,KAAkB,WAAW;;;;;cActD,OAAA,GAAW,IAAA,UAAc,IAAA,UAAc,IAAA;;;;;cAevC,SAAA,GAAa,IAAA,UAAc,IAAA,UAAc,IAAA;;;;;;cAQzC,mBAAA;;;AAhGb;;;cA6Ka,QAAA,GACX,GAAA,EAAK,YAAA,EACL,MAAA,UACA,OAAA,UACA,GAAA,EAAK,aAAA,CAAc,MAAA,CAAO,IAAA,GAC1B,IAAA,UACA,KAAA,sBACA,OAAA,WACA,MAAA,GAAS,IAAA,EAAM,aAAA,CAAc,MAAA,CAAO,IAAA,GAAO,aAAA,yBAAsC,QAAA,KAChF,MAAA,GAAS,WAAA"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { integerIn, isNotEmulated, notEmulated } from "../stateful-emulator.mjs";
|
|
2
|
+
//#region src/google/shared.ts
|
|
3
|
+
/** The origin of every Gmail route (the API and the upload endpoint), as the fixtures record. */
|
|
4
|
+
const googleEmulatorGmailOrigin = "https://gmail.googleapis.com";
|
|
5
|
+
/** The origin of every Calendar and Drive route, as the fixtures record. */
|
|
6
|
+
const googleEmulatorApisOrigin = "https://www.googleapis.com";
|
|
7
|
+
const googleEmulatorDrillKnobs = [
|
|
8
|
+
"gmailPageRepeats",
|
|
9
|
+
"attachmentStandardBase64",
|
|
10
|
+
"notFoundWithoutMessage",
|
|
11
|
+
"labelDeleteKeepsOnMessages",
|
|
12
|
+
"draftUpdateKeepsContent",
|
|
13
|
+
"trashAnswerOmitsTrash",
|
|
14
|
+
"sentMessageWithoutTo",
|
|
15
|
+
"calendarPageRepeats",
|
|
16
|
+
"eventPatchKeepsSummary",
|
|
17
|
+
"repeatedEventDeleteConflict",
|
|
18
|
+
"drivePageRepeats",
|
|
19
|
+
"getFileWithoutParents",
|
|
20
|
+
"listIncludesTrashed"
|
|
21
|
+
];
|
|
22
|
+
/** Route evidence of an unverified connector route on `origin`, citing `caseIds`. */
|
|
23
|
+
const evidence = (origin, method, path, write, caseIds, params = {}) => ({
|
|
24
|
+
method,
|
|
25
|
+
path,
|
|
26
|
+
kind: "connector",
|
|
27
|
+
write,
|
|
28
|
+
caseIds,
|
|
29
|
+
evidence: "unverified",
|
|
30
|
+
origin,
|
|
31
|
+
params
|
|
32
|
+
});
|
|
33
|
+
/** A path parameter, decoded once by the wrapper (empty when absent). */
|
|
34
|
+
const param = (request, name) => request.params[name] ?? "";
|
|
35
|
+
/** The `internalDate` every recorded Gmail message carries. */
|
|
36
|
+
const recordedInternalDate = "1790000000000";
|
|
37
|
+
/** The raw path segment of a Gmail message, thread, label, attachment, or draft id. */
|
|
38
|
+
const gmailIdSegment = /^[A-Za-z0-9_-]{1,100}$/;
|
|
39
|
+
const googleContentType = "application/json; charset=UTF-8";
|
|
40
|
+
const googleJson = (status, body) => new Response(JSON.stringify(body), {
|
|
41
|
+
status,
|
|
42
|
+
headers: { "content-type": googleContentType }
|
|
43
|
+
});
|
|
44
|
+
/** The bodiless 204 every recorded delete answers. */
|
|
45
|
+
const googleNoContent = () => new Response(null, { status: 204 });
|
|
46
|
+
/** The Google JSON error envelope, as the fixtures write it. */
|
|
47
|
+
const googleErrorBody = (code, message, reason, status) => ({ error: {
|
|
48
|
+
code,
|
|
49
|
+
message,
|
|
50
|
+
errors: [{
|
|
51
|
+
message,
|
|
52
|
+
domain: "global",
|
|
53
|
+
reason
|
|
54
|
+
}],
|
|
55
|
+
status
|
|
56
|
+
} });
|
|
57
|
+
/** A read-only commit. */
|
|
58
|
+
const answer = (response) => () => response();
|
|
59
|
+
/** A query value that must equal the recorded one. */
|
|
60
|
+
const recordedValue = (query, key, value) => query[key] === value ? void 0 : notEmulated(`${key} other than ${value} is not emulated`);
|
|
61
|
+
/** A decimal page size from `minimum` to `maximum`, or not emulated. */
|
|
62
|
+
const pageSize = (raw, label, minimum, maximum) => integerIn(raw !== void 0 && /^[1-9]\d{0,5}$/.test(raw) ? Number(raw) : null, label, minimum, maximum);
|
|
63
|
+
/** A request without query parameters, or not emulated. */
|
|
64
|
+
const withoutQuery = (request) => request.query.size > 0 ? notEmulated("query parameters are not emulated on this route") : void 0;
|
|
65
|
+
/** The run id shape of the cases (`GoogleConformanceRunId`): at most 40 characters. */
|
|
66
|
+
const runIdPattern = /^run-[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
67
|
+
/**
|
|
68
|
+
* The well-formed run id of `yolk-conformance <runId> <kind>` (or `... <kind>: <rest>`), or
|
|
69
|
+
* `undefined` when `text` has another form.
|
|
70
|
+
*/
|
|
71
|
+
const runIdOf = (text, kind, rest) => {
|
|
72
|
+
const prefix = "yolk-conformance ";
|
|
73
|
+
const suffix = rest === void 0 ? ` ${kind}` : ` ${kind}: ${rest}`;
|
|
74
|
+
if (!text.startsWith(prefix) || !text.endsWith(suffix)) return void 0;
|
|
75
|
+
const runId = text.slice(17, text.length - suffix.length);
|
|
76
|
+
return runId.length <= 40 && runIdPattern.test(runId) ? runId : void 0;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* True when `text` is `yolk-conformance <runId> <kind>` (or `... <kind>: <rest>`) for a
|
|
80
|
+
* well-formed run id: the only latitude in the run-scoped names, subjects, and summaries.
|
|
81
|
+
*/
|
|
82
|
+
const isRunText = (text, kind, rest) => runIdOf(text, kind, rest) !== void 0;
|
|
83
|
+
/**
|
|
84
|
+
* The length of the fixtures' run id (`run-synthetic`). The draft and sent messages the Gmail
|
|
85
|
+
* fixtures record carry a `sizeEstimate` that covers their subject, so a draft or send subject
|
|
86
|
+
* must carry a run id of exactly this length for that recorded value to stay true.
|
|
87
|
+
*/
|
|
88
|
+
const recordedRunIdLength = 13;
|
|
89
|
+
/** `value` in this generation (see `GoogleApiEnv.generation`); records its first issuance. */
|
|
90
|
+
const generationValue = (env, value) => {
|
|
91
|
+
const first = env.firstIssued.get(value);
|
|
92
|
+
const generation = env.generation.current;
|
|
93
|
+
if (first === void 0) env.firstIssued.set(value, generation);
|
|
94
|
+
return first === void 0 || first === generation ? value : `${value}.g${generation}`;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* The page token for page `page` of a list (in a commit), bound to `issued` (its list, offset, and
|
|
98
|
+
* fingerprint). The first issuance is the fixture's value (`synthetic-<api>-page-<n>`). Token
|
|
99
|
+
* values are globally unique: a value already bound is reused only for exactly the same list,
|
|
100
|
+
* position, and rendering, and never rebound to anything else, so another list or page size, or a
|
|
101
|
+
* changed list, gets a distinct value (`synthetic-<api>-page-<n>.v<k>`) and an earlier token keeps
|
|
102
|
+
* meaning only what it was issued for.
|
|
103
|
+
*/
|
|
104
|
+
const issueToken = (env, prefix, page, issued) => {
|
|
105
|
+
const base = `synthetic-${prefix}-page-${page}`;
|
|
106
|
+
for (let variant = 1;; variant += 1) {
|
|
107
|
+
const value = generationValue(env, variant === 1 ? base : `${base}.v${variant}`);
|
|
108
|
+
const existing = env.pageTokens.get(value);
|
|
109
|
+
if (existing === void 0) {
|
|
110
|
+
env.pageTokens.set(value, issued);
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
if (existing.listKey === issued.listKey && existing.fingerprint === issued.fingerprint && existing.offset === issued.offset) return value;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** Where a page of a list starts: 0, or an issued token's offset; not emulated otherwise. */
|
|
117
|
+
const pageStart = (env, listKey, fingerprint, token) => {
|
|
118
|
+
if (token === void 0) return 0;
|
|
119
|
+
const issued = env.pageTokens.get(token);
|
|
120
|
+
if (issued === void 0 || issued.listKey !== listKey) return notEmulated("a pageToken this emulator did not issue for this list since the last reset is not emulated");
|
|
121
|
+
return issued.fingerprint === fingerprint ? issued.offset : notEmulated("continuing a list that changed since its pageToken was issued is not emulated");
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* The commit answering one page of a list: `render(page, nextPageToken)` for the page's rendered
|
|
125
|
+
* items, with a next token (registered for this list) while items remain. `repeats` (a drill)
|
|
126
|
+
* starts a later page one item early.
|
|
127
|
+
*/
|
|
128
|
+
const listPage = (env, prefix, listKey, all, size, token, repeats, render) => {
|
|
129
|
+
const fingerprint = JSON.stringify(all);
|
|
130
|
+
const offset = pageStart(env, listKey, fingerprint, token);
|
|
131
|
+
if (isNotEmulated(offset)) return offset;
|
|
132
|
+
const start = repeats && offset > 0 ? offset - 1 : offset;
|
|
133
|
+
const end = start + size;
|
|
134
|
+
return () => {
|
|
135
|
+
const next = end < all.length ? issueToken(env, prefix, Math.floor(end / size) + 1, {
|
|
136
|
+
listKey,
|
|
137
|
+
offset: end,
|
|
138
|
+
fingerprint
|
|
139
|
+
}) : void 0;
|
|
140
|
+
return render(all.slice(start, end), next);
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
export { answer, evidence, gmailIdSegment, googleEmulatorApisOrigin, googleEmulatorDrillKnobs, googleEmulatorGmailOrigin, googleErrorBody, googleJson, googleNoContent, isRunText, listPage, pageSize, param, recordedInternalDate, recordedRunIdLength, recordedValue, runIdOf, withoutQuery };
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=shared.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.mjs","names":[],"sources":["../../src/google/shared.ts"],"sourcesContent":["/**\n * Shared pieces of the Google emulator API (internal): origins, the environment, drill knobs,\n * response and error-envelope helpers, query checks, run-scoped text, and page tokens.\n *\n * @experimental\n */\nimport type * as Schema from 'effect/Schema'\nimport {\n integerIn,\n isNotEmulated,\n notEmulated,\n type Commit,\n type EmulatedRequest,\n type NotEmulated,\n type StatefulRoute\n} from '../stateful-emulator.ts'\nimport type { GoogleEmulatorState } from './state.ts'\n\n/** The origin of every Gmail route (the API and the upload endpoint), as the fixtures record. */\nexport const googleEmulatorGmailOrigin = 'https://gmail.googleapis.com'\n\n/** The origin of every Calendar and Drive route, as the fixtures record. */\nexport const googleEmulatorApisOrigin = 'https://www.googleapis.com'\n\n/** Drill knobs (tests only): each makes the emulator disagree with exactly one conformance case. */\nexport type GoogleEmulatorDrills = {\n /** A later `gmail.list` page starts one message early, repeating the previous page's last. */\n readonly gmailPageRepeats?: boolean\n /** Attachment `data` is answered in the standard base64 alphabet instead of base64url. */\n readonly attachmentStandardBase64?: boolean\n /** Gmail 404 envelopes carry no `error.message`. */\n readonly notFoundWithoutMessage?: boolean\n /** Deleting a label leaves it on the messages that carry it. */\n readonly labelDeleteKeepsOnMessages?: boolean\n /** A draft update keeps the earlier subject and body (it ignores the new message). */\n readonly draftUpdateKeepsContent?: boolean\n /** The trash answer omits `TRASH` from `labelIds` (the state still carries it). */\n readonly trashAnswerOmitsTrash?: boolean\n /** The sent message reads back without its `To` header. */\n readonly sentMessageWithoutTo?: boolean\n /** A later `calendar.list_events` page starts one event early. */\n readonly calendarPageRepeats?: boolean\n /** An event PATCH answers (and keeps) the earlier summary. */\n readonly eventPatchKeepsSummary?: boolean\n /** Deleting a cancelled event answers 409 instead of the recorded 410. */\n readonly repeatedEventDeleteConflict?: boolean\n /** A later `drive.list_files` page starts one file early. */\n readonly drivePageRepeats?: boolean\n /** `drive.get_file` answers no `parents`. */\n readonly getFileWithoutParents?: boolean\n /** Folder listings include trashed children. */\n readonly listIncludesTrashed?: boolean\n}\n\nexport const googleEmulatorDrillKnobs: ReadonlyArray<keyof GoogleEmulatorDrills> = [\n 'gmailPageRepeats',\n 'attachmentStandardBase64',\n 'notFoundWithoutMessage',\n 'labelDeleteKeepsOnMessages',\n 'draftUpdateKeepsContent',\n 'trashAnswerOmitsTrash',\n 'sentMessageWithoutTo',\n 'calendarPageRepeats',\n 'eventPatchKeepsSummary',\n 'repeatedEventDeleteConflict',\n 'drivePageRepeats',\n 'getFileWithoutParents',\n 'listIncludesTrashed'\n]\n\n/** A page token this emulator issued: where its list continues, and the list it was issued for. */\nexport type GoogleIssuedPageToken = {\n /** The list it continues (its key names the list and its page size). */\n readonly listKey: string\n readonly offset: number\n /** The whole list as rendered when the token was issued; any change is not emulated. */\n readonly fingerprint: string\n}\n\nexport type GoogleApiEnv = {\n /** Clock in epoch milliseconds (created and updated timestamps, the sent `Date` header). */\n readonly now: () => number\n readonly drills: Readonly<Record<keyof GoogleEmulatorDrills, boolean>>\n /**\n * Issued page tokens by value (runtime-only; cleared by reset and seed). A value is bound to\n * exactly one list, position, and rendering, and never rebound.\n */\n readonly pageTokens: Map<string, GoogleIssuedPageToken>\n /**\n * The token generation: advanced by every reset and seed (never rewound). A page token is the\n * fixture's value (`synthetic-<api>-page-<n>`) only in the generation that first issued that\n * value; a later generation issues `<value>.g<generation>`, so no token value ever crosses a\n * reset or seed.\n */\n readonly generation: { current: number }\n /** The generation that first issued each token value (never cleared). */\n readonly firstIssued: Map<string, number>\n}\n\nexport type GoogleRoute = StatefulRoute<GoogleEmulatorState, GoogleApiEnv>\n\n/** Route evidence of an unverified connector route on `origin`, citing `caseIds`. */\nexport const evidence = (\n origin: string,\n method: string,\n path: string,\n write: boolean,\n caseIds: ReadonlyArray<string>,\n params: Readonly<Record<string, RegExp>> = {}\n) => ({\n method,\n path,\n kind: 'connector' as const,\n write,\n caseIds,\n evidence: 'unverified' as const,\n origin,\n params\n})\n\n/** A path parameter, decoded once by the wrapper (empty when absent). */\nexport const param = (request: EmulatedRequest, name: string): string => request.params[name] ?? ''\n\n/** The `internalDate` every recorded Gmail message carries. */\nexport const recordedInternalDate = '1790000000000'\n\n/** The raw path segment of a Gmail message, thread, label, attachment, or draft id. */\nexport const gmailIdSegment = /^[A-Za-z0-9_-]{1,100}$/\n\n// Responses, as the fixtures record them.\n\nconst googleContentType = 'application/json; charset=UTF-8'\n\nexport const googleJson = (status: number, body: Schema.Json): Response =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': googleContentType } })\n\n/** The bodiless 204 every recorded delete answers. */\nexport const googleNoContent = (): Response => new Response(null, { status: 204 })\n\n/** The Google JSON error envelope, as the fixtures write it. */\nexport const googleErrorBody = (\n code: number,\n message: string,\n reason: string,\n status: string\n): Schema.JsonObject => ({\n error: { code, message, errors: [{ message, domain: 'global', reason }], status }\n})\n\n/** A read-only commit. */\nexport const answer =\n (response: () => Response): Commit =>\n () =>\n response()\n\n// Queries.\n\n/** A query value that must equal the recorded one. */\nexport const recordedValue = (\n query: Readonly<Record<string, string>>,\n key: string,\n value: string\n): NotEmulated | undefined =>\n query[key] === value ? undefined : notEmulated(`${key} other than ${value} is not emulated`)\n\n/** A decimal page size from `minimum` to `maximum`, or not emulated. */\nexport const pageSize = (\n raw: string | undefined,\n label: string,\n minimum: number,\n maximum: number\n): number | NotEmulated =>\n integerIn(\n raw !== undefined && /^[1-9]\\d{0,5}$/.test(raw) ? Number(raw) : null,\n label,\n minimum,\n maximum\n )\n\n/** A request without query parameters, or not emulated. */\nexport const withoutQuery = (request: EmulatedRequest): NotEmulated | undefined =>\n request.query.size > 0\n ? notEmulated('query parameters are not emulated on this route')\n : undefined\n\n// Run-scoped text.\n\n/** The run id shape of the cases (`GoogleConformanceRunId`): at most 40 characters. */\nconst runIdPattern = /^run-[a-z0-9]+(?:-[a-z0-9]+)*$/\n\n/**\n * The well-formed run id of `yolk-conformance <runId> <kind>` (or `... <kind>: <rest>`), or\n * `undefined` when `text` has another form.\n */\nexport const runIdOf = (text: string, kind: string, rest?: string): string | undefined => {\n const prefix = 'yolk-conformance '\n const suffix = rest === undefined ? ` ${kind}` : ` ${kind}: ${rest}`\n\n if (!text.startsWith(prefix) || !text.endsWith(suffix)) return undefined\n\n const runId = text.slice(prefix.length, text.length - suffix.length)\n\n return runId.length <= 40 && runIdPattern.test(runId) ? runId : undefined\n}\n\n/**\n * True when `text` is `yolk-conformance <runId> <kind>` (or `... <kind>: <rest>`) for a\n * well-formed run id: the only latitude in the run-scoped names, subjects, and summaries.\n */\nexport const isRunText = (text: string, kind: string, rest?: string): boolean =>\n runIdOf(text, kind, rest) !== undefined\n\n/**\n * The length of the fixtures' run id (`run-synthetic`). The draft and sent messages the Gmail\n * fixtures record carry a `sizeEstimate` that covers their subject, so a draft or send subject\n * must carry a run id of exactly this length for that recorded value to stay true.\n */\nexport const recordedRunIdLength = 13\n\n// Page tokens: the fixture's value on first issuance, validated by issuance only.\n\n/** `value` in this generation (see `GoogleApiEnv.generation`); records its first issuance. */\nconst generationValue = (env: GoogleApiEnv, value: string): string => {\n const first = env.firstIssued.get(value)\n const generation = env.generation.current\n\n if (first === undefined) env.firstIssued.set(value, generation)\n\n return first === undefined || first === generation ? value : `${value}.g${generation}`\n}\n\n/**\n * The page token for page `page` of a list (in a commit), bound to `issued` (its list, offset, and\n * fingerprint). The first issuance is the fixture's value (`synthetic-<api>-page-<n>`). Token\n * values are globally unique: a value already bound is reused only for exactly the same list,\n * position, and rendering, and never rebound to anything else, so another list or page size, or a\n * changed list, gets a distinct value (`synthetic-<api>-page-<n>.v<k>`) and an earlier token keeps\n * meaning only what it was issued for.\n */\nconst issueToken = (\n env: GoogleApiEnv,\n prefix: string,\n page: number,\n issued: GoogleIssuedPageToken\n): string => {\n const base = `synthetic-${prefix}-page-${page}`\n\n for (let variant = 1; ; variant += 1) {\n const value = generationValue(env, variant === 1 ? base : `${base}.v${variant}`)\n const existing = env.pageTokens.get(value)\n\n if (existing === undefined) {\n env.pageTokens.set(value, issued)\n\n return value\n }\n\n if (\n existing.listKey === issued.listKey &&\n existing.fingerprint === issued.fingerprint &&\n existing.offset === issued.offset\n ) {\n return value\n }\n }\n}\n\n/** Where a page of a list starts: 0, or an issued token's offset; not emulated otherwise. */\nconst pageStart = (\n env: GoogleApiEnv,\n listKey: string,\n fingerprint: string,\n token: string | undefined\n): number | NotEmulated => {\n if (token === undefined) return 0\n\n const issued = env.pageTokens.get(token)\n\n if (issued === undefined || issued.listKey !== listKey) {\n return notEmulated(\n 'a pageToken this emulator did not issue for this list since the last reset is not emulated'\n )\n }\n\n return issued.fingerprint === fingerprint\n ? issued.offset\n : notEmulated('continuing a list that changed since its pageToken was issued is not emulated')\n}\n\n/**\n * The commit answering one page of a list: `render(page, nextPageToken)` for the page's rendered\n * items, with a next token (registered for this list) while items remain. `repeats` (a drill)\n * starts a later page one item early.\n */\nexport const listPage = (\n env: GoogleApiEnv,\n prefix: string,\n listKey: string,\n all: ReadonlyArray<Schema.Json>,\n size: number,\n token: string | undefined,\n repeats: boolean,\n render: (page: ReadonlyArray<Schema.Json>, nextPageToken: string | undefined) => Response\n): Commit | NotEmulated => {\n const fingerprint = JSON.stringify(all)\n const offset = pageStart(env, listKey, fingerprint, token)\n\n if (isNotEmulated(offset)) return offset\n\n const start = repeats && offset > 0 ? offset - 1 : offset\n const end = start + size\n\n return () => {\n const next =\n end < all.length\n ? issueToken(env, prefix, Math.floor(end / size) + 1, { listKey, offset: end, fingerprint })\n : undefined\n\n return render(all.slice(start, end), next)\n }\n}\n"],"mappings":"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"}
|