@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,496 @@
|
|
|
1
|
+
import { recordedInternalDate } from "./shared.mjs";
|
|
2
|
+
import { Result } from "effect";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
//#region src/google/state.ts
|
|
5
|
+
/**
|
|
6
|
+
* Google emulator state: the Gmail messages, labels, attachments, and drafts, the Calendar
|
|
7
|
+
* calendars and events, and the Drive files, the seed input, the default seed, and the profiles
|
|
8
|
+
* (internal; re-exported by `src/google.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Entity shapes and the default entities follow the synthetic Google conformance fixtures and
|
|
11
|
+
* `googleConformanceFixtureSeeds`, copied as data (the same ids, labels, payloads, timestamps, and
|
|
12
|
+
* links), never imported from SDK code. Entities a fixture only names are implied: the paging
|
|
13
|
+
* label (`impliedLabelIds`), the five messages its listing names by id (`impliedMessages`, which
|
|
14
|
+
* only ever render as `{ id, threadId }` list entries), and the practice Drive folder
|
|
15
|
+
* (`impliedFolderIds`, a parent whose own metadata no fixture shows). References resolve through
|
|
16
|
+
* them, but no answer renders them.
|
|
17
|
+
*
|
|
18
|
+
* A stored Gmail message keeps only the renderings a fixture records for it: `minimal` (no
|
|
19
|
+
* payload), and the `metadata` and `full` payloads. A `format` with no recorded rendering is not
|
|
20
|
+
* emulated. Created labels and drafts are never seeded (they exist only once created here).
|
|
21
|
+
*
|
|
22
|
+
* Minted ids never collide with seeded ones: draft and sent message ids (`18f…d<n>`, `18f…e<n>`),
|
|
23
|
+
* which a created draft also uses as its thread id, event ids (`syntheticconformance<nnnn>`), and
|
|
24
|
+
* folder ids (`synthetic-conformance-folder-<nnnn>`) have reserved forms that no seeded message id,
|
|
25
|
+
* thread id, event id, or Drive id may use. Created label ids (`Label_<n>`, `n` from 1 to
|
|
26
|
+
* 999999999 without leading zeros) start above every seeded label number (at least `Label_9101`,
|
|
27
|
+
* the value the label fixture records); a seeded `Label_<digits>` id must have that form, and a
|
|
28
|
+
* create when no number is left is not emulated.
|
|
29
|
+
*
|
|
30
|
+
* @experimental
|
|
31
|
+
*/
|
|
32
|
+
const JsonObject = Schema.Record(Schema.String, Schema.Json);
|
|
33
|
+
/** A Gmail message, thread, label, or attachment id as the fixtures use them. */
|
|
34
|
+
const GmailId = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,100}$/));
|
|
35
|
+
const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
36
|
+
/**
|
|
37
|
+
* The practice account's own address: exactly one plain `local@domain` (the shape the send case's
|
|
38
|
+
* `GooglePracticeAddress` seed takes). The fixtures use it as the sender, the only recipient, and
|
|
39
|
+
* the event creator. A seed may set another one, but the send answers only while it is the
|
|
40
|
+
* recorded `practice@example.test` (the recorded `sizeEstimate` of the sent message covers it).
|
|
41
|
+
*/
|
|
42
|
+
const GoogleEmulatorPracticeAddress = Schema.String.check(Schema.isMaxLength(254), Schema.isPattern(/^[A-Za-z0-9_%+-]+(?:\.[A-Za-z0-9_%+-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/), Schema.makeFilter((value) => value.indexOf("@") <= 64));
|
|
43
|
+
/**
|
|
44
|
+
* A stored Gmail message with the renderings a fixture records for it: `minimal` (true when a
|
|
45
|
+
* fixture records `format=minimal`), and the `metadata` and `full` payloads (null when none is
|
|
46
|
+
* recorded). The head fields are answered as stored.
|
|
47
|
+
*/
|
|
48
|
+
const GoogleEmulatorGmailMessage = Schema.Struct({
|
|
49
|
+
id: GmailId,
|
|
50
|
+
threadId: GmailId,
|
|
51
|
+
labelIds: Schema.Array(Schema.String),
|
|
52
|
+
snippet: Schema.String,
|
|
53
|
+
sizeEstimate: NonNegativeInt,
|
|
54
|
+
historyId: Schema.String,
|
|
55
|
+
internalDate: Schema.String,
|
|
56
|
+
minimal: Schema.Boolean,
|
|
57
|
+
metadataPayload: Schema.NullOr(JsonObject),
|
|
58
|
+
fullPayload: Schema.NullOr(JsonObject)
|
|
59
|
+
});
|
|
60
|
+
/** A message a fixture only names in a listing (`{ id, threadId }`), with the labels it carries. */
|
|
61
|
+
const GoogleEmulatorImpliedMessage = Schema.Struct({
|
|
62
|
+
id: GmailId,
|
|
63
|
+
threadId: GmailId,
|
|
64
|
+
labelIds: Schema.Array(Schema.String)
|
|
65
|
+
});
|
|
66
|
+
/** A label created here (`POST .../labels`), answered as the label fixture records it. */
|
|
67
|
+
const GoogleEmulatorGmailLabel = Schema.Struct({
|
|
68
|
+
id: GmailId,
|
|
69
|
+
name: Schema.String,
|
|
70
|
+
messageListVisibility: Schema.String,
|
|
71
|
+
labelListVisibility: Schema.String,
|
|
72
|
+
type: Schema.String
|
|
73
|
+
});
|
|
74
|
+
/** An attachment Gmail stores apart from its message: `size` bytes as base64url `data`. */
|
|
75
|
+
const GoogleEmulatorAttachment = Schema.Struct({
|
|
76
|
+
messageId: GmailId,
|
|
77
|
+
attachmentId: GmailId,
|
|
78
|
+
size: NonNegativeInt,
|
|
79
|
+
data: Schema.String
|
|
80
|
+
});
|
|
81
|
+
/** A draft created here and its current message. */
|
|
82
|
+
const GoogleEmulatorDraft = Schema.Struct({
|
|
83
|
+
id: Schema.String,
|
|
84
|
+
messageId: GmailId
|
|
85
|
+
});
|
|
86
|
+
/** A calendar the events live in, with the fields a listing page answers. */
|
|
87
|
+
const GoogleEmulatorCalendar = Schema.Struct({
|
|
88
|
+
id: Schema.String,
|
|
89
|
+
summary: Schema.String,
|
|
90
|
+
timeZone: Schema.String,
|
|
91
|
+
accessRole: Schema.String
|
|
92
|
+
});
|
|
93
|
+
/** An event boundary: exactly one of `dateTime` (with an optional `timeZone`) or `date`. */
|
|
94
|
+
const GoogleEmulatorEventBoundary = Schema.Union([Schema.Struct({
|
|
95
|
+
dateTime: Schema.String,
|
|
96
|
+
timeZone: Schema.optionalKey(Schema.String)
|
|
97
|
+
}), Schema.Struct({ date: Schema.String })]);
|
|
98
|
+
/** A stored calendar event with its wire fields (creator and organizer derive from the state). */
|
|
99
|
+
const GoogleEmulatorCalendarEvent = Schema.Struct({
|
|
100
|
+
calendarId: Schema.String,
|
|
101
|
+
id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,1024}$/)),
|
|
102
|
+
etag: Schema.String,
|
|
103
|
+
status: Schema.Literals(["confirmed", "cancelled"]),
|
|
104
|
+
htmlLink: Schema.String,
|
|
105
|
+
created: Schema.String,
|
|
106
|
+
updated: Schema.String,
|
|
107
|
+
summary: Schema.String,
|
|
108
|
+
description: Schema.optionalKey(Schema.String),
|
|
109
|
+
start: GoogleEmulatorEventBoundary,
|
|
110
|
+
end: GoogleEmulatorEventBoundary,
|
|
111
|
+
iCalUID: Schema.String,
|
|
112
|
+
sequence: NonNegativeInt
|
|
113
|
+
});
|
|
114
|
+
/** A Drive file or folder with the fields the connector's `fields` selection answers. */
|
|
115
|
+
const GoogleEmulatorDriveFile = Schema.Struct({
|
|
116
|
+
id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{10,200}$/)),
|
|
117
|
+
name: Schema.String,
|
|
118
|
+
mimeType: Schema.String,
|
|
119
|
+
starred: Schema.Boolean,
|
|
120
|
+
trashed: Schema.Boolean,
|
|
121
|
+
explicitlyTrashed: Schema.Boolean,
|
|
122
|
+
trashedTime: Schema.optionalKey(Schema.String),
|
|
123
|
+
parents: Schema.Array(Schema.String),
|
|
124
|
+
spaces: Schema.Array(Schema.String),
|
|
125
|
+
version: Schema.String,
|
|
126
|
+
webViewLink: Schema.String,
|
|
127
|
+
iconLink: Schema.String,
|
|
128
|
+
hasThumbnail: Schema.Boolean,
|
|
129
|
+
viewedByMe: Schema.Boolean,
|
|
130
|
+
createdTime: Schema.String,
|
|
131
|
+
modifiedTime: Schema.String,
|
|
132
|
+
ownedByMe: Schema.Boolean,
|
|
133
|
+
shared: Schema.Boolean,
|
|
134
|
+
writersCanShare: Schema.Boolean,
|
|
135
|
+
capabilities: JsonObject
|
|
136
|
+
});
|
|
137
|
+
const Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
138
|
+
const Counters = Schema.Struct({
|
|
139
|
+
/** Next created label number (`Label_<n>`). */
|
|
140
|
+
nextLabelNumber: Counter,
|
|
141
|
+
/** Next created draft number (`r-8<n, 18 digits>`). */
|
|
142
|
+
nextDraftNumber: Counter,
|
|
143
|
+
/** Next draft message number (`18f…d<n>`). */
|
|
144
|
+
nextDraftMessageNumber: Counter,
|
|
145
|
+
/** Next sent message number (`18f…e<n>`, and the `Message-ID` header). */
|
|
146
|
+
nextSentNumber: Counter,
|
|
147
|
+
/** Next created event number (`syntheticconformance<nnnn>`). */
|
|
148
|
+
nextEventNumber: Counter,
|
|
149
|
+
/** Next created folder number (`synthetic-conformance-folder-<nnnn>`). */
|
|
150
|
+
nextFolderNumber: Counter
|
|
151
|
+
});
|
|
152
|
+
/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */
|
|
153
|
+
const GoogleEmulatorStateSchema = Schema.Struct({
|
|
154
|
+
practiceAddress: GoogleEmulatorPracticeAddress,
|
|
155
|
+
messages: Schema.Array(GoogleEmulatorGmailMessage),
|
|
156
|
+
impliedMessages: Schema.Array(GoogleEmulatorImpliedMessage),
|
|
157
|
+
impliedLabelIds: Schema.Array(GmailId),
|
|
158
|
+
labels: Schema.Array(GoogleEmulatorGmailLabel),
|
|
159
|
+
attachments: Schema.Array(GoogleEmulatorAttachment),
|
|
160
|
+
drafts: Schema.Array(GoogleEmulatorDraft),
|
|
161
|
+
calendars: Schema.Array(GoogleEmulatorCalendar),
|
|
162
|
+
events: Schema.Array(GoogleEmulatorCalendarEvent),
|
|
163
|
+
files: Schema.Array(GoogleEmulatorDriveFile),
|
|
164
|
+
impliedFolderIds: Schema.Array(Schema.String),
|
|
165
|
+
counters: Counters
|
|
166
|
+
});
|
|
167
|
+
/** Account-variance profiles for the default seed. */
|
|
168
|
+
const GoogleEmulatorProfile = Schema.Literals(["default", "empty"]);
|
|
169
|
+
/**
|
|
170
|
+
* A typed seed. Start from `profile` (default `'default'`, the fixture entities); every other key,
|
|
171
|
+
* when given, replaces that part of the profile. Created labels and drafts are never seeded.
|
|
172
|
+
*/
|
|
173
|
+
const GoogleEmulatorSeed = Schema.Struct({
|
|
174
|
+
profile: Schema.optionalKey(GoogleEmulatorProfile),
|
|
175
|
+
practiceAddress: Schema.optionalKey(GoogleEmulatorPracticeAddress),
|
|
176
|
+
messages: Schema.optionalKey(Schema.Array(GoogleEmulatorGmailMessage)),
|
|
177
|
+
impliedMessages: Schema.optionalKey(Schema.Array(GoogleEmulatorImpliedMessage)),
|
|
178
|
+
impliedLabelIds: Schema.optionalKey(Schema.Array(GmailId)),
|
|
179
|
+
attachments: Schema.optionalKey(Schema.Array(GoogleEmulatorAttachment)),
|
|
180
|
+
calendars: Schema.optionalKey(Schema.Array(GoogleEmulatorCalendar)),
|
|
181
|
+
events: Schema.optionalKey(Schema.Array(GoogleEmulatorCalendarEvent)),
|
|
182
|
+
files: Schema.optionalKey(Schema.Array(GoogleEmulatorDriveFile)),
|
|
183
|
+
impliedFolderIds: Schema.optionalKey(Schema.Array(Schema.String))
|
|
184
|
+
});
|
|
185
|
+
const strict = { onExcessProperty: "error" };
|
|
186
|
+
const decodeSeedInput = Schema.decodeUnknownResult(GoogleEmulatorSeed, strict);
|
|
187
|
+
const decodeStateInput = Schema.decodeUnknownResult(GoogleEmulatorStateSchema, strict);
|
|
188
|
+
const issueMessage = (issue) => new Schema.SchemaError(issue).message;
|
|
189
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
190
|
+
/** A minted message id: `18f`, then `d<n>` (draft) or `e<n>` (sent), zero-padded to 16. */
|
|
191
|
+
const mintedMessageId = (kind, number) => `18f${`${kind}${number}`.padStart(13, "0")}`;
|
|
192
|
+
const mintedMessageIdPattern = /^18f0*[de]\d+$/;
|
|
193
|
+
/** A minted draft id: `r-8` and 18 digits (`r-8000000000000000001` first). */
|
|
194
|
+
const mintedDraftId = (number) => `r-8${pad(number, 18)}`;
|
|
195
|
+
const mintedEventId = (number) => `syntheticconformance${pad(number, 4)}`;
|
|
196
|
+
const mintedEventIdPattern = /^syntheticconformance\d+$/;
|
|
197
|
+
const mintedFolderId = (number) => `synthetic-conformance-folder-${pad(number, 4)}`;
|
|
198
|
+
const mintedFolderIdPattern = /^synthetic-conformance-folder-\d+$/;
|
|
199
|
+
/** The label number the label fixture records for the first created label. */
|
|
200
|
+
const firstLabelNumber = 9101;
|
|
201
|
+
/** The highest label number a created (minted) label id may carry: `Label_999999999`. */
|
|
202
|
+
const lastLabelNumber = 999999999;
|
|
203
|
+
/**
|
|
204
|
+
* The minted label id form, `Label_<1 to 999999999>` without leading zeros. A seeded label id of
|
|
205
|
+
* the `Label_<digits>` shape must have this form, so the created-label counter, which starts above
|
|
206
|
+
* every seeded label number, never mints an id a seed holds.
|
|
207
|
+
*/
|
|
208
|
+
const mintedLabelIdPattern = /^Label_([1-9]\d{0,8})$/;
|
|
209
|
+
const labelDigitsPattern = /^Label_\d+$/;
|
|
210
|
+
/** A Gmail system label (upper case), as opposed to a user label id such as `Label_9001`. */
|
|
211
|
+
const isSystemLabel = (id) => /^[A-Z][A-Z_]*$/.test(id);
|
|
212
|
+
/** The practice account (`practice@example.test`) of every fixture. */
|
|
213
|
+
const googleEmulatorDefaultPracticeAddress = "practice@example.test";
|
|
214
|
+
/** Base64url without padding of UTF-8 text, as the fixtures encode message parts. */
|
|
215
|
+
const base64UrlOfText = (text) => {
|
|
216
|
+
let binary = "";
|
|
217
|
+
for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);
|
|
218
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
219
|
+
};
|
|
220
|
+
/** The seeded work message (`format=minimal`, as the label and trash fixtures read it). */
|
|
221
|
+
const workMessage = {
|
|
222
|
+
id: "18f00000000000b1",
|
|
223
|
+
threadId: "18f00000000000b1",
|
|
224
|
+
labelIds: ["INBOX", "IMPORTANT"],
|
|
225
|
+
snippet: "Synthetic practice message.",
|
|
226
|
+
sizeEstimate: 2048,
|
|
227
|
+
historyId: "900001",
|
|
228
|
+
internalDate: recordedInternalDate,
|
|
229
|
+
minimal: true,
|
|
230
|
+
metadataPayload: null,
|
|
231
|
+
fullPayload: null
|
|
232
|
+
};
|
|
233
|
+
const attachmentText = "Synthetic practice message with an attachment.";
|
|
234
|
+
const attachmentId = "ANGjdJ_synthetic_attachment_0001";
|
|
235
|
+
/** The seeded attachment message (`format=full`, as the attachment fixture reads it). */
|
|
236
|
+
const attachmentMessage = {
|
|
237
|
+
id: "18f00000000000a1",
|
|
238
|
+
threadId: "18f00000000000a1",
|
|
239
|
+
labelIds: ["INBOX"],
|
|
240
|
+
snippet: attachmentText,
|
|
241
|
+
sizeEstimate: 4096,
|
|
242
|
+
historyId: "900010",
|
|
243
|
+
internalDate: recordedInternalDate,
|
|
244
|
+
minimal: false,
|
|
245
|
+
metadataPayload: null,
|
|
246
|
+
fullPayload: {
|
|
247
|
+
partId: "",
|
|
248
|
+
mimeType: "multipart/mixed",
|
|
249
|
+
filename: "",
|
|
250
|
+
headers: [{
|
|
251
|
+
name: "Subject",
|
|
252
|
+
value: "Synthetic practice attachment"
|
|
253
|
+
}, {
|
|
254
|
+
name: "Content-Type",
|
|
255
|
+
value: "multipart/mixed; boundary=\"synthetic\""
|
|
256
|
+
}],
|
|
257
|
+
body: { size: 0 },
|
|
258
|
+
parts: [{
|
|
259
|
+
partId: "0",
|
|
260
|
+
mimeType: "text/plain",
|
|
261
|
+
filename: "",
|
|
262
|
+
headers: [{
|
|
263
|
+
name: "Content-Type",
|
|
264
|
+
value: "text/plain; charset=\"UTF-8\""
|
|
265
|
+
}],
|
|
266
|
+
body: {
|
|
267
|
+
size: 46,
|
|
268
|
+
data: base64UrlOfText(attachmentText)
|
|
269
|
+
}
|
|
270
|
+
}, {
|
|
271
|
+
partId: "1",
|
|
272
|
+
mimeType: "application/octet-stream",
|
|
273
|
+
filename: "synthetic.bin",
|
|
274
|
+
headers: [
|
|
275
|
+
{
|
|
276
|
+
name: "Content-Type",
|
|
277
|
+
value: "application/octet-stream; name=\"synthetic.bin\""
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "Content-Disposition",
|
|
281
|
+
value: "attachment; filename=\"synthetic.bin\""
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "Content-Transfer-Encoding",
|
|
285
|
+
value: "base64"
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
body: {
|
|
289
|
+
attachmentId,
|
|
290
|
+
size: 6
|
|
291
|
+
}
|
|
292
|
+
}]
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const pagingLabelId = "Label_9001";
|
|
296
|
+
/** The five messages the paging fixture names (listing entries only). */
|
|
297
|
+
const pagingMessages = [
|
|
298
|
+
1,
|
|
299
|
+
2,
|
|
300
|
+
3,
|
|
301
|
+
4,
|
|
302
|
+
5
|
|
303
|
+
].map((index) => ({
|
|
304
|
+
id: `18f00000000000c${index}`,
|
|
305
|
+
threadId: `18f00000000000c${index}`,
|
|
306
|
+
labelIds: [pagingLabelId]
|
|
307
|
+
}));
|
|
308
|
+
const calendarId = "practice-calendar@example.test";
|
|
309
|
+
const practiceCalendar = {
|
|
310
|
+
id: calendarId,
|
|
311
|
+
summary: "Synthetic practice calendar",
|
|
312
|
+
timeZone: "UTC",
|
|
313
|
+
accessRole: "owner"
|
|
314
|
+
};
|
|
315
|
+
const seededAt = "2026-09-30T12:00:00.000Z";
|
|
316
|
+
/** An event with the fixture's derived fields (`etag`, `htmlLink`, `iCalUID`). */
|
|
317
|
+
const eventFields = (id) => ({
|
|
318
|
+
etag: `"3${id.length}000000000000"`,
|
|
319
|
+
htmlLink: `https://www.google.com/calendar/event?eid=${id}`,
|
|
320
|
+
iCalUID: `${id}@google.com`
|
|
321
|
+
});
|
|
322
|
+
const rangeEvent = (id, summary, start, end) => ({
|
|
323
|
+
calendarId,
|
|
324
|
+
id,
|
|
325
|
+
...eventFields(id),
|
|
326
|
+
status: "confirmed",
|
|
327
|
+
created: seededAt,
|
|
328
|
+
updated: seededAt,
|
|
329
|
+
summary,
|
|
330
|
+
start,
|
|
331
|
+
end,
|
|
332
|
+
sequence: 0
|
|
333
|
+
});
|
|
334
|
+
const utc = (dateTime) => ({
|
|
335
|
+
dateTime,
|
|
336
|
+
timeZone: "UTC"
|
|
337
|
+
});
|
|
338
|
+
/** The five events of the range fixture (one all-day, two crossing the range edges). */
|
|
339
|
+
const rangeEvents = [
|
|
340
|
+
rangeEvent("syntheticrange0001", "Synthetic overnight", utc("2026-08-31T23:30:00Z"), utc("2026-09-01T00:30:00Z")),
|
|
341
|
+
rangeEvent("syntheticrange0002", "Synthetic standup", utc("2026-09-02T09:00:00Z"), utc("2026-09-02T09:15:00Z")),
|
|
342
|
+
rangeEvent("syntheticrange0003", "Synthetic review", utc("2026-09-03T14:00:00Z"), utc("2026-09-03T15:00:00Z")),
|
|
343
|
+
rangeEvent("syntheticrange0004", "Synthetic all-day", { date: "2026-09-05" }, { date: "2026-09-06" }),
|
|
344
|
+
rangeEvent("syntheticrange0005", "Synthetic late", utc("2026-09-07T23:00:00Z"), utc("2026-09-08T01:00:00Z"))
|
|
345
|
+
];
|
|
346
|
+
/** The folder mime type Drive answers. */
|
|
347
|
+
const driveFolderMimeType = "application/vnd.google-apps.folder";
|
|
348
|
+
const practiceFolderId = "synthetic-practice-folder-0001";
|
|
349
|
+
/** A Drive item with the fixture's derived and constant fields. */
|
|
350
|
+
const driveFile = (fields) => ({
|
|
351
|
+
id: fields.id,
|
|
352
|
+
name: fields.name,
|
|
353
|
+
mimeType: fields.mimeType,
|
|
354
|
+
starred: false,
|
|
355
|
+
trashed: false,
|
|
356
|
+
explicitlyTrashed: false,
|
|
357
|
+
parents: [...fields.parents],
|
|
358
|
+
spaces: ["drive"],
|
|
359
|
+
version: "3",
|
|
360
|
+
webViewLink: `https://drive.google.com/drive/folders/${fields.id}`,
|
|
361
|
+
iconLink: `https://drive-thirdparty.googleusercontent.com/16/type/${fields.mimeType}`,
|
|
362
|
+
hasThumbnail: false,
|
|
363
|
+
viewedByMe: true,
|
|
364
|
+
createdTime: fields.createdTime,
|
|
365
|
+
modifiedTime: fields.createdTime,
|
|
366
|
+
ownedByMe: true,
|
|
367
|
+
shared: false,
|
|
368
|
+
writersCanShare: true,
|
|
369
|
+
capabilities: {
|
|
370
|
+
canAddChildren: fields.mimeType === driveFolderMimeType,
|
|
371
|
+
canDelete: true,
|
|
372
|
+
canEdit: true,
|
|
373
|
+
canRename: true,
|
|
374
|
+
canTrash: true,
|
|
375
|
+
canUntrash: true
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
const child = (id, name, mimeType) => driveFile({
|
|
379
|
+
id,
|
|
380
|
+
name,
|
|
381
|
+
mimeType,
|
|
382
|
+
parents: [practiceFolderId],
|
|
383
|
+
createdTime: seededAt
|
|
384
|
+
});
|
|
385
|
+
/** The five children of the practice folder (the first is the seeded practice file). */
|
|
386
|
+
const practiceChildren = [
|
|
387
|
+
child("synthetic-practice-file-0001", "Synthetic practice notes.txt", "text/plain"),
|
|
388
|
+
child("synthetic-practice-file-0002", "Synthetic practice sheet.csv", "text/csv"),
|
|
389
|
+
child("synthetic-practice-file-0003", "Synthetic practice image.png", "image/png"),
|
|
390
|
+
child("synthetic-practice-folder-0002", "Synthetic practice subfolder", driveFolderMimeType),
|
|
391
|
+
child("synthetic-practice-file-0004", "Synthetic practice report.pdf", "application/pdf")
|
|
392
|
+
];
|
|
393
|
+
const profileEntities = (profile) => profile === "default" ? {
|
|
394
|
+
practiceAddress: googleEmulatorDefaultPracticeAddress,
|
|
395
|
+
messages: [attachmentMessage, workMessage],
|
|
396
|
+
impliedMessages: pagingMessages,
|
|
397
|
+
impliedLabelIds: [pagingLabelId],
|
|
398
|
+
attachments: [{
|
|
399
|
+
messageId: attachmentMessage.id,
|
|
400
|
+
attachmentId,
|
|
401
|
+
size: 6,
|
|
402
|
+
data: "-_-_Pj_-"
|
|
403
|
+
}],
|
|
404
|
+
calendars: [practiceCalendar],
|
|
405
|
+
events: rangeEvents,
|
|
406
|
+
files: practiceChildren,
|
|
407
|
+
impliedFolderIds: [practiceFolderId]
|
|
408
|
+
} : {
|
|
409
|
+
practiceAddress: googleEmulatorDefaultPracticeAddress,
|
|
410
|
+
messages: [],
|
|
411
|
+
impliedMessages: [],
|
|
412
|
+
impliedLabelIds: [],
|
|
413
|
+
attachments: [],
|
|
414
|
+
calendars: [],
|
|
415
|
+
events: [],
|
|
416
|
+
files: [],
|
|
417
|
+
impliedFolderIds: []
|
|
418
|
+
};
|
|
419
|
+
const duplicate = (values) => values.find((value, index) => values.indexOf(value) !== index);
|
|
420
|
+
/** Integrity problems a decoded seed can still have (duplicates, minted forms, references). */
|
|
421
|
+
const seedProblem = (entities) => {
|
|
422
|
+
const messageIds = [...entities.messages.map((message) => message.id), ...entities.impliedMessages.map((message) => message.id)];
|
|
423
|
+
const fileIds = entities.files.map((file) => file.id);
|
|
424
|
+
const duplicates = [
|
|
425
|
+
["message id", duplicate(messageIds)],
|
|
426
|
+
["label id", duplicate(entities.impliedLabelIds)],
|
|
427
|
+
["attachment", duplicate(entities.attachments.map((item) => `${item.messageId} ${item.attachmentId}`))],
|
|
428
|
+
["calendar id", duplicate(entities.calendars.map((calendar) => calendar.id))],
|
|
429
|
+
["event", duplicate(entities.events.map((event) => `${event.calendarId} ${event.id}`))],
|
|
430
|
+
["Drive id", duplicate([...fileIds, ...entities.impliedFolderIds])]
|
|
431
|
+
];
|
|
432
|
+
for (const [label, value] of duplicates) if (value !== void 0) return `duplicate ${label} ${value}`;
|
|
433
|
+
const minted = messageIds.find((id) => mintedMessageIdPattern.test(id)) ?? entities.events.map((event) => event.id).find((id) => mintedEventIdPattern.test(id)) ?? [...fileIds, ...entities.impliedFolderIds].find((id) => mintedFolderIdPattern.test(id));
|
|
434
|
+
if (minted !== void 0) return `seed id ${minted} uses a form reserved for minted ids`;
|
|
435
|
+
const mintedThread = [...entities.messages, ...entities.impliedMessages].map((message) => message.threadId).find((id) => mintedMessageIdPattern.test(id));
|
|
436
|
+
if (mintedThread !== void 0) return `seed thread id ${mintedThread} uses a form reserved for minted ids`;
|
|
437
|
+
const outsideRange = [...entities.impliedLabelIds, ...[...entities.messages, ...entities.impliedMessages].flatMap((message) => message.labelIds)].find((id) => labelDigitsPattern.test(id) && !mintedLabelIdPattern.test(id));
|
|
438
|
+
if (outsideRange !== void 0) return `seed label id ${outsideRange} is outside the Label_<1 to ${lastLabelNumber}> form`;
|
|
439
|
+
const knownLabels = new Set(entities.impliedLabelIds);
|
|
440
|
+
for (const message of [...entities.messages, ...entities.impliedMessages]) {
|
|
441
|
+
const unknown = message.labelIds.find((id) => !isSystemLabel(id) && !knownLabels.has(id));
|
|
442
|
+
if (unknown !== void 0) return `message ${message.id} carries label ${unknown}, which is not in impliedLabelIds`;
|
|
443
|
+
}
|
|
444
|
+
const attachment = entities.attachments.find((item) => !entities.messages.some((message) => message.id === item.messageId));
|
|
445
|
+
if (attachment !== void 0) return `attachment ${attachment.attachmentId} references missing message ${attachment.messageId}`;
|
|
446
|
+
const event = entities.events.find((item) => !entities.calendars.some((calendar) => calendar.id === item.calendarId));
|
|
447
|
+
if (event !== void 0) return `event ${event.id} references missing calendar ${event.calendarId}`;
|
|
448
|
+
const folders = new Set([...entities.files.filter((file) => file.mimeType === driveFolderMimeType).map((file) => file.id), ...entities.impliedFolderIds]);
|
|
449
|
+
const orphan = entities.files.find((file) => file.parents.some((parent) => !folders.has(parent)));
|
|
450
|
+
return orphan === void 0 ? void 0 : `Drive item ${orphan.id} references a missing parent folder`;
|
|
451
|
+
};
|
|
452
|
+
/** Build the emulator state for a decoded seed; a string is an integrity problem. */
|
|
453
|
+
const stateFromSeed = (seed) => {
|
|
454
|
+
const profile = profileEntities(seed.profile ?? "default");
|
|
455
|
+
const entities = {
|
|
456
|
+
practiceAddress: seed.practiceAddress ?? profile.practiceAddress,
|
|
457
|
+
messages: seed.messages ?? profile.messages,
|
|
458
|
+
impliedMessages: seed.impliedMessages ?? profile.impliedMessages,
|
|
459
|
+
impliedLabelIds: seed.impliedLabelIds ?? profile.impliedLabelIds,
|
|
460
|
+
attachments: seed.attachments ?? profile.attachments,
|
|
461
|
+
calendars: seed.calendars ?? profile.calendars,
|
|
462
|
+
events: seed.events ?? profile.events,
|
|
463
|
+
files: seed.files ?? profile.files,
|
|
464
|
+
impliedFolderIds: seed.impliedFolderIds ?? profile.impliedFolderIds
|
|
465
|
+
};
|
|
466
|
+
const problem = seedProblem(entities);
|
|
467
|
+
if (problem !== void 0) return problem;
|
|
468
|
+
const highestLabel = Math.max(firstLabelNumber - 1, ...entities.impliedLabelIds.map((id) => Number(mintedLabelIdPattern.exec(id)?.[1] ?? 0)));
|
|
469
|
+
return {
|
|
470
|
+
...entities,
|
|
471
|
+
labels: [],
|
|
472
|
+
drafts: [],
|
|
473
|
+
counters: {
|
|
474
|
+
nextLabelNumber: highestLabel + 1,
|
|
475
|
+
nextDraftNumber: 1,
|
|
476
|
+
nextDraftMessageNumber: 1,
|
|
477
|
+
nextSentNumber: 1,
|
|
478
|
+
nextEventNumber: 1,
|
|
479
|
+
nextFolderNumber: 1
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
/** Decode and build a seed; a string is the reason it is invalid. */
|
|
484
|
+
const buildSeedState = (input) => {
|
|
485
|
+
const decoded = decodeSeedInput(input);
|
|
486
|
+
return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : stateFromSeed(decoded.success);
|
|
487
|
+
};
|
|
488
|
+
/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */
|
|
489
|
+
const decodeState = (input) => {
|
|
490
|
+
const decoded = decodeStateInput(input);
|
|
491
|
+
return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success };
|
|
492
|
+
};
|
|
493
|
+
//#endregion
|
|
494
|
+
export { GoogleEmulatorAttachment, GoogleEmulatorCalendar, GoogleEmulatorCalendarEvent, GoogleEmulatorDraft, GoogleEmulatorDriveFile, GoogleEmulatorEventBoundary, GoogleEmulatorGmailLabel, GoogleEmulatorGmailMessage, GoogleEmulatorImpliedMessage, GoogleEmulatorPracticeAddress, GoogleEmulatorProfile, GoogleEmulatorSeed, GoogleEmulatorStateSchema, base64UrlOfText, buildSeedState, decodeState, driveFile, driveFolderMimeType, eventFields, googleEmulatorDefaultPracticeAddress, isSystemLabel, lastLabelNumber, mintedDraftId, mintedEventId, mintedEventIdPattern, mintedFolderId, mintedFolderIdPattern, mintedLabelIdPattern, mintedMessageId, mintedMessageIdPattern, pad };
|
|
495
|
+
|
|
496
|
+
//# sourceMappingURL=state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.mjs","names":[],"sources":["../../src/google/state.ts"],"sourcesContent":["/**\n * Google emulator state: the Gmail messages, labels, attachments, and drafts, the Calendar\n * calendars and events, and the Drive files, the seed input, the default seed, and the profiles\n * (internal; re-exported by `src/google.ts`).\n *\n * Entity shapes and the default entities follow the synthetic Google conformance fixtures and\n * `googleConformanceFixtureSeeds`, copied as data (the same ids, labels, payloads, timestamps, and\n * links), never imported from SDK code. Entities a fixture only names are implied: the paging\n * label (`impliedLabelIds`), the five messages its listing names by id (`impliedMessages`, which\n * only ever render as `{ id, threadId }` list entries), and the practice Drive folder\n * (`impliedFolderIds`, a parent whose own metadata no fixture shows). References resolve through\n * them, but no answer renders them.\n *\n * A stored Gmail message keeps only the renderings a fixture records for it: `minimal` (no\n * payload), and the `metadata` and `full` payloads. A `format` with no recorded rendering is not\n * emulated. Created labels and drafts are never seeded (they exist only once created here).\n *\n * Minted ids never collide with seeded ones: draft and sent message ids (`18f…d<n>`, `18f…e<n>`),\n * which a created draft also uses as its thread id, event ids (`syntheticconformance<nnnn>`), and\n * folder ids (`synthetic-conformance-folder-<nnnn>`) have reserved forms that no seeded message id,\n * thread id, event id, or Drive id may use. Created label ids (`Label_<n>`, `n` from 1 to\n * 999999999 without leading zeros) start above every seeded label number (at least `Label_9101`,\n * the value the label fixture records); a seeded `Label_<digits>` id must have that form, and a\n * create when no number is left is not emulated.\n *\n * @experimental\n */\nimport { Result } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { recordedInternalDate } from './shared.ts'\n\nconst JsonObject = Schema.Record(Schema.String, Schema.Json)\n\n/** A Gmail message, thread, label, or attachment id as the fixtures use them. */\nconst GmailId = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,100}$/))\n\nconst NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n\n/**\n * The practice account's own address: exactly one plain `local@domain` (the shape the send case's\n * `GooglePracticeAddress` seed takes). The fixtures use it as the sender, the only recipient, and\n * the event creator. A seed may set another one, but the send answers only while it is the\n * recorded `practice@example.test` (the recorded `sizeEstimate` of the sent message covers it).\n */\nexport const GoogleEmulatorPracticeAddress = Schema.String.check(\n Schema.isMaxLength(254),\n Schema.isPattern(\n /^[A-Za-z0-9_%+-]+(?:\\.[A-Za-z0-9_%+-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$/\n ),\n Schema.makeFilter(value => value.indexOf('@') <= 64)\n)\n\n/**\n * A stored Gmail message with the renderings a fixture records for it: `minimal` (true when a\n * fixture records `format=minimal`), and the `metadata` and `full` payloads (null when none is\n * recorded). The head fields are answered as stored.\n */\nexport const GoogleEmulatorGmailMessage = Schema.Struct({\n id: GmailId,\n threadId: GmailId,\n labelIds: Schema.Array(Schema.String),\n snippet: Schema.String,\n sizeEstimate: NonNegativeInt,\n historyId: Schema.String,\n internalDate: Schema.String,\n minimal: Schema.Boolean,\n metadataPayload: Schema.NullOr(JsonObject),\n fullPayload: Schema.NullOr(JsonObject)\n})\n\nexport type GoogleEmulatorGmailMessage = typeof GoogleEmulatorGmailMessage.Type\n\n/** A message a fixture only names in a listing (`{ id, threadId }`), with the labels it carries. */\nexport const GoogleEmulatorImpliedMessage = Schema.Struct({\n id: GmailId,\n threadId: GmailId,\n labelIds: Schema.Array(Schema.String)\n})\n\nexport type GoogleEmulatorImpliedMessage = typeof GoogleEmulatorImpliedMessage.Type\n\n/** A label created here (`POST .../labels`), answered as the label fixture records it. */\nexport const GoogleEmulatorGmailLabel = Schema.Struct({\n id: GmailId,\n name: Schema.String,\n messageListVisibility: Schema.String,\n labelListVisibility: Schema.String,\n type: Schema.String\n})\n\nexport type GoogleEmulatorGmailLabel = typeof GoogleEmulatorGmailLabel.Type\n\n/** An attachment Gmail stores apart from its message: `size` bytes as base64url `data`. */\nexport const GoogleEmulatorAttachment = Schema.Struct({\n messageId: GmailId,\n attachmentId: GmailId,\n size: NonNegativeInt,\n data: Schema.String\n})\n\nexport type GoogleEmulatorAttachment = typeof GoogleEmulatorAttachment.Type\n\n/** A draft created here and its current message. */\nexport const GoogleEmulatorDraft = Schema.Struct({\n id: Schema.String,\n messageId: GmailId\n})\n\nexport type GoogleEmulatorDraft = typeof GoogleEmulatorDraft.Type\n\n/** A calendar the events live in, with the fields a listing page answers. */\nexport const GoogleEmulatorCalendar = Schema.Struct({\n id: Schema.String,\n summary: Schema.String,\n timeZone: Schema.String,\n accessRole: Schema.String\n})\n\nexport type GoogleEmulatorCalendar = typeof GoogleEmulatorCalendar.Type\n\n/** An event boundary: exactly one of `dateTime` (with an optional `timeZone`) or `date`. */\nexport const GoogleEmulatorEventBoundary = Schema.Union([\n Schema.Struct({ dateTime: Schema.String, timeZone: Schema.optionalKey(Schema.String) }),\n Schema.Struct({ date: Schema.String })\n])\n\nexport type GoogleEmulatorEventBoundary = typeof GoogleEmulatorEventBoundary.Type\n\n/** A stored calendar event with its wire fields (creator and organizer derive from the state). */\nexport const GoogleEmulatorCalendarEvent = Schema.Struct({\n calendarId: Schema.String,\n id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{1,1024}$/)),\n etag: Schema.String,\n status: Schema.Literals(['confirmed', 'cancelled']),\n htmlLink: Schema.String,\n created: Schema.String,\n updated: Schema.String,\n summary: Schema.String,\n description: Schema.optionalKey(Schema.String),\n start: GoogleEmulatorEventBoundary,\n end: GoogleEmulatorEventBoundary,\n iCalUID: Schema.String,\n sequence: NonNegativeInt\n})\n\nexport type GoogleEmulatorCalendarEvent = typeof GoogleEmulatorCalendarEvent.Type\n\n/** A Drive file or folder with the fields the connector's `fields` selection answers. */\nexport const GoogleEmulatorDriveFile = Schema.Struct({\n id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{10,200}$/)),\n name: Schema.String,\n mimeType: Schema.String,\n starred: Schema.Boolean,\n trashed: Schema.Boolean,\n explicitlyTrashed: Schema.Boolean,\n trashedTime: Schema.optionalKey(Schema.String),\n parents: Schema.Array(Schema.String),\n spaces: Schema.Array(Schema.String),\n version: Schema.String,\n webViewLink: Schema.String,\n iconLink: Schema.String,\n hasThumbnail: Schema.Boolean,\n viewedByMe: Schema.Boolean,\n createdTime: Schema.String,\n modifiedTime: Schema.String,\n ownedByMe: Schema.Boolean,\n shared: Schema.Boolean,\n writersCanShare: Schema.Boolean,\n capabilities: JsonObject\n})\n\nexport type GoogleEmulatorDriveFile = typeof GoogleEmulatorDriveFile.Type\n\nconst Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\nconst Counters = Schema.Struct({\n /** Next created label number (`Label_<n>`). */\n nextLabelNumber: Counter,\n /** Next created draft number (`r-8<n, 18 digits>`). */\n nextDraftNumber: Counter,\n /** Next draft message number (`18f…d<n>`). */\n nextDraftMessageNumber: Counter,\n /** Next sent message number (`18f…e<n>`, and the `Message-ID` header). */\n nextSentNumber: Counter,\n /** Next created event number (`syntheticconformance<nnnn>`). */\n nextEventNumber: Counter,\n /** Next created folder number (`synthetic-conformance-folder-<nnnn>`). */\n nextFolderNumber: Counter\n})\n\n/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */\nexport const GoogleEmulatorStateSchema = Schema.Struct({\n practiceAddress: GoogleEmulatorPracticeAddress,\n messages: Schema.Array(GoogleEmulatorGmailMessage),\n impliedMessages: Schema.Array(GoogleEmulatorImpliedMessage),\n impliedLabelIds: Schema.Array(GmailId),\n labels: Schema.Array(GoogleEmulatorGmailLabel),\n attachments: Schema.Array(GoogleEmulatorAttachment),\n drafts: Schema.Array(GoogleEmulatorDraft),\n calendars: Schema.Array(GoogleEmulatorCalendar),\n events: Schema.Array(GoogleEmulatorCalendarEvent),\n files: Schema.Array(GoogleEmulatorDriveFile),\n impliedFolderIds: Schema.Array(Schema.String),\n counters: Counters\n})\n\n/**\n * The emulator state. The container is mutable (routes replace whole lists); every entity is\n * replaced, never edited in place.\n */\nexport type GoogleEmulatorState = {\n practiceAddress: string\n messages: ReadonlyArray<GoogleEmulatorGmailMessage>\n impliedMessages: ReadonlyArray<GoogleEmulatorImpliedMessage>\n impliedLabelIds: ReadonlyArray<string>\n labels: ReadonlyArray<GoogleEmulatorGmailLabel>\n attachments: ReadonlyArray<GoogleEmulatorAttachment>\n drafts: ReadonlyArray<GoogleEmulatorDraft>\n calendars: ReadonlyArray<GoogleEmulatorCalendar>\n events: ReadonlyArray<GoogleEmulatorCalendarEvent>\n files: ReadonlyArray<GoogleEmulatorDriveFile>\n impliedFolderIds: ReadonlyArray<string>\n counters: typeof Counters.Type\n}\n\n/** Account-variance profiles for the default seed. */\nexport const GoogleEmulatorProfile = Schema.Literals(['default', 'empty'])\n\nexport type GoogleEmulatorProfile = typeof GoogleEmulatorProfile.Type\n\n/**\n * A typed seed. Start from `profile` (default `'default'`, the fixture entities); every other key,\n * when given, replaces that part of the profile. Created labels and drafts are never seeded.\n */\nexport const GoogleEmulatorSeed = Schema.Struct({\n profile: Schema.optionalKey(GoogleEmulatorProfile),\n practiceAddress: Schema.optionalKey(GoogleEmulatorPracticeAddress),\n messages: Schema.optionalKey(Schema.Array(GoogleEmulatorGmailMessage)),\n impliedMessages: Schema.optionalKey(Schema.Array(GoogleEmulatorImpliedMessage)),\n impliedLabelIds: Schema.optionalKey(Schema.Array(GmailId)),\n attachments: Schema.optionalKey(Schema.Array(GoogleEmulatorAttachment)),\n calendars: Schema.optionalKey(Schema.Array(GoogleEmulatorCalendar)),\n events: Schema.optionalKey(Schema.Array(GoogleEmulatorCalendarEvent)),\n files: Schema.optionalKey(Schema.Array(GoogleEmulatorDriveFile)),\n impliedFolderIds: Schema.optionalKey(Schema.Array(Schema.String))\n})\n\nexport type GoogleEmulatorSeed = typeof GoogleEmulatorSeed.Type\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeSeedInput = Schema.decodeUnknownResult(GoogleEmulatorSeed, strict)\n\nconst decodeStateInput = Schema.decodeUnknownResult(GoogleEmulatorStateSchema, strict)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n// Minted forms (reserved: a seed may not use them).\n\nexport const pad = (value: number, width: number): string => String(value).padStart(width, '0')\n\n/** A minted message id: `18f`, then `d<n>` (draft) or `e<n>` (sent), zero-padded to 16. */\nexport const mintedMessageId = (kind: 'd' | 'e', number: number): string =>\n `18f${`${kind}${number}`.padStart(13, '0')}`\n\nexport const mintedMessageIdPattern = /^18f0*[de]\\d+$/\n\n/** A minted draft id: `r-8` and 18 digits (`r-8000000000000000001` first). */\nexport const mintedDraftId = (number: number): string => `r-8${pad(number, 18)}`\n\nexport const mintedEventId = (number: number): string => `syntheticconformance${pad(number, 4)}`\n\nexport const mintedEventIdPattern = /^syntheticconformance\\d+$/\n\nexport const mintedFolderId = (number: number): string =>\n `synthetic-conformance-folder-${pad(number, 4)}`\n\nexport const mintedFolderIdPattern = /^synthetic-conformance-folder-\\d+$/\n\n/** The label number the label fixture records for the first created label. */\nconst firstLabelNumber = 9101\n\n/** The highest label number a created (minted) label id may carry: `Label_999999999`. */\nexport const lastLabelNumber = 999_999_999\n\n/**\n * The minted label id form, `Label_<1 to 999999999>` without leading zeros. A seeded label id of\n * the `Label_<digits>` shape must have this form, so the created-label counter, which starts above\n * every seeded label number, never mints an id a seed holds.\n */\nexport const mintedLabelIdPattern = /^Label_([1-9]\\d{0,8})$/\n\nconst labelDigitsPattern = /^Label_\\d+$/\n\n/** A Gmail system label (upper case), as opposed to a user label id such as `Label_9001`. */\nexport const isSystemLabel = (id: string): boolean => /^[A-Z][A-Z_]*$/.test(id)\n\n// Default entities, copied from the fixtures and `googleConformanceFixtureSeeds`.\n\n/** The practice account (`practice@example.test`) of every fixture. */\nexport const googleEmulatorDefaultPracticeAddress = 'practice@example.test'\n\n/** Base64url without padding of UTF-8 text, as the fixtures encode message parts. */\nexport const base64UrlOfText = (text: string): string => {\n let binary = ''\n\n for (const byte of new TextEncoder().encode(text)) {\n binary += String.fromCharCode(byte)\n }\n\n return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '')\n}\n\n/** The seeded work message (`format=minimal`, as the label and trash fixtures read it). */\nconst workMessage: GoogleEmulatorGmailMessage = {\n id: '18f00000000000b1',\n threadId: '18f00000000000b1',\n labelIds: ['INBOX', 'IMPORTANT'],\n snippet: 'Synthetic practice message.',\n sizeEstimate: 2048,\n historyId: '900001',\n internalDate: recordedInternalDate,\n minimal: true,\n metadataPayload: null,\n fullPayload: null\n}\n\nconst attachmentText = 'Synthetic practice message with an attachment.'\n\nconst attachmentId = 'ANGjdJ_synthetic_attachment_0001'\n\n/** The seeded attachment message (`format=full`, as the attachment fixture reads it). */\nconst attachmentMessage: GoogleEmulatorGmailMessage = {\n id: '18f00000000000a1',\n threadId: '18f00000000000a1',\n labelIds: ['INBOX'],\n snippet: attachmentText,\n sizeEstimate: 4096,\n historyId: '900010',\n internalDate: recordedInternalDate,\n minimal: false,\n metadataPayload: null,\n fullPayload: {\n partId: '',\n mimeType: 'multipart/mixed',\n filename: '',\n headers: [\n { name: 'Subject', value: 'Synthetic practice attachment' },\n { name: 'Content-Type', value: 'multipart/mixed; boundary=\"synthetic\"' }\n ],\n body: { size: 0 },\n parts: [\n {\n partId: '0',\n mimeType: 'text/plain',\n filename: '',\n headers: [{ name: 'Content-Type', value: 'text/plain; charset=\"UTF-8\"' }],\n body: { size: attachmentText.length, data: base64UrlOfText(attachmentText) }\n },\n {\n partId: '1',\n mimeType: 'application/octet-stream',\n filename: 'synthetic.bin',\n headers: [\n { name: 'Content-Type', value: 'application/octet-stream; name=\"synthetic.bin\"' },\n { name: 'Content-Disposition', value: 'attachment; filename=\"synthetic.bin\"' },\n { name: 'Content-Transfer-Encoding', value: 'base64' }\n ],\n body: { attachmentId, size: 6 }\n }\n ]\n }\n}\n\nconst pagingLabelId = 'Label_9001'\n\n/** The five messages the paging fixture names (listing entries only). */\nconst pagingMessages: ReadonlyArray<GoogleEmulatorImpliedMessage> = [1, 2, 3, 4, 5].map(index => ({\n id: `18f00000000000c${index}`,\n threadId: `18f00000000000c${index}`,\n labelIds: [pagingLabelId]\n}))\n\nconst calendarId = 'practice-calendar@example.test'\n\nconst practiceCalendar: GoogleEmulatorCalendar = {\n id: calendarId,\n summary: 'Synthetic practice calendar',\n timeZone: 'UTC',\n accessRole: 'owner'\n}\n\nconst seededAt = '2026-09-30T12:00:00.000Z'\n\n/** An event with the fixture's derived fields (`etag`, `htmlLink`, `iCalUID`). */\nexport const eventFields = (\n id: string\n): Pick<GoogleEmulatorCalendarEvent, 'etag' | 'htmlLink' | 'iCalUID'> => ({\n etag: `\"3${id.length}000000000000\"`,\n htmlLink: `https://www.google.com/calendar/event?eid=${id}`,\n iCalUID: `${id}@google.com`\n})\n\nconst rangeEvent = (\n id: string,\n summary: string,\n start: GoogleEmulatorEventBoundary,\n end: GoogleEmulatorEventBoundary\n): GoogleEmulatorCalendarEvent => ({\n calendarId,\n id,\n ...eventFields(id),\n status: 'confirmed',\n created: seededAt,\n updated: seededAt,\n summary,\n start,\n end,\n sequence: 0\n})\n\nconst utc = (dateTime: string): GoogleEmulatorEventBoundary => ({ dateTime, timeZone: 'UTC' })\n\n/** The five events of the range fixture (one all-day, two crossing the range edges). */\nconst rangeEvents: ReadonlyArray<GoogleEmulatorCalendarEvent> = [\n rangeEvent(\n 'syntheticrange0001',\n 'Synthetic overnight',\n utc('2026-08-31T23:30:00Z'),\n utc('2026-09-01T00:30:00Z')\n ),\n rangeEvent(\n 'syntheticrange0002',\n 'Synthetic standup',\n utc('2026-09-02T09:00:00Z'),\n utc('2026-09-02T09:15:00Z')\n ),\n rangeEvent(\n 'syntheticrange0003',\n 'Synthetic review',\n utc('2026-09-03T14:00:00Z'),\n utc('2026-09-03T15:00:00Z')\n ),\n rangeEvent(\n 'syntheticrange0004',\n 'Synthetic all-day',\n { date: '2026-09-05' },\n { date: '2026-09-06' }\n ),\n rangeEvent(\n 'syntheticrange0005',\n 'Synthetic late',\n utc('2026-09-07T23:00:00Z'),\n utc('2026-09-08T01:00:00Z')\n )\n]\n\n/** The folder mime type Drive answers. */\nexport const driveFolderMimeType = 'application/vnd.google-apps.folder'\n\nconst practiceFolderId = 'synthetic-practice-folder-0001'\n\n/** A Drive item with the fixture's derived and constant fields. */\nexport const driveFile = (fields: {\n readonly id: string\n readonly name: string\n readonly mimeType: string\n readonly parents: ReadonlyArray<string>\n readonly createdTime: string\n}): GoogleEmulatorDriveFile => ({\n id: fields.id,\n name: fields.name,\n mimeType: fields.mimeType,\n starred: false,\n trashed: false,\n explicitlyTrashed: false,\n parents: [...fields.parents],\n spaces: ['drive'],\n version: '3',\n webViewLink: `https://drive.google.com/drive/folders/${fields.id}`,\n iconLink: `https://drive-thirdparty.googleusercontent.com/16/type/${fields.mimeType}`,\n hasThumbnail: false,\n viewedByMe: true,\n createdTime: fields.createdTime,\n modifiedTime: fields.createdTime,\n ownedByMe: true,\n shared: false,\n writersCanShare: true,\n capabilities: {\n canAddChildren: fields.mimeType === driveFolderMimeType,\n canDelete: true,\n canEdit: true,\n canRename: true,\n canTrash: true,\n canUntrash: true\n }\n})\n\nconst child = (id: string, name: string, mimeType: string) =>\n driveFile({ id, name, mimeType, parents: [practiceFolderId], createdTime: seededAt })\n\n/** The five children of the practice folder (the first is the seeded practice file). */\nconst practiceChildren: ReadonlyArray<GoogleEmulatorDriveFile> = [\n child('synthetic-practice-file-0001', 'Synthetic practice notes.txt', 'text/plain'),\n child('synthetic-practice-file-0002', 'Synthetic practice sheet.csv', 'text/csv'),\n child('synthetic-practice-file-0003', 'Synthetic practice image.png', 'image/png'),\n child('synthetic-practice-folder-0002', 'Synthetic practice subfolder', driveFolderMimeType),\n child('synthetic-practice-file-0004', 'Synthetic practice report.pdf', 'application/pdf')\n]\n\ntype ProfileEntities = Omit<GoogleEmulatorState, 'counters' | 'labels' | 'drafts'>\n\nconst profileEntities = (profile: GoogleEmulatorProfile): ProfileEntities =>\n profile === 'default'\n ? {\n practiceAddress: googleEmulatorDefaultPracticeAddress,\n messages: [attachmentMessage, workMessage],\n impliedMessages: pagingMessages,\n impliedLabelIds: [pagingLabelId],\n attachments: [{ messageId: attachmentMessage.id, attachmentId, size: 6, data: '-_-_Pj_-' }],\n calendars: [practiceCalendar],\n events: rangeEvents,\n files: practiceChildren,\n impliedFolderIds: [practiceFolderId]\n }\n : {\n practiceAddress: googleEmulatorDefaultPracticeAddress,\n messages: [],\n impliedMessages: [],\n impliedLabelIds: [],\n attachments: [],\n calendars: [],\n events: [],\n files: [],\n impliedFolderIds: []\n }\n\nconst duplicate = (values: ReadonlyArray<string>): string | undefined =>\n values.find((value, index) => values.indexOf(value) !== index)\n\n/** Integrity problems a decoded seed can still have (duplicates, minted forms, references). */\nconst seedProblem = (entities: ProfileEntities): string | undefined => {\n const messageIds = [\n ...entities.messages.map(message => message.id),\n ...entities.impliedMessages.map(message => message.id)\n ]\n\n const fileIds = entities.files.map(file => file.id)\n\n const duplicates: ReadonlyArray<readonly [string, string | undefined]> = [\n ['message id', duplicate(messageIds)],\n ['label id', duplicate(entities.impliedLabelIds)],\n [\n 'attachment',\n duplicate(entities.attachments.map(item => `${item.messageId} ${item.attachmentId}`))\n ],\n ['calendar id', duplicate(entities.calendars.map(calendar => calendar.id))],\n ['event', duplicate(entities.events.map(event => `${event.calendarId} ${event.id}`))],\n ['Drive id', duplicate([...fileIds, ...entities.impliedFolderIds])]\n ]\n\n for (const [label, value] of duplicates) {\n if (value !== undefined) return `duplicate ${label} ${value}`\n }\n\n const minted =\n messageIds.find(id => mintedMessageIdPattern.test(id)) ??\n entities.events.map(event => event.id).find(id => mintedEventIdPattern.test(id)) ??\n [...fileIds, ...entities.impliedFolderIds].find(id => mintedFolderIdPattern.test(id))\n\n if (minted !== undefined) return `seed id ${minted} uses a form reserved for minted ids`\n\n // A thread id shares the minted message id forms (a created draft is its own thread).\n const mintedThread = [...entities.messages, ...entities.impliedMessages]\n .map(message => message.threadId)\n .find(id => mintedMessageIdPattern.test(id))\n\n if (mintedThread !== undefined) {\n return `seed thread id ${mintedThread} uses a form reserved for minted ids`\n }\n\n const outsideRange = [\n ...entities.impliedLabelIds,\n ...[...entities.messages, ...entities.impliedMessages].flatMap(message => message.labelIds)\n ].find(id => labelDigitsPattern.test(id) && !mintedLabelIdPattern.test(id))\n\n if (outsideRange !== undefined) {\n return `seed label id ${outsideRange} is outside the Label_<1 to ${lastLabelNumber}> form`\n }\n\n const knownLabels = new Set(entities.impliedLabelIds)\n\n for (const message of [...entities.messages, ...entities.impliedMessages]) {\n const unknown = message.labelIds.find(id => !isSystemLabel(id) && !knownLabels.has(id))\n\n if (unknown !== undefined) {\n return `message ${message.id} carries label ${unknown}, which is not in impliedLabelIds`\n }\n }\n\n const attachment = entities.attachments.find(\n item => !entities.messages.some(message => message.id === item.messageId)\n )\n\n if (attachment !== undefined) {\n return `attachment ${attachment.attachmentId} references missing message ${attachment.messageId}`\n }\n\n const event = entities.events.find(\n item => !entities.calendars.some(calendar => calendar.id === item.calendarId)\n )\n\n if (event !== undefined) {\n return `event ${event.id} references missing calendar ${event.calendarId}`\n }\n\n const folders = new Set([\n ...entities.files.filter(file => file.mimeType === driveFolderMimeType).map(file => file.id),\n ...entities.impliedFolderIds\n ])\n\n const orphan = entities.files.find(file => file.parents.some(parent => !folders.has(parent)))\n\n return orphan === undefined\n ? undefined\n : `Drive item ${orphan.id} references a missing parent folder`\n}\n\n/** Build the emulator state for a decoded seed; a string is an integrity problem. */\nconst stateFromSeed = (seed: GoogleEmulatorSeed): GoogleEmulatorState | string => {\n const profile = profileEntities(seed.profile ?? 'default')\n\n const entities: ProfileEntities = {\n practiceAddress: seed.practiceAddress ?? profile.practiceAddress,\n messages: seed.messages ?? profile.messages,\n impliedMessages: seed.impliedMessages ?? profile.impliedMessages,\n impliedLabelIds: seed.impliedLabelIds ?? profile.impliedLabelIds,\n attachments: seed.attachments ?? profile.attachments,\n calendars: seed.calendars ?? profile.calendars,\n events: seed.events ?? profile.events,\n files: seed.files ?? profile.files,\n impliedFolderIds: seed.impliedFolderIds ?? profile.impliedFolderIds\n }\n\n const problem = seedProblem(entities)\n\n if (problem !== undefined) return problem\n\n // Above every seeded label number and never below the recorded one (each at most nine digits,\n // checked in `seedProblem`).\n const highestLabel = Math.max(\n firstLabelNumber - 1,\n ...entities.impliedLabelIds.map(id => Number(mintedLabelIdPattern.exec(id)?.[1] ?? 0))\n )\n\n return {\n ...entities,\n labels: [],\n drafts: [],\n counters: {\n nextLabelNumber: highestLabel + 1,\n nextDraftNumber: 1,\n nextDraftMessageNumber: 1,\n nextSentNumber: 1,\n nextEventNumber: 1,\n nextFolderNumber: 1\n }\n }\n}\n\n/** Decode and build a seed; a string is the reason it is invalid. */\nexport const buildSeedState = (input: unknown): GoogleEmulatorState | string => {\n const decoded = decodeSeedInput(input)\n\n return Result.isFailure(decoded)\n ? issueMessage(decoded.failure.issue)\n : stateFromSeed(decoded.success)\n}\n\n/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */\nexport const decodeState = (input: unknown): GoogleEmulatorState | string => {\n const decoded = decodeStateInput(input)\n\n return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EmulatorEvidence, EmulatorRouteEvidence, emulatorEvidenceHeader } from "./route-evidence.mjs";
|
|
2
|
+
import { StatefulCoverage, StatefulEmulatorApi, StatefulFault, StatefulFaultMatch, StatefulFaultState, StatefulInputKind, StatefulLedgerEntry } from "./stateful-emulator.mjs";
|
|
3
|
+
import { GoogleEmulatorAttachment, GoogleEmulatorCalendar, GoogleEmulatorCalendarEvent, GoogleEmulatorDraft, GoogleEmulatorDriveFile, GoogleEmulatorEventBoundary, GoogleEmulatorGmailLabel, GoogleEmulatorGmailMessage, GoogleEmulatorImpliedMessage, GoogleEmulatorPracticeAddress, GoogleEmulatorProfile, GoogleEmulatorSeed, GoogleEmulatorState, GoogleEmulatorStateSchema } from "./google/state.mjs";
|
|
4
|
+
import { GoogleEmulatorDrills, googleEmulatorApisOrigin, googleEmulatorGmailOrigin } from "./google/shared.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/google.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Route evidence manifest: every emulated route, whether it writes, and the conformance cases
|
|
9
|
+
* whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
|
|
10
|
+
* by construction (both come from one route table). Every route cites at least one case.
|
|
11
|
+
*/
|
|
12
|
+
declare const googleEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence>;
|
|
13
|
+
/**
|
|
14
|
+
* Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
|
|
15
|
+
* `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
|
|
16
|
+
* manifest (another value is rejected when the fault is added).
|
|
17
|
+
*/
|
|
18
|
+
declare const GoogleFaultMatch: import("effect/Schema").Struct<{
|
|
19
|
+
readonly method: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
20
|
+
readonly path: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
21
|
+
readonly route: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
22
|
+
}>;
|
|
23
|
+
type GoogleFaultMatch = typeof StatefulFaultMatch.Type;
|
|
24
|
+
/**
|
|
25
|
+
* A status fault (400-599): answer matching requests with this status, headers, and body instead
|
|
26
|
+
* of the route's write, so nothing is written. The body defaults to
|
|
27
|
+
* `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
|
|
28
|
+
* Only a request the emulator would answer reaches a fault: a request that is not emulated, by
|
|
29
|
+
* its shape or by the state, never uses one up.
|
|
30
|
+
*/
|
|
31
|
+
declare const GoogleFault: import("effect/Schema").Struct<{
|
|
32
|
+
readonly kind: import("effect/Schema").Literal<"status">;
|
|
33
|
+
readonly status: import("effect/Schema").Int;
|
|
34
|
+
readonly headers: import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").String>>;
|
|
35
|
+
readonly body: import("effect/Schema").optionalKey<import("effect/Schema").Codec<import("effect/Schema").Json, import("effect/Schema").Json, never, never>>;
|
|
36
|
+
readonly match: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
|
|
37
|
+
readonly method: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
38
|
+
readonly path: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
39
|
+
readonly route: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
40
|
+
}>>;
|
|
41
|
+
readonly count: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
42
|
+
}>;
|
|
43
|
+
type GoogleFault = StatefulFault;
|
|
44
|
+
type GoogleFaultState = StatefulFaultState;
|
|
45
|
+
type GoogleLedgerEntry = StatefulLedgerEntry;
|
|
46
|
+
type GoogleCoverage = StatefulCoverage;
|
|
47
|
+
declare const GoogleEmulatorInputInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
48
|
+
readonly _tag: "GoogleEmulatorInputInvalid";
|
|
49
|
+
} & Readonly<A>;
|
|
50
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
51
|
+
declare class GoogleEmulatorInputInvalid extends GoogleEmulatorInputInvalid_base<{
|
|
52
|
+
readonly input: StatefulInputKind;
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
}> {
|
|
55
|
+
get message(): string;
|
|
56
|
+
}
|
|
57
|
+
type GoogleEmulatorOptions = {
|
|
58
|
+
/** Typed seed; defaults to the fixture entities (`profile: 'default'`). */readonly seed?: GoogleEmulatorSeed;
|
|
59
|
+
/**
|
|
60
|
+
* Clock in epoch milliseconds (created and updated event times, Drive folder times, and the
|
|
61
|
+
* sent message's `Date` header). Defaults to `Date.now`.
|
|
62
|
+
*/
|
|
63
|
+
readonly now?: () => number; /** Drill knobs (tests only): make the emulator disagree with one conformance case. */
|
|
64
|
+
readonly drills?: GoogleEmulatorDrills;
|
|
65
|
+
};
|
|
66
|
+
type GoogleEmulator = StatefulEmulatorApi<GoogleEmulatorState, GoogleEmulatorSeed>;
|
|
67
|
+
/**
|
|
68
|
+
* Create a stateful Google emulator on the `@emulators/core` custom runtime. Each call has its
|
|
69
|
+
* own state, ledger, faults, and page tokens. Rejects with `GoogleEmulatorInputInvalid` for an
|
|
70
|
+
* invalid seed or option. See `src/stateful-emulator.ts` for the request precedence.
|
|
71
|
+
*/
|
|
72
|
+
declare const makeGoogleEmulator: (options?: GoogleEmulatorOptions) => Promise<GoogleEmulator>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { type EmulatorEvidence, type EmulatorRouteEvidence, GoogleCoverage, GoogleEmulator, GoogleEmulatorAttachment, GoogleEmulatorCalendar, GoogleEmulatorCalendarEvent, GoogleEmulatorDraft, type GoogleEmulatorDrills, GoogleEmulatorDriveFile, GoogleEmulatorEventBoundary, GoogleEmulatorGmailLabel, GoogleEmulatorGmailMessage, GoogleEmulatorImpliedMessage, GoogleEmulatorInputInvalid, GoogleEmulatorOptions, GoogleEmulatorPracticeAddress, GoogleEmulatorProfile, GoogleEmulatorSeed, type GoogleEmulatorState, GoogleEmulatorStateSchema, GoogleFault, GoogleFaultMatch, GoogleFaultState, GoogleLedgerEntry, emulatorEvidenceHeader, googleEmulatorApisOrigin, googleEmulatorGmailOrigin, googleEmulatorRoutes, makeGoogleEmulator };
|
|
75
|
+
//# sourceMappingURL=google.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.d.mts","names":[],"sources":["../src/google.ts"],"mappings":";;;;;;;;AAmKsE;AAQtE;;cARa,oBAAA,EAAsB,aAAa,CAAC,qBAAA;;;;;;cAQpC,gBAAA,0BAAgB,MAAA;EAAA;;;;KAEjB,gBAAA,UAA0B,kBAAA,CAAmB,IAAI;;;AAA7D;;;;AAA6D;cAShD,WAAA,0BAAW,MAAA;EAAA;;;;;;;;;;;KAEZ,WAAA,GAAc,aAAa;AAAA,KAE3B,gBAAA,GAAmB,kBAAkB;AAAA,KAErC,iBAAA,GAAoB,mBAAmB;AAAA,KAEvC,cAAA,GAAiB,gBAAgB;AAAA,cAAA,+BAAA;;;;cAGhC,0BAAA,SAAmC,+BAAA;EAAA,SACrC,KAAA,EAAO,iBAAA;EAAA,SACP,MAAA;AAAA;EAAA,IAEI,OAAA,CAAA;AAAA;AAAA,KAKH,qBAAA;sFAED,IAAA,GAAO,kBAAA;;;;;WAKP,GAAA;WAEA,MAAA,GAAS,oBAAoB;AAAA;AAAA,KAG5B,cAAA,GAAiB,mBAAA,CAAoB,mBAAA,EAAqB,kBAAA;;;AA9B/B;AAEvC;;cA6Ca,kBAAA,GACX,OAAA,GAAS,qBAAA,KACR,OAAA,CAAQ,cAAA"}
|