@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,625 @@
|
|
|
1
|
+
import { Result, Struct } from "effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
//#region src/microsoft/state.ts
|
|
4
|
+
/**
|
|
5
|
+
* Microsoft Graph emulator state: the typed entities, the seed input, the default seed, and the
|
|
6
|
+
* profiles (internal; re-exported by `src/microsoft.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Entity shapes and the default entities follow the synthetic Microsoft conformance fixtures,
|
|
9
|
+
* copied as data (the same mailbox, calendar, event, message, folder, drive, and item ids as
|
|
10
|
+
* `microsoftConformanceFixtureSeeds`), never imported from SDK code. Where no fixture records a
|
|
11
|
+
* value, the default is synthesized and says so below: the user id, the well-known mail folders
|
|
12
|
+
* other than Drafts and Deleted Items, the attachment message itself, and the drive root and
|
|
13
|
+
* `Sources` folder have no fixture.
|
|
14
|
+
*
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
17
|
+
const Timestamp = Schema.String;
|
|
18
|
+
/** A recipient as stored: `name` falls back to the address when Graph has none. */
|
|
19
|
+
const MicrosoftEmulatorRecipient = Schema.Struct({
|
|
20
|
+
name: Schema.NullOr(Schema.String),
|
|
21
|
+
address: Schema.String
|
|
22
|
+
});
|
|
23
|
+
/** The mailbox owner every `/users/{userId}` route answers for (id, `mail`, or UPN). */
|
|
24
|
+
const MicrosoftEmulatorUser = Schema.Struct({
|
|
25
|
+
id: Schema.String,
|
|
26
|
+
displayName: Schema.String,
|
|
27
|
+
mail: Schema.String,
|
|
28
|
+
userPrincipalName: Schema.String
|
|
29
|
+
});
|
|
30
|
+
const WellKnownFolder = Schema.Literals([
|
|
31
|
+
"inbox",
|
|
32
|
+
"drafts",
|
|
33
|
+
"deleteditems"
|
|
34
|
+
]);
|
|
35
|
+
/** A mail folder; `wellKnownName` lets routes address it by name (`deleteditems`). */
|
|
36
|
+
const MicrosoftEmulatorMailFolder = Schema.Struct({
|
|
37
|
+
id: Schema.String,
|
|
38
|
+
displayName: Schema.String,
|
|
39
|
+
wellKnownName: Schema.NullOr(WellKnownFolder)
|
|
40
|
+
});
|
|
41
|
+
const BodyContentType = Schema.Literals(["text", "html"]);
|
|
42
|
+
const messageFields = {
|
|
43
|
+
/** The immutable id (`Prefer: IdType="ImmutableId"`): stable across moves. */
|
|
44
|
+
id: Schema.String,
|
|
45
|
+
parentFolderId: Schema.String,
|
|
46
|
+
subject: Schema.String,
|
|
47
|
+
bodyContentType: BodyContentType,
|
|
48
|
+
bodyContent: Schema.String,
|
|
49
|
+
from: Schema.NullOr(MicrosoftEmulatorRecipient),
|
|
50
|
+
sender: Schema.NullOr(MicrosoftEmulatorRecipient),
|
|
51
|
+
toRecipients: Schema.Array(MicrosoftEmulatorRecipient),
|
|
52
|
+
ccRecipients: Schema.Array(MicrosoftEmulatorRecipient),
|
|
53
|
+
bccRecipients: Schema.Array(MicrosoftEmulatorRecipient),
|
|
54
|
+
replyTo: Schema.Array(MicrosoftEmulatorRecipient),
|
|
55
|
+
isRead: Schema.Boolean,
|
|
56
|
+
isDraft: Schema.Boolean,
|
|
57
|
+
importance: Schema.Literals([
|
|
58
|
+
"low",
|
|
59
|
+
"normal",
|
|
60
|
+
"high"
|
|
61
|
+
]),
|
|
62
|
+
flagStatus: Schema.Literals([
|
|
63
|
+
"notFlagged",
|
|
64
|
+
"flagged",
|
|
65
|
+
"complete"
|
|
66
|
+
]),
|
|
67
|
+
categories: Schema.Array(Schema.String),
|
|
68
|
+
/** Answered as stored (not derived from the attachments; no fixture shows the rule). */
|
|
69
|
+
hasAttachments: Schema.Boolean,
|
|
70
|
+
createdDateTime: Timestamp,
|
|
71
|
+
lastModifiedDateTime: Timestamp,
|
|
72
|
+
receivedDateTime: Timestamp,
|
|
73
|
+
sentDateTime: Timestamp,
|
|
74
|
+
conversationId: Schema.String,
|
|
75
|
+
internetMessageId: Schema.String,
|
|
76
|
+
changeKey: Schema.String
|
|
77
|
+
};
|
|
78
|
+
/** A stored message. Every message is addressed by its immutable id. */
|
|
79
|
+
const MicrosoftEmulatorMessage = Schema.Struct(messageFields);
|
|
80
|
+
const attachmentFields = {
|
|
81
|
+
id: Schema.String,
|
|
82
|
+
/** Immutable id of the owning message. */
|
|
83
|
+
messageId: Schema.String,
|
|
84
|
+
name: Schema.String,
|
|
85
|
+
contentType: Schema.String,
|
|
86
|
+
size: Schema.Int,
|
|
87
|
+
isInline: Schema.Boolean,
|
|
88
|
+
contentId: Schema.NullOr(Schema.String),
|
|
89
|
+
contentLocation: Schema.NullOr(Schema.String),
|
|
90
|
+
contentBytes: Schema.String,
|
|
91
|
+
lastModifiedDateTime: Timestamp
|
|
92
|
+
};
|
|
93
|
+
/** A stored file attachment (`#microsoft.graph.fileAttachment`). */
|
|
94
|
+
const MicrosoftEmulatorAttachment = Schema.Struct(attachmentFields);
|
|
95
|
+
const MicrosoftEmulatorCalendar = Schema.Struct({
|
|
96
|
+
id: Schema.String,
|
|
97
|
+
name: Schema.String
|
|
98
|
+
});
|
|
99
|
+
/** Graph local date-time in UTC with seven fractional digits (`2026-09-23T12:00:00.0000000`). */
|
|
100
|
+
const microsoftEmulatorDateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}$/;
|
|
101
|
+
const UtcDateTime = Schema.String.check(Schema.isPattern(microsoftEmulatorDateTimePattern));
|
|
102
|
+
const eventFields = {
|
|
103
|
+
id: Schema.String,
|
|
104
|
+
calendarId: Schema.String,
|
|
105
|
+
subject: Schema.String,
|
|
106
|
+
bodyContentType: BodyContentType,
|
|
107
|
+
bodyContent: Schema.String,
|
|
108
|
+
/** Start in UTC, seven fractional digits. */
|
|
109
|
+
start: UtcDateTime,
|
|
110
|
+
/** End in UTC, seven fractional digits. */
|
|
111
|
+
end: UtcDateTime,
|
|
112
|
+
isCancelled: Schema.Boolean,
|
|
113
|
+
isReminderOn: Schema.Boolean,
|
|
114
|
+
showAs: Schema.Literals([
|
|
115
|
+
"free",
|
|
116
|
+
"tentative",
|
|
117
|
+
"busy",
|
|
118
|
+
"oof",
|
|
119
|
+
"workingElsewhere",
|
|
120
|
+
"unknown"
|
|
121
|
+
]),
|
|
122
|
+
changeKey: Schema.String,
|
|
123
|
+
createdDateTime: Timestamp,
|
|
124
|
+
lastModifiedDateTime: Timestamp
|
|
125
|
+
};
|
|
126
|
+
/** A stored single-instance calendar event (no attendees, no recurrence). */
|
|
127
|
+
const MicrosoftEmulatorEvent = Schema.Struct(eventFields);
|
|
128
|
+
/** The one emulated drive. */
|
|
129
|
+
const MicrosoftEmulatorDrive = Schema.Struct({
|
|
130
|
+
id: Schema.String,
|
|
131
|
+
driveType: Schema.Literals(["business", "personal"]),
|
|
132
|
+
rootId: Schema.String
|
|
133
|
+
});
|
|
134
|
+
const driveItemFields = {
|
|
135
|
+
id: Schema.String,
|
|
136
|
+
/** `null` only for the drive root. */
|
|
137
|
+
parentId: Schema.NullOr(Schema.String),
|
|
138
|
+
name: Schema.String,
|
|
139
|
+
kind: Schema.Literals(["folder", "file"]),
|
|
140
|
+
/** File size in bytes; folders report the sum of their contents instead. */
|
|
141
|
+
size: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
142
|
+
mimeType: Schema.NullOr(Schema.String),
|
|
143
|
+
quickXorHash: Schema.NullOr(Schema.String),
|
|
144
|
+
createdDateTime: Timestamp,
|
|
145
|
+
lastModifiedDateTime: Timestamp
|
|
146
|
+
};
|
|
147
|
+
/** A stored drive item (file or folder). Deleted items are removed (no recycle bin). */
|
|
148
|
+
const MicrosoftEmulatorDriveItem = Schema.Struct(driveItemFields);
|
|
149
|
+
const Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
|
|
150
|
+
const Counters = Schema.Struct({
|
|
151
|
+
/** Next number in created event ids (`AAMkAGI2-synthetic-event-0101=`). */
|
|
152
|
+
nextEventNumber: Counter,
|
|
153
|
+
/** Next number in created message immutable ids. */
|
|
154
|
+
nextMessageNumber: Counter,
|
|
155
|
+
/** Next change key number (message and event writes). */
|
|
156
|
+
nextChangeKeyNumber: Counter,
|
|
157
|
+
/** Next number in created drive item ids. */
|
|
158
|
+
nextItemNumber: Counter
|
|
159
|
+
});
|
|
160
|
+
/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */
|
|
161
|
+
const MicrosoftEmulatorStateSchema = Schema.Struct({
|
|
162
|
+
user: MicrosoftEmulatorUser,
|
|
163
|
+
mailFolders: Schema.Array(MicrosoftEmulatorMailFolder),
|
|
164
|
+
messages: Schema.Array(MicrosoftEmulatorMessage),
|
|
165
|
+
attachments: Schema.Array(MicrosoftEmulatorAttachment),
|
|
166
|
+
calendars: Schema.Array(MicrosoftEmulatorCalendar),
|
|
167
|
+
events: Schema.Array(MicrosoftEmulatorEvent),
|
|
168
|
+
drive: MicrosoftEmulatorDrive,
|
|
169
|
+
driveItems: Schema.Array(MicrosoftEmulatorDriveItem),
|
|
170
|
+
counters: Counters
|
|
171
|
+
});
|
|
172
|
+
/** A seeded message: `id`, `parentFolderId`, and `subject` are required. */
|
|
173
|
+
const MicrosoftEmulatorMessageSeed = Schema.Struct(Struct.mapOmit(messageFields, [
|
|
174
|
+
"id",
|
|
175
|
+
"parentFolderId",
|
|
176
|
+
"subject"
|
|
177
|
+
], Schema.optionalKey));
|
|
178
|
+
/** A seeded attachment: `id`, `messageId`, and `name` are required. */
|
|
179
|
+
const MicrosoftEmulatorAttachmentSeed = Schema.Struct(Struct.mapOmit(attachmentFields, [
|
|
180
|
+
"id",
|
|
181
|
+
"messageId",
|
|
182
|
+
"name"
|
|
183
|
+
], Schema.optionalKey));
|
|
184
|
+
/**
|
|
185
|
+
* A seeded event: `id`, `calendarId`, `subject`, `start`, and `end` are required. `start` and
|
|
186
|
+
* `end` are UTC: either an ISO instant ending in `Z` or a seven-digit local date-time.
|
|
187
|
+
*/
|
|
188
|
+
const MicrosoftEmulatorEventSeed = Schema.Struct({
|
|
189
|
+
...Struct.mapOmit(eventFields, [
|
|
190
|
+
"id",
|
|
191
|
+
"calendarId",
|
|
192
|
+
"subject",
|
|
193
|
+
"start",
|
|
194
|
+
"end"
|
|
195
|
+
], Schema.optionalKey),
|
|
196
|
+
start: Schema.String,
|
|
197
|
+
end: Schema.String
|
|
198
|
+
});
|
|
199
|
+
/** A seeded drive item: `id`, `parentId`, `name`, and `kind` are required. */
|
|
200
|
+
const MicrosoftEmulatorDriveItemSeed = Schema.Struct(Struct.mapOmit(driveItemFields, [
|
|
201
|
+
"id",
|
|
202
|
+
"parentId",
|
|
203
|
+
"name",
|
|
204
|
+
"kind"
|
|
205
|
+
], Schema.optionalKey));
|
|
206
|
+
/** Account-variance profiles for the default seed. */
|
|
207
|
+
const MicrosoftEmulatorProfile = Schema.Literals(["default", "empty"]);
|
|
208
|
+
/**
|
|
209
|
+
* A typed seed. Start from `profile` (default `'default'`, the fixture entities); every other
|
|
210
|
+
* key, when given, replaces that part of the profile.
|
|
211
|
+
*/
|
|
212
|
+
const MicrosoftEmulatorSeed = Schema.Struct({
|
|
213
|
+
profile: Schema.optionalKey(MicrosoftEmulatorProfile),
|
|
214
|
+
user: Schema.optionalKey(MicrosoftEmulatorUser),
|
|
215
|
+
mailFolders: Schema.optionalKey(Schema.Array(MicrosoftEmulatorMailFolder)),
|
|
216
|
+
messages: Schema.optionalKey(Schema.Array(MicrosoftEmulatorMessageSeed)),
|
|
217
|
+
attachments: Schema.optionalKey(Schema.Array(MicrosoftEmulatorAttachmentSeed)),
|
|
218
|
+
calendars: Schema.optionalKey(Schema.Array(MicrosoftEmulatorCalendar)),
|
|
219
|
+
events: Schema.optionalKey(Schema.Array(MicrosoftEmulatorEventSeed)),
|
|
220
|
+
drive: Schema.optionalKey(MicrosoftEmulatorDrive),
|
|
221
|
+
driveItems: Schema.optionalKey(Schema.Array(MicrosoftEmulatorDriveItemSeed))
|
|
222
|
+
});
|
|
223
|
+
const strict = { onExcessProperty: "error" };
|
|
224
|
+
const decodeSeedInput = Schema.decodeUnknownResult(MicrosoftEmulatorSeed, strict);
|
|
225
|
+
const decodeStateInput = Schema.decodeUnknownResult(MicrosoftEmulatorStateSchema, strict);
|
|
226
|
+
const issueMessage = (issue) => new Schema.SchemaError(issue).message;
|
|
227
|
+
const ticksPerMilli = BigInt(1e4);
|
|
228
|
+
const ticksPerSecond = BigInt(1e7);
|
|
229
|
+
const instantPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,7}))?(Z|[+-]\d{2}:\d{2})?$/;
|
|
230
|
+
/**
|
|
231
|
+
* Ticks of an ISO-8601 date-time. `zone` `'required'` needs `Z` or an offset; `'forbidden'`
|
|
232
|
+
* needs none (a Graph local date-time, read as UTC); `'optional'` reads a missing one as UTC.
|
|
233
|
+
*/
|
|
234
|
+
const parseInstant = (value, zone) => {
|
|
235
|
+
const match = instantPattern.exec(value);
|
|
236
|
+
if (match === null) return void 0;
|
|
237
|
+
const [, year, month, day, hour, minute, second, fraction = "", offset] = match;
|
|
238
|
+
if (zone === "required" && offset === void 0 || zone === "forbidden" && offset !== void 0) return;
|
|
239
|
+
const wholeMillis = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second));
|
|
240
|
+
const check = new Date(wholeMillis);
|
|
241
|
+
if (!Number.isFinite(wholeMillis) || check.getUTCFullYear() !== Number(year) || check.getUTCMonth() !== Number(month) - 1 || check.getUTCDate() !== Number(day) || check.getUTCHours() !== Number(hour) || check.getUTCMinutes() !== Number(minute)) return;
|
|
242
|
+
const offsetMinutes = offset === void 0 || offset === "Z" ? 0 : (offset.startsWith("-") ? -1 : 1) * (Number(offset.slice(1, 3)) * 60 + Number(offset.slice(4, 6)));
|
|
243
|
+
return BigInt(wholeMillis - offsetMinutes * 6e4) * ticksPerMilli + BigInt(fraction.padEnd(7, "0"));
|
|
244
|
+
};
|
|
245
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
246
|
+
/** A UTC local date-time with `digits` fractional digits (Graph uses seven). */
|
|
247
|
+
const formatLocalDateTime = (ticks, digits) => {
|
|
248
|
+
const millis = Number(ticks / ticksPerMilli);
|
|
249
|
+
const date = new Date(millis);
|
|
250
|
+
const fraction = pad(Number((ticks % ticksPerSecond + ticksPerSecond) % ticksPerSecond), 7);
|
|
251
|
+
const whole = `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}T${pad(date.getUTCHours(), 2)}:${pad(date.getUTCMinutes(), 2)}:${pad(date.getUTCSeconds(), 2)}`;
|
|
252
|
+
return digits === 0 ? whole : `${whole}.${fraction.slice(0, digits)}`;
|
|
253
|
+
};
|
|
254
|
+
const ada = {
|
|
255
|
+
id: "00000000-0000-4000-8000-00000000ada1",
|
|
256
|
+
displayName: "Ada Example",
|
|
257
|
+
mail: "ada@example.test",
|
|
258
|
+
userPrincipalName: "ada@example.test"
|
|
259
|
+
};
|
|
260
|
+
const grace = {
|
|
261
|
+
name: "Grace Example",
|
|
262
|
+
address: "grace@example.test"
|
|
263
|
+
};
|
|
264
|
+
const adaRecipient = {
|
|
265
|
+
name: "Ada Example",
|
|
266
|
+
address: "ada@example.test"
|
|
267
|
+
};
|
|
268
|
+
const inboxId = "AAMkAGI2-synthetic-inbox-folder=";
|
|
269
|
+
const pagingFolderId = "AAMkAGI2-synthetic-folder-0001=";
|
|
270
|
+
const defaultMailFolders = [
|
|
271
|
+
{
|
|
272
|
+
id: inboxId,
|
|
273
|
+
displayName: "Inbox",
|
|
274
|
+
wellKnownName: "inbox"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "AAMkAGI2-synthetic-drafts-folder=",
|
|
278
|
+
displayName: "Drafts",
|
|
279
|
+
wellKnownName: "drafts"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "AAMkAGI2-synthetic-deleteditems-folder=",
|
|
283
|
+
displayName: "Deleted Items",
|
|
284
|
+
wellKnownName: "deleteditems"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: pagingFolderId,
|
|
288
|
+
displayName: "Synthetic updates",
|
|
289
|
+
wellKnownName: null
|
|
290
|
+
}
|
|
291
|
+
];
|
|
292
|
+
const pagingMessage = (number, hour) => {
|
|
293
|
+
const id = `AAMkAGI2-synthetic-message-${pad(number, 4)}=`;
|
|
294
|
+
const at = `2026-09-22T${pad(hour, 2)}:00:00Z`;
|
|
295
|
+
return {
|
|
296
|
+
id,
|
|
297
|
+
parentFolderId: pagingFolderId,
|
|
298
|
+
subject: `Synthetic update ${number - 100}`,
|
|
299
|
+
bodyContent: "Synthetic message body.",
|
|
300
|
+
from: grace,
|
|
301
|
+
sender: grace,
|
|
302
|
+
toRecipients: [adaRecipient],
|
|
303
|
+
isRead: true,
|
|
304
|
+
isDraft: false,
|
|
305
|
+
receivedDateTime: at,
|
|
306
|
+
sentDateTime: at,
|
|
307
|
+
createdDateTime: at,
|
|
308
|
+
lastModifiedDateTime: at,
|
|
309
|
+
conversationId: `AAQkAGI2-synthetic-conversation-${pad(number, 4)}=`,
|
|
310
|
+
internetMessageId: `<synthetic-${pad(number, 4)}@example.test>`,
|
|
311
|
+
changeKey: `CQAAABYAAAAsynthetic${pad(number, 4)}`
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
const attachmentMessageId = "AAMkAGI2-synthetic-message-0001=";
|
|
315
|
+
const defaultMessages = [
|
|
316
|
+
{
|
|
317
|
+
id: attachmentMessageId,
|
|
318
|
+
parentFolderId: inboxId,
|
|
319
|
+
subject: "Synthetic message with attachments",
|
|
320
|
+
bodyContentType: "html",
|
|
321
|
+
bodyContent: "<p>Synthetic body <img src=\"cid:image001.png@01DD2E00.00000000\"></p>",
|
|
322
|
+
from: grace,
|
|
323
|
+
sender: grace,
|
|
324
|
+
toRecipients: [adaRecipient],
|
|
325
|
+
isRead: false,
|
|
326
|
+
isDraft: false,
|
|
327
|
+
hasAttachments: true,
|
|
328
|
+
receivedDateTime: "2026-09-22T08:15:00Z",
|
|
329
|
+
sentDateTime: "2026-09-22T08:15:00Z",
|
|
330
|
+
createdDateTime: "2026-09-22T08:15:00Z",
|
|
331
|
+
lastModifiedDateTime: "2026-09-22T08:15:00Z",
|
|
332
|
+
conversationId: "AAQkAGI2-synthetic-conversation-0001=",
|
|
333
|
+
internetMessageId: "<synthetic-0001@example.test>",
|
|
334
|
+
changeKey: "CQAAABYAAAAsynthetic0001"
|
|
335
|
+
},
|
|
336
|
+
pagingMessage(101, 9),
|
|
337
|
+
pagingMessage(102, 8),
|
|
338
|
+
pagingMessage(103, 7)
|
|
339
|
+
];
|
|
340
|
+
const defaultAttachments = [{
|
|
341
|
+
id: "AAMkAGI2-synthetic-attachment-0001=",
|
|
342
|
+
messageId: attachmentMessageId,
|
|
343
|
+
name: "image001.png",
|
|
344
|
+
contentType: "image/png",
|
|
345
|
+
size: 1024,
|
|
346
|
+
isInline: true,
|
|
347
|
+
contentId: "image001.png@01DD2E00.00000000",
|
|
348
|
+
contentBytes: "iVBORw0KGgo=",
|
|
349
|
+
lastModifiedDateTime: "2026-09-22T08:15:00Z"
|
|
350
|
+
}, {
|
|
351
|
+
id: "AAMkAGI2-synthetic-attachment-0002=",
|
|
352
|
+
messageId: attachmentMessageId,
|
|
353
|
+
name: "synthetic-report.pdf",
|
|
354
|
+
contentType: "application/pdf",
|
|
355
|
+
size: 2048,
|
|
356
|
+
isInline: false,
|
|
357
|
+
contentId: null,
|
|
358
|
+
contentBytes: "JVBERi0xLjQK",
|
|
359
|
+
lastModifiedDateTime: "2026-09-22T08:15:00Z"
|
|
360
|
+
}];
|
|
361
|
+
const calendarId = "AAMkAGI2-synthetic-calendar-0001=";
|
|
362
|
+
const defaultCalendars = [{
|
|
363
|
+
id: calendarId,
|
|
364
|
+
name: "Calendar"
|
|
365
|
+
}];
|
|
366
|
+
const defaultEvents = [{
|
|
367
|
+
id: "AAMkAGI2-synthetic-event-0001=",
|
|
368
|
+
calendarId,
|
|
369
|
+
subject: "Synthetic planning session",
|
|
370
|
+
start: "2026-09-23T12:00:00Z",
|
|
371
|
+
end: "2026-09-23T13:00:00Z",
|
|
372
|
+
changeKey: "DwAAABYAAAAsynthetic0001"
|
|
373
|
+
}, {
|
|
374
|
+
id: "AAMkAGI2-synthetic-event-0002=",
|
|
375
|
+
calendarId,
|
|
376
|
+
subject: "Synthetic review",
|
|
377
|
+
start: "2026-09-24T08:30:00Z",
|
|
378
|
+
end: "2026-09-24T09:00:00Z",
|
|
379
|
+
changeKey: "DwAAABYAAAAsynthetic0002"
|
|
380
|
+
}];
|
|
381
|
+
const defaultDrive = {
|
|
382
|
+
id: "b!synthetic-drive-0001",
|
|
383
|
+
driveType: "business",
|
|
384
|
+
rootId: "01SYNTHETICROOTFOLDER00000000001"
|
|
385
|
+
};
|
|
386
|
+
const quickXorHash = "AAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
|
387
|
+
const conformanceFolderId = "01SYNTHETICPARENTFOLDER0000000001";
|
|
388
|
+
const sourcesFolderId = "01SYNTHETICSOURCEPARENT000000001";
|
|
389
|
+
const rootItem = {
|
|
390
|
+
id: defaultDrive.rootId,
|
|
391
|
+
parentId: null,
|
|
392
|
+
name: "root",
|
|
393
|
+
kind: "folder"
|
|
394
|
+
};
|
|
395
|
+
const defaultDriveItems = [
|
|
396
|
+
rootItem,
|
|
397
|
+
{
|
|
398
|
+
id: conformanceFolderId,
|
|
399
|
+
parentId: defaultDrive.rootId,
|
|
400
|
+
name: "Conformance",
|
|
401
|
+
kind: "folder"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "01SYNTHETICEXISTINGFILE000000001",
|
|
405
|
+
parentId: conformanceFolderId,
|
|
406
|
+
name: "synthetic-existing.txt",
|
|
407
|
+
kind: "file",
|
|
408
|
+
size: 12,
|
|
409
|
+
mimeType: "text/plain",
|
|
410
|
+
quickXorHash
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: sourcesFolderId,
|
|
414
|
+
parentId: defaultDrive.rootId,
|
|
415
|
+
name: "Sources",
|
|
416
|
+
kind: "folder"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: "01SYNTHETICSOURCEFILE00000000001",
|
|
420
|
+
parentId: sourcesFolderId,
|
|
421
|
+
name: "synthetic-notes.txt",
|
|
422
|
+
kind: "file",
|
|
423
|
+
size: 24,
|
|
424
|
+
mimeType: "text/plain",
|
|
425
|
+
quickXorHash
|
|
426
|
+
}
|
|
427
|
+
];
|
|
428
|
+
const profileEntities = (profile) => {
|
|
429
|
+
switch (profile) {
|
|
430
|
+
case "default": return {
|
|
431
|
+
user: ada,
|
|
432
|
+
mailFolders: defaultMailFolders,
|
|
433
|
+
messages: defaultMessages,
|
|
434
|
+
attachments: defaultAttachments,
|
|
435
|
+
calendars: defaultCalendars,
|
|
436
|
+
events: defaultEvents,
|
|
437
|
+
drive: defaultDrive,
|
|
438
|
+
driveItems: defaultDriveItems
|
|
439
|
+
};
|
|
440
|
+
case "empty": return {
|
|
441
|
+
user: ada,
|
|
442
|
+
mailFolders: defaultMailFolders.filter((folder) => folder.wellKnownName !== null),
|
|
443
|
+
messages: [],
|
|
444
|
+
attachments: [],
|
|
445
|
+
calendars: defaultCalendars,
|
|
446
|
+
events: [],
|
|
447
|
+
drive: defaultDrive,
|
|
448
|
+
driveItems: [rootItem]
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const defaultTimestamp = "2026-09-29T10:00:00Z";
|
|
453
|
+
const messageFromSeed = (seed, user) => {
|
|
454
|
+
const owner = {
|
|
455
|
+
name: user.displayName,
|
|
456
|
+
address: user.mail
|
|
457
|
+
};
|
|
458
|
+
const created = seed.createdDateTime ?? defaultTimestamp;
|
|
459
|
+
return {
|
|
460
|
+
id: seed.id,
|
|
461
|
+
parentFolderId: seed.parentFolderId,
|
|
462
|
+
subject: seed.subject,
|
|
463
|
+
bodyContentType: seed.bodyContentType ?? "text",
|
|
464
|
+
bodyContent: seed.bodyContent ?? "",
|
|
465
|
+
from: seed.from === void 0 ? owner : seed.from,
|
|
466
|
+
sender: seed.sender === void 0 ? owner : seed.sender,
|
|
467
|
+
toRecipients: seed.toRecipients ?? [],
|
|
468
|
+
ccRecipients: seed.ccRecipients ?? [],
|
|
469
|
+
bccRecipients: seed.bccRecipients ?? [],
|
|
470
|
+
replyTo: seed.replyTo ?? [],
|
|
471
|
+
isRead: seed.isRead ?? true,
|
|
472
|
+
isDraft: seed.isDraft ?? false,
|
|
473
|
+
importance: seed.importance ?? "normal",
|
|
474
|
+
flagStatus: seed.flagStatus ?? "notFlagged",
|
|
475
|
+
categories: seed.categories ?? [],
|
|
476
|
+
hasAttachments: seed.hasAttachments ?? false,
|
|
477
|
+
createdDateTime: created,
|
|
478
|
+
lastModifiedDateTime: seed.lastModifiedDateTime ?? created,
|
|
479
|
+
receivedDateTime: seed.receivedDateTime ?? created,
|
|
480
|
+
sentDateTime: seed.sentDateTime ?? created,
|
|
481
|
+
conversationId: seed.conversationId ?? `AAQkAGI2-synthetic-conversation-${seed.id}`,
|
|
482
|
+
internetMessageId: seed.internetMessageId ?? `<${seed.id}@example.test>`,
|
|
483
|
+
changeKey: seed.changeKey ?? "CQAAABYAAAAsynthetic0000"
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
/** Decoded size of a base64 string (no validation beyond the padding count). */
|
|
487
|
+
const base64Size = (value) => Math.max(0, Math.floor(value.length * 3 / 4) - (value.match(/=+$/)?.[0].length ?? 0));
|
|
488
|
+
const attachmentFromSeed = (seed) => {
|
|
489
|
+
const contentBytes = seed.contentBytes ?? "";
|
|
490
|
+
return {
|
|
491
|
+
id: seed.id,
|
|
492
|
+
messageId: seed.messageId,
|
|
493
|
+
name: seed.name,
|
|
494
|
+
contentType: seed.contentType ?? "application/octet-stream",
|
|
495
|
+
size: seed.size ?? base64Size(contentBytes),
|
|
496
|
+
isInline: seed.isInline ?? false,
|
|
497
|
+
contentId: seed.contentId ?? null,
|
|
498
|
+
contentLocation: seed.contentLocation ?? null,
|
|
499
|
+
contentBytes,
|
|
500
|
+
lastModifiedDateTime: seed.lastModifiedDateTime ?? defaultTimestamp
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
/** A seeded start/end as a seven-digit UTC local date-time, or `undefined` when unreadable. */
|
|
504
|
+
const seedDateTime = (value) => {
|
|
505
|
+
const ticks = value.endsWith("Z") ? parseInstant(value, "required") : parseInstant(value, "forbidden");
|
|
506
|
+
return ticks === void 0 ? void 0 : formatLocalDateTime(ticks, 7);
|
|
507
|
+
};
|
|
508
|
+
const eventFromSeed = (seed, start, end) => ({
|
|
509
|
+
id: seed.id,
|
|
510
|
+
calendarId: seed.calendarId,
|
|
511
|
+
subject: seed.subject,
|
|
512
|
+
bodyContentType: seed.bodyContentType ?? "text",
|
|
513
|
+
bodyContent: seed.bodyContent ?? "",
|
|
514
|
+
start,
|
|
515
|
+
end,
|
|
516
|
+
isCancelled: seed.isCancelled ?? false,
|
|
517
|
+
isReminderOn: seed.isReminderOn ?? false,
|
|
518
|
+
showAs: seed.showAs ?? "busy",
|
|
519
|
+
changeKey: seed.changeKey ?? "DwAAABYAAAAsynthetic0000",
|
|
520
|
+
createdDateTime: seed.createdDateTime ?? defaultTimestamp,
|
|
521
|
+
lastModifiedDateTime: seed.lastModifiedDateTime ?? seed.createdDateTime ?? defaultTimestamp
|
|
522
|
+
});
|
|
523
|
+
const driveItemFromSeed = (seed) => ({
|
|
524
|
+
id: seed.id,
|
|
525
|
+
parentId: seed.parentId,
|
|
526
|
+
name: seed.name,
|
|
527
|
+
kind: seed.kind,
|
|
528
|
+
size: seed.kind === "folder" ? 0 : seed.size ?? 0,
|
|
529
|
+
mimeType: seed.kind === "folder" ? null : seed.mimeType ?? "application/octet-stream",
|
|
530
|
+
quickXorHash: seed.kind === "folder" ? null : seed.quickXorHash ?? null,
|
|
531
|
+
createdDateTime: seed.createdDateTime ?? defaultTimestamp,
|
|
532
|
+
lastModifiedDateTime: seed.lastModifiedDateTime ?? seed.createdDateTime ?? defaultTimestamp
|
|
533
|
+
});
|
|
534
|
+
const duplicate = (values) => values.find((value, index) => values.indexOf(value) !== index);
|
|
535
|
+
/** Integrity problems a decoded seed can still have (duplicates, dangling references, cycles). */
|
|
536
|
+
const seedProblem = (entities) => {
|
|
537
|
+
const folderIds = entities.mailFolders.map((folder) => folder.id);
|
|
538
|
+
const messageIds = entities.messages.map((message) => message.id);
|
|
539
|
+
const calendarIds = entities.calendars.map((calendar) => calendar.id);
|
|
540
|
+
const itemIds = entities.driveItems.map((item) => item.id);
|
|
541
|
+
const duplicates = [
|
|
542
|
+
["mail folder id", duplicate(folderIds)],
|
|
543
|
+
["well-known folder", duplicate(entities.mailFolders.flatMap((folder) => folder.wellKnownName ?? []))],
|
|
544
|
+
["message id", duplicate(messageIds)],
|
|
545
|
+
["attachment id", duplicate(entities.attachments.map((attachment) => attachment.id))],
|
|
546
|
+
["calendar id", duplicate(calendarIds)],
|
|
547
|
+
["event id", duplicate(entities.events.map((event) => event.id))],
|
|
548
|
+
["drive item id", duplicate(itemIds)]
|
|
549
|
+
];
|
|
550
|
+
for (const [label, value] of duplicates) if (value !== void 0) return `duplicate ${label} ${value}`;
|
|
551
|
+
for (const wellKnown of ["drafts", "deleteditems"]) if (!entities.mailFolders.some((folder) => folder.wellKnownName === wellKnown)) return `the mail folders need a ${wellKnown} folder`;
|
|
552
|
+
const message = entities.messages.find((candidate) => !folderIds.includes(candidate.parentFolderId));
|
|
553
|
+
if (message !== void 0) return `message ${message.id} references missing folder ${message.parentFolderId}`;
|
|
554
|
+
const attachment = entities.attachments.find((candidate) => !messageIds.includes(candidate.messageId));
|
|
555
|
+
if (attachment !== void 0) return `attachment ${attachment.id} references missing message ${attachment.messageId}`;
|
|
556
|
+
const event = entities.events.find((candidate) => !calendarIds.includes(candidate.calendarId));
|
|
557
|
+
if (event !== void 0) return `event ${event.id} references missing calendar ${event.calendarId}`;
|
|
558
|
+
const roots = entities.driveItems.filter((item) => item.parentId === null);
|
|
559
|
+
if (roots.length !== 1 || roots[0]?.id !== entities.drive.rootId || roots[0].kind !== "folder") return `the drive items need exactly one root folder with id ${entities.drive.rootId}`;
|
|
560
|
+
const byId = new Map(entities.driveItems.map((item) => [item.id, item]));
|
|
561
|
+
for (const item of entities.driveItems) {
|
|
562
|
+
let parentId = item.parentId;
|
|
563
|
+
let steps = 0;
|
|
564
|
+
while (parentId !== null) {
|
|
565
|
+
const parent = byId.get(parentId);
|
|
566
|
+
if (parent === void 0 || parent.kind !== "folder") return `drive item ${item.id} has a missing or non-folder parent ${parentId}`;
|
|
567
|
+
steps += 1;
|
|
568
|
+
if (steps > entities.driveItems.length) return `drive item ${item.id} is in a parent cycle`;
|
|
569
|
+
parentId = parent.parentId;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
/** Build the emulator state for a decoded seed; a string is an integrity problem. */
|
|
574
|
+
const stateFromSeed = (seed) => {
|
|
575
|
+
const profile = profileEntities(seed.profile ?? "default");
|
|
576
|
+
const entities = {
|
|
577
|
+
user: seed.user ?? profile.user,
|
|
578
|
+
mailFolders: seed.mailFolders ?? profile.mailFolders,
|
|
579
|
+
messages: seed.messages ?? profile.messages,
|
|
580
|
+
attachments: seed.attachments ?? profile.attachments,
|
|
581
|
+
calendars: seed.calendars ?? profile.calendars,
|
|
582
|
+
events: seed.events ?? profile.events,
|
|
583
|
+
drive: seed.drive ?? profile.drive,
|
|
584
|
+
driveItems: seed.driveItems ?? profile.driveItems
|
|
585
|
+
};
|
|
586
|
+
const problem = seedProblem(entities);
|
|
587
|
+
if (problem !== void 0) return problem;
|
|
588
|
+
const events = [];
|
|
589
|
+
for (const event of entities.events) {
|
|
590
|
+
const start = seedDateTime(event.start);
|
|
591
|
+
const end = seedDateTime(event.end);
|
|
592
|
+
if (start === void 0 || end === void 0 || end < start) return `event ${event.id} needs readable UTC start and end, with end not before start`;
|
|
593
|
+
events.push(eventFromSeed(event, start, end));
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
user: entities.user,
|
|
597
|
+
mailFolders: entities.mailFolders,
|
|
598
|
+
messages: entities.messages.map((message) => messageFromSeed(message, entities.user)),
|
|
599
|
+
attachments: entities.attachments.map(attachmentFromSeed),
|
|
600
|
+
calendars: entities.calendars,
|
|
601
|
+
events,
|
|
602
|
+
drive: entities.drive,
|
|
603
|
+
driveItems: entities.driveItems.map(driveItemFromSeed),
|
|
604
|
+
counters: {
|
|
605
|
+
nextEventNumber: 101,
|
|
606
|
+
nextMessageNumber: 1,
|
|
607
|
+
nextChangeKeyNumber: 1001,
|
|
608
|
+
nextItemNumber: 1
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
/** Decode and build a seed; a string is the reason it is invalid. */
|
|
613
|
+
const buildSeedState = (input) => {
|
|
614
|
+
const decoded = decodeSeedInput(input);
|
|
615
|
+
return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : stateFromSeed(decoded.success);
|
|
616
|
+
};
|
|
617
|
+
/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */
|
|
618
|
+
const decodeState = (input) => {
|
|
619
|
+
const decoded = decodeStateInput(input);
|
|
620
|
+
return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success };
|
|
621
|
+
};
|
|
622
|
+
//#endregion
|
|
623
|
+
export { MicrosoftEmulatorAttachment, MicrosoftEmulatorAttachmentSeed, MicrosoftEmulatorCalendar, MicrosoftEmulatorDrive, MicrosoftEmulatorDriveItem, MicrosoftEmulatorDriveItemSeed, MicrosoftEmulatorEvent, MicrosoftEmulatorEventSeed, MicrosoftEmulatorMailFolder, MicrosoftEmulatorMessage, MicrosoftEmulatorMessageSeed, MicrosoftEmulatorProfile, MicrosoftEmulatorRecipient, MicrosoftEmulatorSeed, MicrosoftEmulatorStateSchema, MicrosoftEmulatorUser, buildSeedState, decodeState, formatLocalDateTime, microsoftEmulatorDateTimePattern, parseInstant };
|
|
624
|
+
|
|
625
|
+
//# sourceMappingURL=state.mjs.map
|