@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,488 @@
|
|
|
1
|
+
import { bodyObject, codes, collection, decodeSegment, entity, invalidValue, isJsonObject, jsonResponse, metadataContext, nestedObject, nextChangeKey, nextLinkOf, notEmulated, nowTimestamp, odataKey, padded, pageOf, parsePreferences, project, resolveUser, selectSuffix, selectedFields, textBody } from "./graph.mjs";
|
|
2
|
+
import { Predicate } from "effect";
|
|
3
|
+
//#region src/microsoft/mail.ts
|
|
4
|
+
/**
|
|
5
|
+
* Microsoft Graph emulator Outlook routes (internal): folder message listing with paging, draft
|
|
6
|
+
* create, message update and move with immutable ids, attachment listing and retrieval, and the
|
|
7
|
+
* JSON `$batch` permanent delete. Wire shapes follow the synthetic Outlook conformance fixtures.
|
|
8
|
+
*
|
|
9
|
+
* Ids: every Outlook fixture request sends `Prefer: IdType="ImmutableId"`, so every Outlook route
|
|
10
|
+
* (and every `$batch` subrequest) needs it; without it the request fails closed (400). Messages
|
|
11
|
+
* have one id, the immutable id, which a move keeps.
|
|
12
|
+
*
|
|
13
|
+
* Writes accept only the fields the fixtures send: a draft create takes `subject`, a text `body`,
|
|
14
|
+
* `toRecipients`, and the owner as `from`; an update takes `subject` and `isRead`; a move takes
|
|
15
|
+
* `destinationId`, which must be `deleteditems`. Anything else, including unknown nested keys,
|
|
16
|
+
* fails closed before any write.
|
|
17
|
+
* The fixtures update, move, and permanently delete only drafts the case created, so those writes
|
|
18
|
+
* to a message that is not a draft are not emulated (400), and neither is permanently deleting a
|
|
19
|
+
* message with attachments (no fixture creates a draft with attachments).
|
|
20
|
+
*
|
|
21
|
+
* Concurrent writes: the first committed write (update or move) holds its message for
|
|
22
|
+
* `conflictWindowMs` before answering (a refused or faulted write commits nothing and holds
|
|
23
|
+
* nothing); an overlapping write to that message gets 409 `ErrorIrresolvableConflict` and changes
|
|
24
|
+
* nothing. Non-overlapping writes both apply (an emulator extrapolation).
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
const messageTagPrefix = "CQAAABYAAAAsynthetic";
|
|
29
|
+
const messageFields = [
|
|
30
|
+
"id",
|
|
31
|
+
"createdDateTime",
|
|
32
|
+
"lastModifiedDateTime",
|
|
33
|
+
"changeKey",
|
|
34
|
+
"categories",
|
|
35
|
+
"receivedDateTime",
|
|
36
|
+
"sentDateTime",
|
|
37
|
+
"hasAttachments",
|
|
38
|
+
"internetMessageId",
|
|
39
|
+
"subject",
|
|
40
|
+
"bodyPreview",
|
|
41
|
+
"importance",
|
|
42
|
+
"parentFolderId",
|
|
43
|
+
"conversationId",
|
|
44
|
+
"isDeliveryReceiptRequested",
|
|
45
|
+
"isReadReceiptRequested",
|
|
46
|
+
"isRead",
|
|
47
|
+
"isDraft",
|
|
48
|
+
"webLink",
|
|
49
|
+
"body",
|
|
50
|
+
"sender",
|
|
51
|
+
"from",
|
|
52
|
+
"toRecipients",
|
|
53
|
+
"ccRecipients",
|
|
54
|
+
"bccRecipients",
|
|
55
|
+
"replyTo",
|
|
56
|
+
"flag"
|
|
57
|
+
];
|
|
58
|
+
const attachmentListFields = [
|
|
59
|
+
"id",
|
|
60
|
+
"name",
|
|
61
|
+
"contentType",
|
|
62
|
+
"size",
|
|
63
|
+
"isInline",
|
|
64
|
+
"lastModifiedDateTime"
|
|
65
|
+
];
|
|
66
|
+
const notFound = (request) => request.error(404, codes.mailItemNotFound, "The specified object was not found in the store.");
|
|
67
|
+
const conflict = (request) => request.error(409, codes.conflict, "The send or update operation could not be performed because the change key passed in the request does not match the current change key for the item.");
|
|
68
|
+
const recipient = (value) => ({ emailAddress: {
|
|
69
|
+
name: value.name ?? value.address,
|
|
70
|
+
address: value.address
|
|
71
|
+
} });
|
|
72
|
+
/** Plain-text preview: tags stripped, whitespace collapsed, at most 255 characters. */
|
|
73
|
+
const bodyPreview = (message) => (message.bodyContentType === "html" ? message.bodyContent.replace(/<[^>]*>/g, " ") : message.bodyContent).replace(/\s+/g, " ").trim().slice(0, 255);
|
|
74
|
+
/** 400 for an Outlook request without `Prefer: IdType="ImmutableId"` (every fixture sends it). */
|
|
75
|
+
const immutableIdProblem = (request) => request.prefer.immutableId ? void 0 : notEmulated(request, "Outlook requests without Prefer: IdType=\"ImmutableId\" are not emulated.");
|
|
76
|
+
/** `users('{userId}')`, the context prefix of every Outlook route. */
|
|
77
|
+
const userContext = (request) => `users${odataKey(request.params.userId ?? "")}`;
|
|
78
|
+
/** `@odata.context` of a single message (create, update, move), as the fixtures record it. */
|
|
79
|
+
const messageEntityContext = (env, request) => metadataContext(env, `${userContext(request)}/messages/$entity`);
|
|
80
|
+
/** The full message, in the draft fixture's key order. */
|
|
81
|
+
const renderMessage = (message) => {
|
|
82
|
+
const id = message.id;
|
|
83
|
+
return {
|
|
84
|
+
"@odata.etag": `W/"${message.changeKey}"`,
|
|
85
|
+
id,
|
|
86
|
+
createdDateTime: message.createdDateTime,
|
|
87
|
+
lastModifiedDateTime: message.lastModifiedDateTime,
|
|
88
|
+
changeKey: message.changeKey,
|
|
89
|
+
categories: message.categories,
|
|
90
|
+
receivedDateTime: message.receivedDateTime,
|
|
91
|
+
sentDateTime: message.sentDateTime,
|
|
92
|
+
hasAttachments: message.hasAttachments,
|
|
93
|
+
internetMessageId: message.internetMessageId,
|
|
94
|
+
subject: message.subject,
|
|
95
|
+
bodyPreview: bodyPreview(message),
|
|
96
|
+
importance: message.importance,
|
|
97
|
+
parentFolderId: message.parentFolderId,
|
|
98
|
+
conversationId: message.conversationId,
|
|
99
|
+
isDeliveryReceiptRequested: false,
|
|
100
|
+
isReadReceiptRequested: false,
|
|
101
|
+
isRead: message.isRead,
|
|
102
|
+
isDraft: message.isDraft,
|
|
103
|
+
webLink: `https://outlook.office365.com/owa/?ItemID=${encodeURIComponent(id)}&exvsurl=1&viewmodel=ReadMessageItem`,
|
|
104
|
+
body: {
|
|
105
|
+
contentType: message.bodyContentType,
|
|
106
|
+
content: message.bodyContent
|
|
107
|
+
},
|
|
108
|
+
sender: message.sender === null ? null : recipient(message.sender),
|
|
109
|
+
from: message.from === null ? null : recipient(message.from),
|
|
110
|
+
toRecipients: message.toRecipients.map(recipient),
|
|
111
|
+
ccRecipients: message.ccRecipients.map(recipient),
|
|
112
|
+
bccRecipients: message.bccRecipients.map(recipient),
|
|
113
|
+
replyTo: message.replyTo.map(recipient),
|
|
114
|
+
flag: { flagStatus: message.flagStatus }
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
/** A folder by id (the paging fixture lists a folder by id, never by well-known name). */
|
|
118
|
+
const folderById = (state, id) => state.mailFolders.find((folder) => folder.id === id);
|
|
119
|
+
/** The seeded folder with a well-known name (internal lookups and the move destination). */
|
|
120
|
+
const wellKnownFolder = (state, name) => state.mailFolders.find((folder) => folder.wellKnownName === name);
|
|
121
|
+
/** The only move destination a fixture sends (`{ destinationId: 'deleteditems' }`). */
|
|
122
|
+
const moveDestination = "deleteditems";
|
|
123
|
+
/** The largest folder message `$top` a fixture sends (`$top=2`, the paging case). */
|
|
124
|
+
const folderMessagesMaxTop = 2;
|
|
125
|
+
/** A message by its (immutable) id. */
|
|
126
|
+
const findMessage = (state, id) => state.messages.find((message) => message.id === id);
|
|
127
|
+
/** 400 for a write to a message that is not a draft (the fixtures write only case drafts). */
|
|
128
|
+
const notDraftProblem = (request, message) => message.isDraft ? void 0 : notEmulated(request, "writes to a message that is not a draft are not emulated (the fixtures write only drafts).");
|
|
129
|
+
const replaceMessage = (state, updated) => {
|
|
130
|
+
state.messages = state.messages.map((message) => message.id === updated.id ? updated : message);
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* `GET /users/{userId}/mailFolders/{folderId}/messages`: newest `receivedDateTime` first, one
|
|
134
|
+
* `$top`/`$skip` page with an opaque `@odata.nextLink` on the configured Graph origin. The folder
|
|
135
|
+
* is named by id (a well-known name or an unknown id is not emulated), and `$top` is required, at
|
|
136
|
+
* most `folderMessagesMaxTop`, as the paging fixture sends it.
|
|
137
|
+
*/
|
|
138
|
+
const listFolderMessages = (state, request, env) => {
|
|
139
|
+
const user = resolveUser(state, request);
|
|
140
|
+
if (user instanceof Response) return user;
|
|
141
|
+
const immutable = immutableIdProblem(request);
|
|
142
|
+
if (immutable !== void 0) return immutable;
|
|
143
|
+
const folder = folderById(state, request.params.folderId ?? "");
|
|
144
|
+
if (folder === void 0) return notEmulated(request, "listing a folder by well-known name or an unknown folder id is not emulated (the paging fixture lists a folder by id).");
|
|
145
|
+
const fields = selectedFields(request, messageFields);
|
|
146
|
+
if (fields instanceof Response) return fields;
|
|
147
|
+
const page = pageOf(state.messages.filter((message) => message.parentFolderId === folder.id).sort((left, right) => left.receivedDateTime === right.receivedDateTime ? left.id.localeCompare(right.id) : left.receivedDateTime < right.receivedDateTime ? 1 : -1), request, folderMessagesMaxTop);
|
|
148
|
+
if (page instanceof Response) return page;
|
|
149
|
+
return jsonResponse(200, collection(metadataContext(env, `${userContext(request)}/mailFolders${odataKey(request.params.folderId ?? "")}/messages${selectSuffix(fields)}`), page.items.map((message) => project(renderMessage(message), fields)), nextLinkOf(env, request, page, ["$select"])));
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* A `recipient` write value, `{ emailAddress: { address, name? } }` with only those keys (the
|
|
153
|
+
* shape the fixture responses show); anything else fails closed.
|
|
154
|
+
*/
|
|
155
|
+
const recipientOf = (request, key, value) => {
|
|
156
|
+
const expected = `${key} { emailAddress: { address, name? } }`;
|
|
157
|
+
const recipient = nestedObject(request, value, ["emailAddress"], expected);
|
|
158
|
+
if (recipient instanceof Response) return recipient;
|
|
159
|
+
const email = nestedObject(request, recipient.emailAddress, ["address", "name"], expected);
|
|
160
|
+
if (email instanceof Response) return email;
|
|
161
|
+
const { address, name } = email;
|
|
162
|
+
if (!Predicate.isString(address) || name !== void 0 && !Predicate.isString(name)) return invalidValue(request, `${expected} needs a string address (and name).`);
|
|
163
|
+
return {
|
|
164
|
+
name: name ?? null,
|
|
165
|
+
address
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
/** `[recipient]` as stored recipients. */
|
|
169
|
+
const recipientsOf = (request, key, value) => {
|
|
170
|
+
if (!Array.isArray(value)) return invalidValue(request, `${key} must be an array.`);
|
|
171
|
+
const recipients = [];
|
|
172
|
+
for (const entry of value) {
|
|
173
|
+
const recipient = recipientOf(request, key, entry);
|
|
174
|
+
if (recipient instanceof Response) return recipient;
|
|
175
|
+
recipients.push(recipient);
|
|
176
|
+
}
|
|
177
|
+
return recipients;
|
|
178
|
+
};
|
|
179
|
+
/** Draft create keys, as the Outlook fixtures send them. */
|
|
180
|
+
const draftKeys = [
|
|
181
|
+
"subject",
|
|
182
|
+
"body",
|
|
183
|
+
"toRecipients",
|
|
184
|
+
"from"
|
|
185
|
+
];
|
|
186
|
+
/** Message update keys, as the Outlook fixtures send them. */
|
|
187
|
+
const updateKeys = ["subject", "isRead"];
|
|
188
|
+
/** The emulated message fields of a create or update body (keys already allowlisted). */
|
|
189
|
+
const messageWrite = (request, fields, user) => {
|
|
190
|
+
const write = {};
|
|
191
|
+
for (const [key, value] of Object.entries(fields)) switch (key) {
|
|
192
|
+
case "subject":
|
|
193
|
+
if (!Predicate.isString(value)) return invalidValue(request, "subject must be a string.");
|
|
194
|
+
write.subject = value;
|
|
195
|
+
break;
|
|
196
|
+
case "body": {
|
|
197
|
+
const content = textBody(request, value);
|
|
198
|
+
if (content instanceof Response) return content;
|
|
199
|
+
write.bodyContent = content;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "toRecipients": {
|
|
203
|
+
const recipients = recipientsOf(request, key, value);
|
|
204
|
+
if (recipients instanceof Response) return recipients;
|
|
205
|
+
write.toRecipients = recipients;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case "from": {
|
|
209
|
+
const from = recipientOf(request, key, value);
|
|
210
|
+
if (from instanceof Response) return from;
|
|
211
|
+
if (from.address.toLowerCase() !== user.mail.toLowerCase()) return notEmulated(request, "from must be the mailbox owner (send-as is not emulated).");
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "isRead":
|
|
215
|
+
if (!Predicate.isBoolean(value)) return invalidValue(request, "isRead must be a boolean.");
|
|
216
|
+
write.isRead = value;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
return write;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* `POST /users/{userId}/messages`: a new draft in Drafts (201 with the message). Never sent. New
|
|
223
|
+
* drafts have no attachments (`hasAttachments: false`, as the fixtures record).
|
|
224
|
+
*/
|
|
225
|
+
const createDraft = (state, request, env) => {
|
|
226
|
+
const user = resolveUser(state, request);
|
|
227
|
+
if (user instanceof Response) return user;
|
|
228
|
+
const immutable = immutableIdProblem(request);
|
|
229
|
+
if (immutable !== void 0) return immutable;
|
|
230
|
+
const fields = bodyObject(request, draftKeys);
|
|
231
|
+
if (fields instanceof Response) return fields;
|
|
232
|
+
const write = messageWrite(request, fields, user);
|
|
233
|
+
if (write instanceof Response) return write;
|
|
234
|
+
const drafts = wellKnownFolder(state, "drafts");
|
|
235
|
+
if (drafts === void 0) return notFound(request);
|
|
236
|
+
let number = state.counters.nextMessageNumber;
|
|
237
|
+
let id = `AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0A-synthetic-immutable-${padded(number, 4)}=`;
|
|
238
|
+
while (findMessage(state, id) !== void 0) {
|
|
239
|
+
number += 1;
|
|
240
|
+
id = `AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0A-synthetic-immutable-${padded(number, 4)}=`;
|
|
241
|
+
}
|
|
242
|
+
state.counters = {
|
|
243
|
+
...state.counters,
|
|
244
|
+
nextMessageNumber: number + 1
|
|
245
|
+
};
|
|
246
|
+
const now = nowTimestamp(env);
|
|
247
|
+
const owner = {
|
|
248
|
+
name: user.displayName,
|
|
249
|
+
address: user.mail
|
|
250
|
+
};
|
|
251
|
+
const message = {
|
|
252
|
+
id,
|
|
253
|
+
parentFolderId: drafts.id,
|
|
254
|
+
subject: write.subject ?? "",
|
|
255
|
+
bodyContentType: "text",
|
|
256
|
+
bodyContent: write.bodyContent ?? "",
|
|
257
|
+
from: owner,
|
|
258
|
+
sender: owner,
|
|
259
|
+
toRecipients: write.toRecipients ?? [],
|
|
260
|
+
ccRecipients: [],
|
|
261
|
+
bccRecipients: [],
|
|
262
|
+
replyTo: [],
|
|
263
|
+
isRead: true,
|
|
264
|
+
isDraft: true,
|
|
265
|
+
importance: "normal",
|
|
266
|
+
flagStatus: "notFlagged",
|
|
267
|
+
categories: [],
|
|
268
|
+
hasAttachments: false,
|
|
269
|
+
createdDateTime: now,
|
|
270
|
+
lastModifiedDateTime: now,
|
|
271
|
+
receivedDateTime: now,
|
|
272
|
+
sentDateTime: now,
|
|
273
|
+
conversationId: `AAQkAGI2-synthetic-conversation-draft-${padded(number, 4)}=`,
|
|
274
|
+
internetMessageId: `<synthetic-draft-${padded(number, 4)}@example.test>`,
|
|
275
|
+
changeKey: nextChangeKey(state, messageTagPrefix)
|
|
276
|
+
};
|
|
277
|
+
state.messages = [...state.messages, message];
|
|
278
|
+
return jsonResponse(201, entity(messageEntityContext(env, request), renderMessage(message)));
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* A message write that will hold its message: 409 (nothing changes) when the message is held by
|
|
282
|
+
* another write; otherwise the write runs (on the draft) and its answer names the message to hold.
|
|
283
|
+
* The route registration takes the hold only once the write is committed, then keeps it for
|
|
284
|
+
* `conflictWindowMs` before answering, so a refused or faulted write holds nothing.
|
|
285
|
+
*/
|
|
286
|
+
const heldWrite = (env, request, message, commit) => env.messageLocks.has(message.id) ? conflict(request) : {
|
|
287
|
+
response: commit(),
|
|
288
|
+
holds: message.id
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* `PATCH /users/{userId}/messages/{messageId}`: update a draft's `subject` or `isRead`; 200 with
|
|
292
|
+
* the message.
|
|
293
|
+
*/
|
|
294
|
+
const updateMessage = (state, request, env) => {
|
|
295
|
+
const user = resolveUser(state, request);
|
|
296
|
+
if (user instanceof Response) return user;
|
|
297
|
+
const immutable = immutableIdProblem(request);
|
|
298
|
+
if (immutable !== void 0) return immutable;
|
|
299
|
+
const message = findMessage(state, request.params.messageId ?? "");
|
|
300
|
+
if (message === void 0) return notFound(request);
|
|
301
|
+
const notDraft = notDraftProblem(request, message);
|
|
302
|
+
if (notDraft !== void 0) return notDraft;
|
|
303
|
+
const fields = bodyObject(request, updateKeys);
|
|
304
|
+
if (fields instanceof Response) return fields;
|
|
305
|
+
const write = messageWrite(request, fields, user);
|
|
306
|
+
if (write instanceof Response) return write;
|
|
307
|
+
return heldWrite(env, request, message, () => {
|
|
308
|
+
const updated = {
|
|
309
|
+
...message,
|
|
310
|
+
...write,
|
|
311
|
+
lastModifiedDateTime: nowTimestamp(env),
|
|
312
|
+
changeKey: nextChangeKey(state, messageTagPrefix)
|
|
313
|
+
};
|
|
314
|
+
replaceMessage(state, updated);
|
|
315
|
+
return jsonResponse(200, entity(messageEntityContext(env, request), renderMessage(updated)));
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* `POST /users/{userId}/messages/{messageId}/move` with `{ destinationId: 'deleteditems' }`, the
|
|
320
|
+
* only destination a fixture sends: 201 with the moved draft, which keeps its (immutable) id. Any
|
|
321
|
+
* other destination (another well-known name such as `inbox` or `drafts`, or a folder id) is not
|
|
322
|
+
* emulated (400) and moves nothing.
|
|
323
|
+
*/
|
|
324
|
+
const moveMessage = (state, request, env) => {
|
|
325
|
+
const user = resolveUser(state, request);
|
|
326
|
+
if (user instanceof Response) return user;
|
|
327
|
+
const immutable = immutableIdProblem(request);
|
|
328
|
+
if (immutable !== void 0) return immutable;
|
|
329
|
+
const message = findMessage(state, request.params.messageId ?? "");
|
|
330
|
+
if (message === void 0) return notFound(request);
|
|
331
|
+
const notDraft = notDraftProblem(request, message);
|
|
332
|
+
if (notDraft !== void 0) return notDraft;
|
|
333
|
+
const fields = bodyObject(request, ["destinationId"]);
|
|
334
|
+
if (fields instanceof Response) return fields;
|
|
335
|
+
if (!Predicate.isString(fields.destinationId)) return invalidValue(request, "destinationId must be a string (only deleteditems is emulated).");
|
|
336
|
+
if (fields.destinationId !== moveDestination) return notEmulated(request, `moves to destinations other than ${moveDestination} are not emulated (the fixtures move only to Deleted Items).`);
|
|
337
|
+
const destination = wellKnownFolder(state, moveDestination);
|
|
338
|
+
if (destination === void 0) return notFound(request);
|
|
339
|
+
return heldWrite(env, request, message, () => {
|
|
340
|
+
const moved = {
|
|
341
|
+
...message,
|
|
342
|
+
parentFolderId: destination.id,
|
|
343
|
+
lastModifiedDateTime: nowTimestamp(env),
|
|
344
|
+
changeKey: nextChangeKey(state, messageTagPrefix)
|
|
345
|
+
};
|
|
346
|
+
replaceMessage(state, moved);
|
|
347
|
+
return jsonResponse(201, entity(messageEntityContext(env, request), renderMessage(moved)));
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
const attachmentBase = (attachment) => ({
|
|
351
|
+
"@odata.type": "#microsoft.graph.fileAttachment",
|
|
352
|
+
"@odata.mediaContentType": attachment.contentType,
|
|
353
|
+
id: attachment.id
|
|
354
|
+
});
|
|
355
|
+
/** A listed attachment: base `attachment` properties only (no `contentId`), as the fixture shows. */
|
|
356
|
+
const renderListedAttachment = (attachment) => ({
|
|
357
|
+
...attachmentBase(attachment),
|
|
358
|
+
name: attachment.name,
|
|
359
|
+
contentType: attachment.contentType,
|
|
360
|
+
size: attachment.size,
|
|
361
|
+
isInline: attachment.isInline,
|
|
362
|
+
lastModifiedDateTime: attachment.lastModifiedDateTime
|
|
363
|
+
});
|
|
364
|
+
/** A retrieved file attachment, with `contentId` and `contentBytes`, in the fixture key order. */
|
|
365
|
+
const renderAttachment = (attachment) => ({
|
|
366
|
+
...attachmentBase(attachment),
|
|
367
|
+
lastModifiedDateTime: attachment.lastModifiedDateTime,
|
|
368
|
+
name: attachment.name,
|
|
369
|
+
contentType: attachment.contentType,
|
|
370
|
+
size: attachment.size,
|
|
371
|
+
isInline: attachment.isInline,
|
|
372
|
+
contentId: attachment.contentId,
|
|
373
|
+
contentLocation: attachment.contentLocation,
|
|
374
|
+
contentBytes: attachment.contentBytes
|
|
375
|
+
});
|
|
376
|
+
/**
|
|
377
|
+
* `GET /users/{userId}/messages/{messageId}/attachments`: every attachment, inline ones included,
|
|
378
|
+
* in one response (the fixtures send only `$select`: no `$top`, `$skip`, or `@odata.nextLink`).
|
|
379
|
+
* `$select` accepts only base `attachment` properties, so a listing never carries `contentId`.
|
|
380
|
+
*/
|
|
381
|
+
const listAttachments = (state, request, env) => {
|
|
382
|
+
const user = resolveUser(state, request);
|
|
383
|
+
if (user instanceof Response) return user;
|
|
384
|
+
const immutable = immutableIdProblem(request);
|
|
385
|
+
if (immutable !== void 0) return immutable;
|
|
386
|
+
const message = findMessage(state, request.params.messageId ?? "");
|
|
387
|
+
if (message === void 0) return notFound(request);
|
|
388
|
+
const fields = selectedFields(request, attachmentListFields);
|
|
389
|
+
if (fields instanceof Response) return fields;
|
|
390
|
+
const attachments = state.attachments.filter((attachment) => attachment.messageId === message.id);
|
|
391
|
+
return jsonResponse(200, collection(metadataContext(env, `${userContext(request)}/messages${odataKey(request.params.messageId ?? "")}/attachments${selectSuffix(fields)}`), attachments.map((attachment) => project(renderListedAttachment(attachment), fields)), void 0));
|
|
392
|
+
};
|
|
393
|
+
/** `GET /users/{userId}/messages/{messageId}/attachments/{attachmentId}`: the file attachment. */
|
|
394
|
+
const getAttachment = (state, request, env) => {
|
|
395
|
+
const user = resolveUser(state, request);
|
|
396
|
+
if (user instanceof Response) return user;
|
|
397
|
+
const immutable = immutableIdProblem(request);
|
|
398
|
+
if (immutable !== void 0) return immutable;
|
|
399
|
+
const message = findMessage(state, request.params.messageId ?? "");
|
|
400
|
+
const attachment = message === void 0 ? void 0 : state.attachments.find((candidate) => candidate.messageId === message.id && candidate.id === request.params.attachmentId);
|
|
401
|
+
const context = metadataContext(env, `${userContext(request)}/messages${odataKey(request.params.messageId ?? "")}/attachments/$entity`);
|
|
402
|
+
return attachment === void 0 ? notFound(request) : jsonResponse(200, entity(context, renderAttachment(attachment)));
|
|
403
|
+
};
|
|
404
|
+
const maxBatchRequests = 20;
|
|
405
|
+
const permanentDeletePattern = /^\/?users\/([^/?#]+)\/messages\/([^/?#]+)\/permanentDelete$/;
|
|
406
|
+
/** Subrequest headers the fixtures send (`Prefer`, which must ask for immutable ids). */
|
|
407
|
+
const allowedSubrequestHeaders = ["prefer"];
|
|
408
|
+
/** A validated subrequest, or why the whole batch is refused. */
|
|
409
|
+
const permanentDeleteOf = (entry) => {
|
|
410
|
+
if (!isJsonObject(entry)) return "each batch request must be an object.";
|
|
411
|
+
const unknown = Object.keys(entry).find((key) => ![
|
|
412
|
+
"id",
|
|
413
|
+
"method",
|
|
414
|
+
"url",
|
|
415
|
+
"headers"
|
|
416
|
+
].includes(key));
|
|
417
|
+
if (unknown !== void 0) return `batch request property '${unknown}' is not emulated (no dependsOn or bodies).`;
|
|
418
|
+
const { id, method, url, headers } = entry;
|
|
419
|
+
if (!Predicate.isString(id) || id.trim() === "") return "each batch request needs an id.";
|
|
420
|
+
if (!Predicate.isString(method) || !Predicate.isString(url)) return "each batch request needs a method and a url.";
|
|
421
|
+
const match = permanentDeletePattern.exec(url);
|
|
422
|
+
if (method.toUpperCase() !== "POST" || match === null) return "only POST /users/{userId}/messages/{messageId}/permanentDelete subrequests are emulated.";
|
|
423
|
+
const userId = decodeSegment(match[1] ?? "");
|
|
424
|
+
const messageId = decodeSegment(match[2] ?? "");
|
|
425
|
+
if (userId === void 0 || messageId === void 0) return "a batch url is not decodable.";
|
|
426
|
+
if (!isJsonObject(headers)) return "batch requests without headers { Prefer: IdType=\"ImmutableId\" } are not emulated.";
|
|
427
|
+
let prefer;
|
|
428
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
429
|
+
if (!allowedSubrequestHeaders.includes(name.toLowerCase()) || !Predicate.isString(value)) return `batch request header '${name}' is not emulated.`;
|
|
430
|
+
prefer = value;
|
|
431
|
+
}
|
|
432
|
+
if (!parsePreferences(prefer ?? null).immutableId) return "batch requests without Prefer: IdType=\"ImmutableId\" are not emulated.";
|
|
433
|
+
return {
|
|
434
|
+
id,
|
|
435
|
+
userId,
|
|
436
|
+
messageId
|
|
437
|
+
};
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* `POST /$batch`: at most 20 subrequests with unique ids, each a message `permanentDelete` with
|
|
441
|
+
* `Prefer: IdType="ImmutableId"`, answered `{ responses: [{ id, status: 204, headers: {} }] }` as
|
|
442
|
+
* the fixtures record. Every subrequest is checked before any runs: anything else (another
|
|
443
|
+
* route, an unknown user or message, a message named twice, a message with a write in flight, a
|
|
444
|
+
* message that is not a draft, or one with attachments) refuses the whole batch with 400 and
|
|
445
|
+
* deletes nothing, because no fixture records a failed subrequest or deletes anything but a
|
|
446
|
+
* case-created draft (which has no attachments).
|
|
447
|
+
*/
|
|
448
|
+
const batch = (state, request, env) => {
|
|
449
|
+
const fields = bodyObject(request, ["requests"]);
|
|
450
|
+
if (fields instanceof Response) return fields;
|
|
451
|
+
const requests = fields.requests;
|
|
452
|
+
if (!Array.isArray(requests) || requests.length === 0 || requests.length > maxBatchRequests) return invalidValue(request, `requests must hold 1 to ${maxBatchRequests} subrequests.`);
|
|
453
|
+
const deletes = [];
|
|
454
|
+
for (const entry of requests) {
|
|
455
|
+
const parsed = permanentDeleteOf(entry);
|
|
456
|
+
if (Predicate.isString(parsed)) return request.error(400, codes.unknownRoute, `Synthetic: ${parsed}`);
|
|
457
|
+
if (deletes.some((existing) => existing.id === parsed.id)) return invalidValue(request, `batch request id ${parsed.id} is used twice.`);
|
|
458
|
+
deletes.push(parsed);
|
|
459
|
+
}
|
|
460
|
+
const messages = [];
|
|
461
|
+
for (const entry of deletes) {
|
|
462
|
+
const itemRequest = {
|
|
463
|
+
...request,
|
|
464
|
+
params: {
|
|
465
|
+
userId: entry.userId,
|
|
466
|
+
messageId: entry.messageId
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
const message = findMessage(state, entry.messageId);
|
|
470
|
+
if (resolveUser(state, itemRequest) instanceof Response) return notEmulated(request, `batch request ${entry.id} names a user that is not emulated.`);
|
|
471
|
+
if (message === void 0 || messages.includes(message)) return notEmulated(request, `batch request ${entry.id} names a missing or repeated message (failed subrequests are not emulated).`);
|
|
472
|
+
if (!message.isDraft) return notEmulated(request, `batch request ${entry.id} names a message that is not a draft (the fixtures delete only drafts).`);
|
|
473
|
+
if (state.attachments.some((attachment) => attachment.messageId === message.id)) return notEmulated(request, `batch request ${entry.id} names a message with attachments (not emulated: no fixture deletes one).`);
|
|
474
|
+
if (env.messageLocks.has(message.id)) return notEmulated(request, `batch request ${entry.id} names a message with a write in flight (failed subrequests are not emulated).`);
|
|
475
|
+
messages.push(message);
|
|
476
|
+
}
|
|
477
|
+
const removed = new Set(messages.map((message) => message.id));
|
|
478
|
+
state.messages = state.messages.filter((message) => !removed.has(message.id));
|
|
479
|
+
return jsonResponse(200, { responses: deletes.map((entry) => ({
|
|
480
|
+
id: entry.id,
|
|
481
|
+
status: 204,
|
|
482
|
+
headers: {}
|
|
483
|
+
})) });
|
|
484
|
+
};
|
|
485
|
+
//#endregion
|
|
486
|
+
export { batch, createDraft, getAttachment, listAttachments, listFolderMessages, moveMessage, updateMessage };
|
|
487
|
+
|
|
488
|
+
//# sourceMappingURL=mail.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.mjs","names":[],"sources":["../../src/microsoft/mail.ts"],"sourcesContent":["/**\n * Microsoft Graph emulator Outlook routes (internal): folder message listing with paging, draft\n * create, message update and move with immutable ids, attachment listing and retrieval, and the\n * JSON `$batch` permanent delete. Wire shapes follow the synthetic Outlook conformance fixtures.\n *\n * Ids: every Outlook fixture request sends `Prefer: IdType=\"ImmutableId\"`, so every Outlook route\n * (and every `$batch` subrequest) needs it; without it the request fails closed (400). Messages\n * have one id, the immutable id, which a move keeps.\n *\n * Writes accept only the fields the fixtures send: a draft create takes `subject`, a text `body`,\n * `toRecipients`, and the owner as `from`; an update takes `subject` and `isRead`; a move takes\n * `destinationId`, which must be `deleteditems`. Anything else, including unknown nested keys,\n * fails closed before any write.\n * The fixtures update, move, and permanently delete only drafts the case created, so those writes\n * to a message that is not a draft are not emulated (400), and neither is permanently deleting a\n * message with attachments (no fixture creates a draft with attachments).\n *\n * Concurrent writes: the first committed write (update or move) holds its message for\n * `conflictWindowMs` before answering (a refused or faulted write commits nothing and holds\n * nothing); an overlapping write to that message gets 409 `ErrorIrresolvableConflict` and changes\n * nothing. Non-overlapping writes both apply (an emulator extrapolation).\n *\n * @experimental\n */\nimport { Predicate } from 'effect'\nimport type * as Schema from 'effect/Schema'\nimport {\n bodyObject,\n codes,\n collection,\n decodeSegment,\n entity,\n invalidValue,\n isJsonObject,\n jsonResponse,\n metadataContext,\n nestedObject,\n nextChangeKey,\n nextLinkOf,\n notEmulated,\n nowTimestamp,\n odataKey,\n padded,\n pageOf,\n parsePreferences,\n project,\n resolveUser,\n selectSuffix,\n selectedFields,\n textBody,\n type HeldAnswer,\n type MicrosoftApiEnv,\n type RouteHandler,\n type RouteRequest\n} from './graph.ts'\nimport type {\n MicrosoftEmulatorAttachment,\n MicrosoftEmulatorMailFolder,\n MicrosoftEmulatorMessage,\n MicrosoftEmulatorRecipient,\n MicrosoftEmulatorState,\n MicrosoftEmulatorUser\n} from './state.ts'\n\nconst messageTagPrefix = 'CQAAABYAAAAsynthetic'\n\nconst messageFields: ReadonlyArray<string> = [\n 'id',\n 'createdDateTime',\n 'lastModifiedDateTime',\n 'changeKey',\n 'categories',\n 'receivedDateTime',\n 'sentDateTime',\n 'hasAttachments',\n 'internetMessageId',\n 'subject',\n 'bodyPreview',\n 'importance',\n 'parentFolderId',\n 'conversationId',\n 'isDeliveryReceiptRequested',\n 'isReadReceiptRequested',\n 'isRead',\n 'isDraft',\n 'webLink',\n 'body',\n 'sender',\n 'from',\n 'toRecipients',\n 'ccRecipients',\n 'bccRecipients',\n 'replyTo',\n 'flag'\n]\n\nconst attachmentListFields: ReadonlyArray<string> = [\n 'id',\n 'name',\n 'contentType',\n 'size',\n 'isInline',\n 'lastModifiedDateTime'\n]\n\nconst notFound = (request: RouteRequest): Response =>\n request.error(404, codes.mailItemNotFound, 'The specified object was not found in the store.')\n\nconst conflict = (request: RouteRequest): Response =>\n request.error(\n 409,\n codes.conflict,\n 'The send or update operation could not be performed because the change key passed in the request does not match the current change key for the item.'\n )\n\nconst recipient = (value: MicrosoftEmulatorRecipient) => ({\n emailAddress: { name: value.name ?? value.address, address: value.address }\n})\n\n/** Plain-text preview: tags stripped, whitespace collapsed, at most 255 characters. */\nconst bodyPreview = (message: MicrosoftEmulatorMessage): string =>\n (message.bodyContentType === 'html'\n ? message.bodyContent.replace(/<[^>]*>/g, ' ')\n : message.bodyContent\n )\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 255)\n\n/** 400 for an Outlook request without `Prefer: IdType=\"ImmutableId\"` (every fixture sends it). */\nconst immutableIdProblem = (request: RouteRequest): Response | undefined =>\n request.prefer.immutableId\n ? undefined\n : notEmulated(\n request,\n 'Outlook requests without Prefer: IdType=\"ImmutableId\" are not emulated.'\n )\n\n/** `users('{userId}')`, the context prefix of every Outlook route. */\nconst userContext = (request: RouteRequest): string =>\n `users${odataKey(request.params.userId ?? '')}`\n\n/** `@odata.context` of a single message (create, update, move), as the fixtures record it. */\nconst messageEntityContext = (env: MicrosoftApiEnv, request: RouteRequest): string =>\n metadataContext(env, `${userContext(request)}/messages/$entity`)\n\n/** The full message, in the draft fixture's key order. */\nconst renderMessage = (message: MicrosoftEmulatorMessage): Schema.JsonObject => {\n const id = message.id\n\n return {\n '@odata.etag': `W/\"${message.changeKey}\"`,\n id,\n createdDateTime: message.createdDateTime,\n lastModifiedDateTime: message.lastModifiedDateTime,\n changeKey: message.changeKey,\n categories: message.categories,\n receivedDateTime: message.receivedDateTime,\n sentDateTime: message.sentDateTime,\n hasAttachments: message.hasAttachments,\n internetMessageId: message.internetMessageId,\n subject: message.subject,\n bodyPreview: bodyPreview(message),\n importance: message.importance,\n parentFolderId: message.parentFolderId,\n conversationId: message.conversationId,\n isDeliveryReceiptRequested: false,\n isReadReceiptRequested: false,\n isRead: message.isRead,\n isDraft: message.isDraft,\n webLink: `https://outlook.office365.com/owa/?ItemID=${encodeURIComponent(id)}&exvsurl=1&viewmodel=ReadMessageItem`,\n body: { contentType: message.bodyContentType, content: message.bodyContent },\n sender: message.sender === null ? null : recipient(message.sender),\n from: message.from === null ? null : recipient(message.from),\n toRecipients: message.toRecipients.map(recipient),\n ccRecipients: message.ccRecipients.map(recipient),\n bccRecipients: message.bccRecipients.map(recipient),\n replyTo: message.replyTo.map(recipient),\n flag: { flagStatus: message.flagStatus }\n }\n}\n\n/** A folder by id (the paging fixture lists a folder by id, never by well-known name). */\nconst folderById = (\n state: MicrosoftEmulatorState,\n id: string\n): MicrosoftEmulatorMailFolder | undefined => state.mailFolders.find(folder => folder.id === id)\n\n/** The seeded folder with a well-known name (internal lookups and the move destination). */\nconst wellKnownFolder = (\n state: MicrosoftEmulatorState,\n name: MicrosoftEmulatorMailFolder['wellKnownName']\n): MicrosoftEmulatorMailFolder | undefined =>\n state.mailFolders.find(folder => folder.wellKnownName === name)\n\n/** The only move destination a fixture sends (`{ destinationId: 'deleteditems' }`). */\nconst moveDestination = 'deleteditems'\n\n/** The largest folder message `$top` a fixture sends (`$top=2`, the paging case). */\nconst folderMessagesMaxTop = 2\n\n/** A message by its (immutable) id. */\nconst findMessage = (\n state: MicrosoftEmulatorState,\n id: string\n): MicrosoftEmulatorMessage | undefined => state.messages.find(message => message.id === id)\n\n/** 400 for a write to a message that is not a draft (the fixtures write only case drafts). */\nconst notDraftProblem = (\n request: RouteRequest,\n message: MicrosoftEmulatorMessage\n): Response | undefined =>\n message.isDraft\n ? undefined\n : notEmulated(\n request,\n 'writes to a message that is not a draft are not emulated (the fixtures write only drafts).'\n )\n\nconst replaceMessage = (state: MicrosoftEmulatorState, updated: MicrosoftEmulatorMessage) => {\n state.messages = state.messages.map(message => (message.id === updated.id ? updated : message))\n}\n\n/**\n * `GET /users/{userId}/mailFolders/{folderId}/messages`: newest `receivedDateTime` first, one\n * `$top`/`$skip` page with an opaque `@odata.nextLink` on the configured Graph origin. The folder\n * is named by id (a well-known name or an unknown id is not emulated), and `$top` is required, at\n * most `folderMessagesMaxTop`, as the paging fixture sends it.\n */\nexport const listFolderMessages: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const immutable = immutableIdProblem(request)\n\n if (immutable !== undefined) return immutable\n\n const folder = folderById(state, request.params.folderId ?? '')\n\n if (folder === undefined) {\n return notEmulated(\n request,\n 'listing a folder by well-known name or an unknown folder id is not emulated (the paging fixture lists a folder by id).'\n )\n }\n\n const fields = selectedFields(request, messageFields)\n\n if (fields instanceof Response) return fields\n\n const messages = state.messages\n .filter(message => message.parentFolderId === folder.id)\n .sort((left, right) =>\n left.receivedDateTime === right.receivedDateTime\n ? left.id.localeCompare(right.id)\n : left.receivedDateTime < right.receivedDateTime\n ? 1\n : -1\n )\n\n const page = pageOf(messages, request, folderMessagesMaxTop)\n\n if (page instanceof Response) return page\n\n const context = metadataContext(\n env,\n `${userContext(request)}/mailFolders${odataKey(request.params.folderId ?? '')}/messages${selectSuffix(fields)}`\n )\n\n return jsonResponse(\n 200,\n collection(\n context,\n page.items.map(message => project(renderMessage(message), fields)),\n nextLinkOf(env, request, page, ['$select'])\n )\n )\n}\n\n/**\n * A `recipient` write value, `{ emailAddress: { address, name? } }` with only those keys (the\n * shape the fixture responses show); anything else fails closed.\n */\nconst recipientOf = (\n request: RouteRequest,\n key: string,\n value: Schema.Json\n): MicrosoftEmulatorRecipient | Response => {\n const expected = `${key} { emailAddress: { address, name? } }`\n const recipient = nestedObject(request, value, ['emailAddress'], expected)\n\n if (recipient instanceof Response) return recipient\n\n const email = nestedObject(request, recipient.emailAddress, ['address', 'name'], expected)\n\n if (email instanceof Response) return email\n\n const { address, name } = email\n\n if (!Predicate.isString(address) || (name !== undefined && !Predicate.isString(name))) {\n return invalidValue(request, `${expected} needs a string address (and name).`)\n }\n\n return { name: name ?? null, address }\n}\n\n/** `[recipient]` as stored recipients. */\nconst recipientsOf = (\n request: RouteRequest,\n key: string,\n value: Schema.Json\n): ReadonlyArray<MicrosoftEmulatorRecipient> | Response => {\n if (!Array.isArray(value)) return invalidValue(request, `${key} must be an array.`)\n\n const recipients: Array<MicrosoftEmulatorRecipient> = []\n\n for (const entry of value) {\n const recipient = recipientOf(request, key, entry)\n\n if (recipient instanceof Response) return recipient\n\n recipients.push(recipient)\n }\n\n return recipients\n}\n\ntype MessageWrite = {\n subject?: string\n bodyContent?: string\n toRecipients?: ReadonlyArray<MicrosoftEmulatorRecipient>\n isRead?: boolean\n}\n\n/** Draft create keys, as the Outlook fixtures send them. */\nconst draftKeys: ReadonlyArray<string> = ['subject', 'body', 'toRecipients', 'from']\n\n/** Message update keys, as the Outlook fixtures send them. */\nconst updateKeys: ReadonlyArray<string> = ['subject', 'isRead']\n\n/** The emulated message fields of a create or update body (keys already allowlisted). */\nconst messageWrite = (\n request: RouteRequest,\n fields: Schema.JsonObject,\n user: MicrosoftEmulatorUser\n): MessageWrite | Response => {\n const write: MessageWrite = {}\n\n for (const [key, value] of Object.entries(fields)) {\n switch (key) {\n case 'subject':\n if (!Predicate.isString(value)) return invalidValue(request, 'subject must be a string.')\n\n write.subject = value\n break\n case 'body': {\n const content = textBody(request, value)\n\n if (content instanceof Response) return content\n\n write.bodyContent = content\n break\n }\n\n case 'toRecipients': {\n const recipients = recipientsOf(request, key, value)\n\n if (recipients instanceof Response) return recipients\n\n write.toRecipients = recipients\n break\n }\n\n case 'from': {\n const from = recipientOf(request, key, value)\n\n if (from instanceof Response) return from\n\n if (from.address.toLowerCase() !== user.mail.toLowerCase()) {\n return notEmulated(request, 'from must be the mailbox owner (send-as is not emulated).')\n }\n\n break\n }\n\n case 'isRead':\n if (!Predicate.isBoolean(value)) return invalidValue(request, 'isRead must be a boolean.')\n\n write.isRead = value\n break\n }\n }\n\n return write\n}\n\n/**\n * `POST /users/{userId}/messages`: a new draft in Drafts (201 with the message). Never sent. New\n * drafts have no attachments (`hasAttachments: false`, as the fixtures record).\n */\nexport const createDraft: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const immutable = immutableIdProblem(request)\n\n if (immutable !== undefined) return immutable\n\n const fields = bodyObject(request, draftKeys)\n\n if (fields instanceof Response) return fields\n\n const write = messageWrite(request, fields, user)\n\n if (write instanceof Response) return write\n\n const drafts = wellKnownFolder(state, 'drafts')\n\n if (drafts === undefined) return notFound(request)\n\n let number = state.counters.nextMessageNumber\n let id = `AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0A-synthetic-immutable-${padded(number, 4)}=`\n\n while (findMessage(state, id) !== undefined) {\n number += 1\n id = `AAkALgAAAAAAHYQDEapmEc2byACqAC-EWg0A-synthetic-immutable-${padded(number, 4)}=`\n }\n\n state.counters = { ...state.counters, nextMessageNumber: number + 1 }\n\n const now = nowTimestamp(env)\n const owner: MicrosoftEmulatorRecipient = { name: user.displayName, address: user.mail }\n\n const message: MicrosoftEmulatorMessage = {\n id,\n parentFolderId: drafts.id,\n subject: write.subject ?? '',\n bodyContentType: 'text',\n bodyContent: write.bodyContent ?? '',\n from: owner,\n sender: owner,\n toRecipients: write.toRecipients ?? [],\n ccRecipients: [],\n bccRecipients: [],\n replyTo: [],\n isRead: true,\n isDraft: true,\n importance: 'normal',\n flagStatus: 'notFlagged',\n categories: [],\n hasAttachments: false,\n createdDateTime: now,\n lastModifiedDateTime: now,\n receivedDateTime: now,\n sentDateTime: now,\n conversationId: `AAQkAGI2-synthetic-conversation-draft-${padded(number, 4)}=`,\n internetMessageId: `<synthetic-draft-${padded(number, 4)}@example.test>`,\n changeKey: nextChangeKey(state, messageTagPrefix)\n }\n\n state.messages = [...state.messages, message]\n\n return jsonResponse(201, entity(messageEntityContext(env, request), renderMessage(message)))\n}\n\n/**\n * A message write that will hold its message: 409 (nothing changes) when the message is held by\n * another write; otherwise the write runs (on the draft) and its answer names the message to hold.\n * The route registration takes the hold only once the write is committed, then keeps it for\n * `conflictWindowMs` before answering, so a refused or faulted write holds nothing.\n */\nconst heldWrite = (\n env: MicrosoftApiEnv,\n request: RouteRequest,\n message: MicrosoftEmulatorMessage,\n commit: () => Response\n): Response | HeldAnswer =>\n env.messageLocks.has(message.id) ? conflict(request) : { response: commit(), holds: message.id }\n\n/**\n * `PATCH /users/{userId}/messages/{messageId}`: update a draft's `subject` or `isRead`; 200 with\n * the message.\n */\nexport const updateMessage: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const immutable = immutableIdProblem(request)\n\n if (immutable !== undefined) return immutable\n\n const message = findMessage(state, request.params.messageId ?? '')\n\n if (message === undefined) return notFound(request)\n\n const notDraft = notDraftProblem(request, message)\n\n if (notDraft !== undefined) return notDraft\n\n const fields = bodyObject(request, updateKeys)\n\n if (fields instanceof Response) return fields\n\n const write = messageWrite(request, fields, user)\n\n if (write instanceof Response) return write\n\n return heldWrite(env, request, message, () => {\n const updated: MicrosoftEmulatorMessage = {\n ...message,\n ...write,\n lastModifiedDateTime: nowTimestamp(env),\n changeKey: nextChangeKey(state, messageTagPrefix)\n }\n\n replaceMessage(state, updated)\n\n return jsonResponse(200, entity(messageEntityContext(env, request), renderMessage(updated)))\n })\n}\n\n/**\n * `POST /users/{userId}/messages/{messageId}/move` with `{ destinationId: 'deleteditems' }`, the\n * only destination a fixture sends: 201 with the moved draft, which keeps its (immutable) id. Any\n * other destination (another well-known name such as `inbox` or `drafts`, or a folder id) is not\n * emulated (400) and moves nothing.\n */\nexport const moveMessage: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const immutable = immutableIdProblem(request)\n\n if (immutable !== undefined) return immutable\n\n const message = findMessage(state, request.params.messageId ?? '')\n\n if (message === undefined) return notFound(request)\n\n const notDraft = notDraftProblem(request, message)\n\n if (notDraft !== undefined) return notDraft\n\n const fields = bodyObject(request, ['destinationId'])\n\n if (fields instanceof Response) return fields\n\n if (!Predicate.isString(fields.destinationId)) {\n return invalidValue(request, 'destinationId must be a string (only deleteditems is emulated).')\n }\n\n if (fields.destinationId !== moveDestination) {\n return notEmulated(\n request,\n `moves to destinations other than ${moveDestination} are not emulated (the fixtures move only to Deleted Items).`\n )\n }\n\n // Every valid seed has a Deleted Items folder (the seed check requires it).\n const destination = wellKnownFolder(state, moveDestination)\n\n if (destination === undefined) return notFound(request)\n\n return heldWrite(env, request, message, () => {\n const moved: MicrosoftEmulatorMessage = {\n ...message,\n parentFolderId: destination.id,\n lastModifiedDateTime: nowTimestamp(env),\n changeKey: nextChangeKey(state, messageTagPrefix)\n }\n\n replaceMessage(state, moved)\n\n return jsonResponse(201, entity(messageEntityContext(env, request), renderMessage(moved)))\n })\n}\n\nconst attachmentBase = (attachment: MicrosoftEmulatorAttachment) => ({\n '@odata.type': '#microsoft.graph.fileAttachment',\n '@odata.mediaContentType': attachment.contentType,\n id: attachment.id\n})\n\n/** A listed attachment: base `attachment` properties only (no `contentId`), as the fixture shows. */\nconst renderListedAttachment = (attachment: MicrosoftEmulatorAttachment) => ({\n ...attachmentBase(attachment),\n name: attachment.name,\n contentType: attachment.contentType,\n size: attachment.size,\n isInline: attachment.isInline,\n lastModifiedDateTime: attachment.lastModifiedDateTime\n})\n\n/** A retrieved file attachment, with `contentId` and `contentBytes`, in the fixture key order. */\nconst renderAttachment = (attachment: MicrosoftEmulatorAttachment) => ({\n ...attachmentBase(attachment),\n lastModifiedDateTime: attachment.lastModifiedDateTime,\n name: attachment.name,\n contentType: attachment.contentType,\n size: attachment.size,\n isInline: attachment.isInline,\n contentId: attachment.contentId,\n contentLocation: attachment.contentLocation,\n contentBytes: attachment.contentBytes\n})\n\n/**\n * `GET /users/{userId}/messages/{messageId}/attachments`: every attachment, inline ones included,\n * in one response (the fixtures send only `$select`: no `$top`, `$skip`, or `@odata.nextLink`).\n * `$select` accepts only base `attachment` properties, so a listing never carries `contentId`.\n */\nexport const listAttachments: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const immutable = immutableIdProblem(request)\n\n if (immutable !== undefined) return immutable\n\n const message = findMessage(state, request.params.messageId ?? '')\n\n if (message === undefined) return notFound(request)\n\n const fields = selectedFields(request, attachmentListFields)\n\n if (fields instanceof Response) return fields\n\n const attachments = state.attachments.filter(attachment => attachment.messageId === message.id)\n\n const context = metadataContext(\n env,\n `${userContext(request)}/messages${odataKey(request.params.messageId ?? '')}/attachments${selectSuffix(fields)}`\n )\n\n return jsonResponse(\n 200,\n collection(\n context,\n attachments.map(attachment => project(renderListedAttachment(attachment), fields)),\n undefined\n )\n )\n}\n\n/** `GET /users/{userId}/messages/{messageId}/attachments/{attachmentId}`: the file attachment. */\nexport const getAttachment: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const immutable = immutableIdProblem(request)\n\n if (immutable !== undefined) return immutable\n\n const message = findMessage(state, request.params.messageId ?? '')\n\n const attachment =\n message === undefined\n ? undefined\n : state.attachments.find(\n candidate =>\n candidate.messageId === message.id && candidate.id === request.params.attachmentId\n )\n\n const context = metadataContext(\n env,\n `${userContext(request)}/messages${odataKey(request.params.messageId ?? '')}/attachments/$entity`\n )\n\n return attachment === undefined\n ? notFound(request)\n : jsonResponse(200, entity(context, renderAttachment(attachment)))\n}\n\n// JSON batching: only permanent-delete subrequests are emulated.\n\nconst maxBatchRequests = 20\n\nconst permanentDeletePattern = /^\\/?users\\/([^/?#]+)\\/messages\\/([^/?#]+)\\/permanentDelete$/\n\n/** Subrequest headers the fixtures send (`Prefer`, which must ask for immutable ids). */\nconst allowedSubrequestHeaders: ReadonlyArray<string> = ['prefer']\n\ntype PermanentDelete = {\n readonly id: string\n readonly userId: string\n readonly messageId: string\n}\n\n/** A validated subrequest, or why the whole batch is refused. */\nconst permanentDeleteOf = (entry: Schema.Json): PermanentDelete | string => {\n if (!isJsonObject(entry)) return 'each batch request must be an object.'\n\n const unknown = Object.keys(entry).find(key => !['id', 'method', 'url', 'headers'].includes(key))\n\n if (unknown !== undefined) {\n return `batch request property '${unknown}' is not emulated (no dependsOn or bodies).`\n }\n\n const { id, method, url, headers } = entry\n\n if (!Predicate.isString(id) || id.trim() === '') return 'each batch request needs an id.'\n\n if (!Predicate.isString(method) || !Predicate.isString(url)) {\n return 'each batch request needs a method and a url.'\n }\n\n const match = permanentDeletePattern.exec(url)\n\n if (method.toUpperCase() !== 'POST' || match === null) {\n return 'only POST /users/{userId}/messages/{messageId}/permanentDelete subrequests are emulated.'\n }\n\n const userId = decodeSegment(match[1] ?? '')\n const messageId = decodeSegment(match[2] ?? '')\n\n if (userId === undefined || messageId === undefined) return 'a batch url is not decodable.'\n\n if (!isJsonObject(headers)) {\n return 'batch requests without headers { Prefer: IdType=\"ImmutableId\" } are not emulated.'\n }\n\n let prefer: string | undefined\n\n for (const [name, value] of Object.entries(headers)) {\n if (!allowedSubrequestHeaders.includes(name.toLowerCase()) || !Predicate.isString(value)) {\n return `batch request header '${name}' is not emulated.`\n }\n\n prefer = value\n }\n\n if (!parsePreferences(prefer ?? null).immutableId) {\n return 'batch requests without Prefer: IdType=\"ImmutableId\" are not emulated.'\n }\n\n return { id, userId, messageId }\n}\n\n/**\n * `POST /$batch`: at most 20 subrequests with unique ids, each a message `permanentDelete` with\n * `Prefer: IdType=\"ImmutableId\"`, answered `{ responses: [{ id, status: 204, headers: {} }] }` as\n * the fixtures record. Every subrequest is checked before any runs: anything else (another\n * route, an unknown user or message, a message named twice, a message with a write in flight, a\n * message that is not a draft, or one with attachments) refuses the whole batch with 400 and\n * deletes nothing, because no fixture records a failed subrequest or deletes anything but a\n * case-created draft (which has no attachments).\n */\nexport const batch: RouteHandler = (state, request, env) => {\n const fields = bodyObject(request, ['requests'])\n\n if (fields instanceof Response) return fields\n\n const requests = fields.requests\n\n if (!Array.isArray(requests) || requests.length === 0 || requests.length > maxBatchRequests) {\n return invalidValue(request, `requests must hold 1 to ${maxBatchRequests} subrequests.`)\n }\n\n const deletes: Array<PermanentDelete> = []\n\n for (const entry of requests) {\n const parsed = permanentDeleteOf(entry)\n\n if (Predicate.isString(parsed)) {\n return request.error(400, codes.unknownRoute, `Synthetic: ${parsed}`)\n }\n\n if (deletes.some(existing => existing.id === parsed.id)) {\n return invalidValue(request, `batch request id ${parsed.id} is used twice.`)\n }\n\n deletes.push(parsed)\n }\n\n const messages: Array<MicrosoftEmulatorMessage> = []\n\n for (const entry of deletes) {\n const itemRequest: RouteRequest = {\n ...request,\n params: { userId: entry.userId, messageId: entry.messageId }\n }\n\n const message = findMessage(state, entry.messageId)\n\n if (resolveUser(state, itemRequest) instanceof Response) {\n return notEmulated(request, `batch request ${entry.id} names a user that is not emulated.`)\n }\n\n if (message === undefined || messages.includes(message)) {\n return notEmulated(\n request,\n `batch request ${entry.id} names a missing or repeated message (failed subrequests are not emulated).`\n )\n }\n\n if (!message.isDraft) {\n return notEmulated(\n request,\n `batch request ${entry.id} names a message that is not a draft (the fixtures delete only drafts).`\n )\n }\n\n if (state.attachments.some(attachment => attachment.messageId === message.id)) {\n return notEmulated(\n request,\n `batch request ${entry.id} names a message with attachments (not emulated: no fixture deletes one).`\n )\n }\n\n if (env.messageLocks.has(message.id)) {\n return notEmulated(\n request,\n `batch request ${entry.id} names a message with a write in flight (failed subrequests are not emulated).`\n )\n }\n\n messages.push(message)\n }\n\n const removed = new Set(messages.map(message => message.id))\n\n state.messages = state.messages.filter(message => !removed.has(message.id))\n\n return jsonResponse(200, {\n responses: deletes.map(entry => ({ id: entry.id, status: 204, headers: {} }))\n })\n}\n"],"mappings":"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"}
|