@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,178 @@
|
|
|
1
|
+
import { answeredOutsideCore, handlerFailedResponse } from "../emulator-http.mjs";
|
|
2
|
+
import { codes, decodeSegment, errorContext, graphError, microsoftEmulatorBasePath, parseJsonText, parsePreferences } from "./graph.mjs";
|
|
3
|
+
import { calendarView, cancelEvent, createEvent, deleteEvent, getEvent, updateEvent } from "./calendar.mjs";
|
|
4
|
+
import { copyItem, copyMonitor, createFolder, deleteItem, getItem, listChildren } from "./drive.mjs";
|
|
5
|
+
import { batch, createDraft, getAttachment, listAttachments, listFolderMessages, moveMessage, updateMessage } from "./mail.mjs";
|
|
6
|
+
//#region src/microsoft/api.ts
|
|
7
|
+
const listRangeCase = "microsoft.calendar.list-range-returns-events";
|
|
8
|
+
const precisionCase = "microsoft.calendar.timestamp-precision";
|
|
9
|
+
const createEventCase = "microsoft.calendar.create-returns-event-id";
|
|
10
|
+
const cancelCase = "microsoft.calendar.cancel-semantics";
|
|
11
|
+
const attachmentsCase = "microsoft.outlook.attachments-listing";
|
|
12
|
+
const contentIdCase = "microsoft.outlook.attachment-content-id";
|
|
13
|
+
const pagingCase = "microsoft.outlook.paging-next-link";
|
|
14
|
+
const immutableIdCase = "microsoft.outlook.immutable-id-survives-move";
|
|
15
|
+
const concurrentCase = "microsoft.outlook.concurrent-writes-same-message";
|
|
16
|
+
const folderCase = "microsoft.onedrive.create-folder-roundtrip";
|
|
17
|
+
const copyCase = "microsoft.onedrive.copy-accepted-monitor";
|
|
18
|
+
const route = (method, path, write, caseIds, handler, queryKeys = [], auth = true) => ({
|
|
19
|
+
method,
|
|
20
|
+
path,
|
|
21
|
+
kind: "connector",
|
|
22
|
+
write,
|
|
23
|
+
caseIds,
|
|
24
|
+
evidence: "unverified",
|
|
25
|
+
queryKeys,
|
|
26
|
+
auth,
|
|
27
|
+
handler
|
|
28
|
+
});
|
|
29
|
+
const graph = (path) => `${microsoftEmulatorBasePath}${path}`;
|
|
30
|
+
/** Folder message listing: the paging fixture's `$select`, `$top`, and `$skip`. */
|
|
31
|
+
const paging = [
|
|
32
|
+
"$select",
|
|
33
|
+
"$top",
|
|
34
|
+
"$skip"
|
|
35
|
+
];
|
|
36
|
+
/** Calendar views and children listings: their fixtures send `$select` and `$top`, never `$skip`. */
|
|
37
|
+
const onePage = ["$select", "$top"];
|
|
38
|
+
/** The route table: evidence plus handler. `microsoftEmulatorRoutes` is its evidence part. */
|
|
39
|
+
const microsoftApiRoutes = [
|
|
40
|
+
route("GET", graph("/users/{userId}/calendars/{calendarId}/calendarView"), false, [listRangeCase, precisionCase], calendarView, [
|
|
41
|
+
"startDateTime",
|
|
42
|
+
"endDateTime",
|
|
43
|
+
...onePage
|
|
44
|
+
]),
|
|
45
|
+
route("POST", graph("/users/{userId}/calendars/{calendarId}/events"), true, [createEventCase, cancelCase], createEvent),
|
|
46
|
+
route("GET", graph("/users/{userId}/events/{eventId}"), false, [
|
|
47
|
+
precisionCase,
|
|
48
|
+
createEventCase,
|
|
49
|
+
cancelCase
|
|
50
|
+
], getEvent, ["$select"]),
|
|
51
|
+
route("PATCH", graph("/users/{userId}/events/{eventId}"), true, [createEventCase], updateEvent),
|
|
52
|
+
route("DELETE", graph("/users/{userId}/events/{eventId}"), true, [createEventCase, cancelCase], deleteEvent),
|
|
53
|
+
route("POST", graph("/users/{userId}/events/{eventId}/cancel"), true, [cancelCase], cancelEvent),
|
|
54
|
+
route("GET", graph("/users/{userId}/mailFolders/{folderId}/messages"), false, [pagingCase], listFolderMessages, paging),
|
|
55
|
+
route("POST", graph("/users/{userId}/messages"), true, [immutableIdCase, concurrentCase], createDraft),
|
|
56
|
+
route("PATCH", graph("/users/{userId}/messages/{messageId}"), true, [immutableIdCase, concurrentCase], updateMessage),
|
|
57
|
+
route("POST", graph("/users/{userId}/messages/{messageId}/move"), true, [immutableIdCase], moveMessage),
|
|
58
|
+
route("GET", graph("/users/{userId}/messages/{messageId}/attachments"), false, [attachmentsCase, contentIdCase], listAttachments, ["$select"]),
|
|
59
|
+
route("GET", graph("/users/{userId}/messages/{messageId}/attachments/{attachmentId}"), false, [attachmentsCase, contentIdCase], getAttachment),
|
|
60
|
+
route("POST", graph("/$batch"), true, [immutableIdCase, concurrentCase], batch),
|
|
61
|
+
route("GET", graph("/drives/{driveId}/items/{itemId}"), false, [folderCase, copyCase], getItem, ["$select"]),
|
|
62
|
+
route("GET", graph("/drives/{driveId}/items/{itemId}/children"), false, [folderCase, copyCase], listChildren, onePage),
|
|
63
|
+
route("POST", graph("/drives/{driveId}/items/{itemId}/children"), true, [folderCase, copyCase], createFolder),
|
|
64
|
+
route("DELETE", graph("/drives/{driveId}/items/{itemId}"), true, [folderCase, copyCase], deleteItem),
|
|
65
|
+
route("POST", graph("/drives/{driveId}/items/{itemId}/copy"), true, [copyCase], copyItem, ["@microsoft.graph.conflictBehavior"]),
|
|
66
|
+
route("GET", "/personal/{site}/_api/v2.0/monitor/{monitorId}", false, [copyCase], copyMonitor, [], false)
|
|
67
|
+
];
|
|
68
|
+
const templatePattern = (template) => new RegExp(`^${template.split(/(\{[A-Za-z]+\})/).map((part) => /^\{[A-Za-z]+\}$/.test(part) ? "([^/]+)" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("")}$`);
|
|
69
|
+
const templateNames = (template) => [...template.matchAll(/\{([A-Za-z]+)\}/g)].map((match) => match[1] ?? "");
|
|
70
|
+
const compiledRoutes = microsoftApiRoutes.map((candidate) => ({
|
|
71
|
+
route: candidate,
|
|
72
|
+
pattern: templatePattern(candidate.path),
|
|
73
|
+
names: templateNames(candidate.path)
|
|
74
|
+
}));
|
|
75
|
+
/**
|
|
76
|
+
* The route answering `method` + raw (percent-encoded) `path` with its decoded parameters, or
|
|
77
|
+
* `undefined` (fail closed; also for a parameter that is not valid percent-encoding).
|
|
78
|
+
*/
|
|
79
|
+
const matchMicrosoftRoute = (method, path) => {
|
|
80
|
+
for (const candidate of compiledRoutes) {
|
|
81
|
+
if (candidate.route.method !== method.toUpperCase()) continue;
|
|
82
|
+
const match = candidate.pattern.exec(path);
|
|
83
|
+
if (match === null) continue;
|
|
84
|
+
const params = {};
|
|
85
|
+
for (const [index, name] of candidate.names.entries()) {
|
|
86
|
+
const value = decodeSegment(match[index + 1] ?? "");
|
|
87
|
+
if (value === void 0) return void 0;
|
|
88
|
+
params[name] = value;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
route: candidate.route,
|
|
92
|
+
params
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/** Header the wrapper sets on core requests: the ledger sequence number (for error ids). */
|
|
97
|
+
const requestSeqHeader = "x-emulator-request-seq";
|
|
98
|
+
const delay = (ms) => new Promise((resolve) => {
|
|
99
|
+
setTimeout(resolve, ms);
|
|
100
|
+
});
|
|
101
|
+
/** The first query key the route does not emulate, or `undefined`. */
|
|
102
|
+
const unsupportedQueryKey = (query, allowed) => [...query.keys()].find((candidate) => !allowed.includes(candidate));
|
|
103
|
+
/** `{Name}` path templates become `:Name` core route parameters. */
|
|
104
|
+
const corePath = (template) => template.replace(/\{([A-Za-z]+)\}/g, ":$1");
|
|
105
|
+
/**
|
|
106
|
+
* One core request: re-match, check the query allowlist and `If-Match`, then plan, fault, and
|
|
107
|
+
* commit. The handler runs on drafts: a shallow copy of the state (handlers replace whole lists
|
|
108
|
+
* and counters, never edit them in place) and copies of the copy monitors and their counter. A
|
|
109
|
+
* refusal (any answer that is not 2xx) is sent as is and uses up no fault; a successful answer asks
|
|
110
|
+
* `decideFault`, and only an unfaulted one commits the drafts (and then holds a written message
|
|
111
|
+
* for `conflictWindowMs`). Plan, fault decision, and commit run synchronously together, so no
|
|
112
|
+
* other request interleaves.
|
|
113
|
+
*/
|
|
114
|
+
const handle = async (raw, state, env, decideFault) => {
|
|
115
|
+
const url = new URL(raw.url);
|
|
116
|
+
const headers = raw.headers;
|
|
117
|
+
const seq = Number(headers.get("x-emulator-request-seq") ?? "0");
|
|
118
|
+
const error = (status, code, message) => graphError(errorContext(env.now(), Number.isSafeInteger(seq) ? seq : 0, headers.get("client-request-id")), status, code, message);
|
|
119
|
+
const matched = matchMicrosoftRoute(raw.method, url.pathname);
|
|
120
|
+
if (matched === void 0) return error(404, codes.unknownRoute, "Synthetic: no emulated Microsoft Graph route.");
|
|
121
|
+
const unsupported = unsupportedQueryKey(url.searchParams, matched.route.queryKeys);
|
|
122
|
+
if (unsupported !== void 0) return error(400, codes.unsupportedQuery, `Synthetic: query parameter ${unsupported} is not emulated on this route.`);
|
|
123
|
+
if (headers.has("if-match")) return error(400, codes.unsupportedValue, "Synthetic: conditional requests (If-Match) are not emulated.");
|
|
124
|
+
const text = await raw.text();
|
|
125
|
+
const request = {
|
|
126
|
+
params: matched.params,
|
|
127
|
+
path: url.pathname,
|
|
128
|
+
query: url.searchParams,
|
|
129
|
+
body: text === "" ? void 0 : parseJsonText(text),
|
|
130
|
+
prefer: parsePreferences(headers.get("prefer")),
|
|
131
|
+
error
|
|
132
|
+
};
|
|
133
|
+
const draft = { ...state };
|
|
134
|
+
const monitors = new Map([...env.monitors].map(([id, monitor]) => [id, { ...monitor }]));
|
|
135
|
+
const monitorCounter = { next: env.monitorCounter.next };
|
|
136
|
+
const answer = matched.route.handler(draft, request, {
|
|
137
|
+
...env,
|
|
138
|
+
monitors,
|
|
139
|
+
monitorCounter
|
|
140
|
+
});
|
|
141
|
+
const response = answer instanceof Response ? answer : answer.response;
|
|
142
|
+
if (!response.ok) return response;
|
|
143
|
+
if (decideFault(headers.get("x-emulator-job-id"))) return answeredOutsideCore();
|
|
144
|
+
Object.assign(state, draft);
|
|
145
|
+
env.monitors.clear();
|
|
146
|
+
for (const [id, monitor] of monitors) env.monitors.set(id, monitor);
|
|
147
|
+
env.monitorCounter.next = monitorCounter.next;
|
|
148
|
+
if (answer instanceof Response) return answer;
|
|
149
|
+
env.messageLocks.add(answer.holds);
|
|
150
|
+
try {
|
|
151
|
+
if (env.conflictWindowMs > 0) await delay(env.conflictWindowMs);
|
|
152
|
+
return answer.response;
|
|
153
|
+
} finally {
|
|
154
|
+
env.messageLocks.delete(answer.holds);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Register every route of the table on the core app, over the generation's state. Each handler
|
|
159
|
+
* re-matches the raw request path against the same table the wrapper ledgers (so parameters are
|
|
160
|
+
* decoded once, failing closed on invalid percent-encoding), checks the route's query allowlist
|
|
161
|
+
* and refuses `If-Match` (no fixture sends one) before the handler runs, then plans, faults, and
|
|
162
|
+
* commits (see `handle`). A handler that throws answers `handlerFailedResponse()` (nothing
|
|
163
|
+
* written), which the wrapper turns into its Graph error envelope 500 with `responseError` in the
|
|
164
|
+
* ledger.
|
|
165
|
+
*/
|
|
166
|
+
const registerMicrosoftApi = (app, state, env, decideFault) => {
|
|
167
|
+
for (const apiRoute of microsoftApiRoutes) app.on(apiRoute.method, corePath(apiRoute.path), async (context) => {
|
|
168
|
+
try {
|
|
169
|
+
return await handle(context.req.raw, state, env, decideFault);
|
|
170
|
+
} catch {
|
|
171
|
+
return handlerFailedResponse();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
176
|
+
export { matchMicrosoftRoute, microsoftApiRoutes, microsoftEmulatorBasePath, registerMicrosoftApi, requestSeqHeader };
|
|
177
|
+
|
|
178
|
+
//# sourceMappingURL=api.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/microsoft/api.ts"],"sourcesContent":["/**\n * Microsoft Graph emulator API: the route table (with its evidence and query allowlist), route\n * matching, and the registration of the stateful handlers on the `@emulators/core` app\n * (internal; re-exported by `src/microsoft.ts`).\n *\n * Only the routes the eleven Microsoft conformance cases need are emulated, plus the copy monitor\n * URL they require; everything else fails closed. Each route lists the cases whose claims it\n * follows.\n *\n * @experimental\n */\nimport type { Hono } from '@emulators/core'\nimport { answeredOutsideCore, emulatorJobHeader, handlerFailedResponse } from '../emulator-http.ts'\nimport type { EmulatorRouteEvidence } from '../route-evidence.ts'\nimport {\n calendarView,\n cancelEvent,\n createEvent,\n deleteEvent,\n getEvent,\n updateEvent\n} from './calendar.ts'\nimport { copyItem, copyMonitor, createFolder, deleteItem, getItem, listChildren } from './drive.ts'\nimport {\n codes,\n decodeSegment,\n errorContext,\n graphError,\n microsoftEmulatorBasePath,\n parseJsonText,\n parsePreferences,\n type MicrosoftApiEnv,\n type RouteHandler,\n type RouteRequest\n} from './graph.ts'\nimport {\n batch,\n createDraft,\n getAttachment,\n listAttachments,\n listFolderMessages,\n moveMessage,\n updateMessage\n} from './mail.ts'\nimport type { MicrosoftEmulatorState } from './state.ts'\n\nexport { microsoftEmulatorBasePath } from './graph.ts'\n\ntype MicrosoftApiRoute = EmulatorRouteEvidence & {\n /**\n * Query parameters the route emulates; any other key answers 400 before the handler runs, so\n * a rejected request never writes. Empty by default.\n */\n readonly queryKeys: ReadonlyArray<string>\n /** `false` only for the copy monitor, a pre-authenticated capability URL. */\n readonly auth: boolean\n readonly handler: RouteHandler\n}\n\nconst listRangeCase = 'microsoft.calendar.list-range-returns-events'\n\nconst precisionCase = 'microsoft.calendar.timestamp-precision'\n\nconst createEventCase = 'microsoft.calendar.create-returns-event-id'\n\nconst cancelCase = 'microsoft.calendar.cancel-semantics'\n\nconst attachmentsCase = 'microsoft.outlook.attachments-listing'\n\nconst contentIdCase = 'microsoft.outlook.attachment-content-id'\n\nconst pagingCase = 'microsoft.outlook.paging-next-link'\n\nconst immutableIdCase = 'microsoft.outlook.immutable-id-survives-move'\n\nconst concurrentCase = 'microsoft.outlook.concurrent-writes-same-message'\n\nconst folderCase = 'microsoft.onedrive.create-folder-roundtrip'\n\nconst copyCase = 'microsoft.onedrive.copy-accepted-monitor'\n\nconst route = (\n method: string,\n path: string,\n write: boolean,\n caseIds: ReadonlyArray<string>,\n handler: RouteHandler,\n queryKeys: ReadonlyArray<string> = [],\n auth = true\n): MicrosoftApiRoute => ({\n method,\n path,\n kind: 'connector',\n write,\n caseIds,\n evidence: 'unverified',\n queryKeys,\n auth,\n handler\n})\n\nconst graph = (path: string) => `${microsoftEmulatorBasePath}${path}`\n\n/** Folder message listing: the paging fixture's `$select`, `$top`, and `$skip`. */\nconst paging: ReadonlyArray<string> = ['$select', '$top', '$skip']\n\n/** Calendar views and children listings: their fixtures send `$select` and `$top`, never `$skip`. */\nconst onePage: ReadonlyArray<string> = ['$select', '$top']\n\n/** The route table: evidence plus handler. `microsoftEmulatorRoutes` is its evidence part. */\nexport const microsoftApiRoutes: ReadonlyArray<MicrosoftApiRoute> = [\n route(\n 'GET',\n graph('/users/{userId}/calendars/{calendarId}/calendarView'),\n false,\n [listRangeCase, precisionCase],\n calendarView,\n ['startDateTime', 'endDateTime', ...onePage]\n ),\n route(\n 'POST',\n graph('/users/{userId}/calendars/{calendarId}/events'),\n true,\n [createEventCase, cancelCase],\n createEvent\n ),\n route(\n 'GET',\n graph('/users/{userId}/events/{eventId}'),\n false,\n [precisionCase, createEventCase, cancelCase],\n getEvent,\n ['$select']\n ),\n route('PATCH', graph('/users/{userId}/events/{eventId}'), true, [createEventCase], updateEvent),\n route(\n 'DELETE',\n graph('/users/{userId}/events/{eventId}'),\n true,\n [createEventCase, cancelCase],\n deleteEvent\n ),\n route('POST', graph('/users/{userId}/events/{eventId}/cancel'), true, [cancelCase], cancelEvent),\n route(\n 'GET',\n graph('/users/{userId}/mailFolders/{folderId}/messages'),\n false,\n [pagingCase],\n listFolderMessages,\n paging\n ),\n route(\n 'POST',\n graph('/users/{userId}/messages'),\n true,\n [immutableIdCase, concurrentCase],\n createDraft\n ),\n route(\n 'PATCH',\n graph('/users/{userId}/messages/{messageId}'),\n true,\n [immutableIdCase, concurrentCase],\n updateMessage\n ),\n route(\n 'POST',\n graph('/users/{userId}/messages/{messageId}/move'),\n true,\n [immutableIdCase],\n moveMessage\n ),\n route(\n 'GET',\n graph('/users/{userId}/messages/{messageId}/attachments'),\n false,\n [attachmentsCase, contentIdCase],\n listAttachments,\n ['$select']\n ),\n route(\n 'GET',\n graph('/users/{userId}/messages/{messageId}/attachments/{attachmentId}'),\n false,\n [attachmentsCase, contentIdCase],\n getAttachment\n ),\n route('POST', graph('/$batch'), true, [immutableIdCase, concurrentCase], batch),\n route('GET', graph('/drives/{driveId}/items/{itemId}'), false, [folderCase, copyCase], getItem, [\n '$select'\n ]),\n route(\n 'GET',\n graph('/drives/{driveId}/items/{itemId}/children'),\n false,\n [folderCase, copyCase],\n listChildren,\n onePage\n ),\n route(\n 'POST',\n graph('/drives/{driveId}/items/{itemId}/children'),\n true,\n [folderCase, copyCase],\n createFolder\n ),\n route(\n 'DELETE',\n graph('/drives/{driveId}/items/{itemId}'),\n true,\n [folderCase, copyCase],\n deleteItem\n ),\n route('POST', graph('/drives/{driveId}/items/{itemId}/copy'), true, [copyCase], copyItem, [\n '@microsoft.graph.conflictBehavior'\n ]),\n route(\n 'GET',\n '/personal/{site}/_api/v2.0/monitor/{monitorId}',\n false,\n [copyCase],\n copyMonitor,\n [],\n false\n )\n]\n\nconst templatePattern = (template: string): RegExp =>\n new RegExp(\n `^${template\n .split(/(\\{[A-Za-z]+\\})/)\n .map(part =>\n /^\\{[A-Za-z]+\\}$/.test(part) ? '([^/]+)' : part.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n )\n .join('')}$`\n )\n\nconst templateNames = (template: string): ReadonlyArray<string> =>\n [...template.matchAll(/\\{([A-Za-z]+)\\}/g)].map(match => match[1] ?? '')\n\nconst compiledRoutes = microsoftApiRoutes.map(candidate => ({\n route: candidate,\n pattern: templatePattern(candidate.path),\n names: templateNames(candidate.path)\n}))\n\nexport type MatchedRoute = {\n readonly route: MicrosoftApiRoute\n /** Decoded path parameters. */\n readonly params: Readonly<Record<string, string>>\n}\n\n/**\n * The route answering `method` + raw (percent-encoded) `path` with its decoded parameters, or\n * `undefined` (fail closed; also for a parameter that is not valid percent-encoding).\n */\nexport const matchMicrosoftRoute = (method: string, path: string): MatchedRoute | undefined => {\n for (const candidate of compiledRoutes) {\n if (candidate.route.method !== method.toUpperCase()) continue\n\n const match = candidate.pattern.exec(path)\n\n if (match === null) continue\n\n const params: Record<string, string> = {}\n\n for (const [index, name] of candidate.names.entries()) {\n const value = decodeSegment(match[index + 1] ?? '')\n\n if (value === undefined) return undefined\n\n params[name] = value\n }\n\n return { route: candidate.route, params }\n }\n\n return undefined\n}\n\n/** Header the wrapper sets on core requests: the ledger sequence number (for error ids). */\nexport const requestSeqHeader = 'x-emulator-request-seq'\n\n/**\n * The wrapper's fault decision for the core request of job `jobId` (its `emulatorJobHeader`),\n * asked only once the route would answer successfully: `true` when a fault answers instead (it is\n * used up, and the wrapper sends its answer), `false` when none applies.\n */\nexport type MicrosoftFaultDecision = (jobId: string | null) => boolean\n\nconst delay = (ms: number): Promise<void> =>\n new Promise(resolve => {\n setTimeout(resolve, ms)\n })\n\n/** The first query key the route does not emulate, or `undefined`. */\nconst unsupportedQueryKey = (\n query: URLSearchParams,\n allowed: ReadonlyArray<string>\n): string | undefined => [...query.keys()].find(candidate => !allowed.includes(candidate))\n\n/** `{Name}` path templates become `:Name` core route parameters. */\nconst corePath = (template: string): string => template.replace(/\\{([A-Za-z]+)\\}/g, ':$1')\n\n/**\n * One core request: re-match, check the query allowlist and `If-Match`, then plan, fault, and\n * commit. The handler runs on drafts: a shallow copy of the state (handlers replace whole lists\n * and counters, never edit them in place) and copies of the copy monitors and their counter. A\n * refusal (any answer that is not 2xx) is sent as is and uses up no fault; a successful answer asks\n * `decideFault`, and only an unfaulted one commits the drafts (and then holds a written message\n * for `conflictWindowMs`). Plan, fault decision, and commit run synchronously together, so no\n * other request interleaves.\n */\nconst handle = async (\n raw: Request,\n state: MicrosoftEmulatorState,\n env: MicrosoftApiEnv,\n decideFault: MicrosoftFaultDecision\n): Promise<Response> => {\n const url = new URL(raw.url)\n const headers = raw.headers\n const seq = Number(headers.get(requestSeqHeader) ?? '0')\n\n // Built on demand, so the clock is read only when an error is answered.\n const error = (status: number, code: string, message: string) =>\n graphError(\n errorContext(\n env.now(),\n Number.isSafeInteger(seq) ? seq : 0,\n headers.get('client-request-id')\n ),\n status,\n code,\n message\n )\n\n const matched = matchMicrosoftRoute(raw.method, url.pathname)\n\n if (matched === undefined) {\n return error(404, codes.unknownRoute, 'Synthetic: no emulated Microsoft Graph route.')\n }\n\n const unsupported = unsupportedQueryKey(url.searchParams, matched.route.queryKeys)\n\n if (unsupported !== undefined) {\n return error(\n 400,\n codes.unsupportedQuery,\n `Synthetic: query parameter ${unsupported} is not emulated on this route.`\n )\n }\n\n if (headers.has('if-match')) {\n return error(\n 400,\n codes.unsupportedValue,\n 'Synthetic: conditional requests (If-Match) are not emulated.'\n )\n }\n\n const text = await raw.text()\n\n const request: RouteRequest = {\n params: matched.params,\n path: url.pathname,\n query: url.searchParams,\n body: text === '' ? undefined : parseJsonText(text),\n prefer: parsePreferences(headers.get('prefer')),\n error\n }\n\n const draft: MicrosoftEmulatorState = { ...state }\n const monitors = new Map([...env.monitors].map(([id, monitor]) => [id, { ...monitor }]))\n const monitorCounter = { next: env.monitorCounter.next }\n const answer = matched.route.handler(draft, request, { ...env, monitors, monitorCounter })\n const response = answer instanceof Response ? answer : answer.response\n\n if (!response.ok) return response\n\n if (decideFault(headers.get(emulatorJobHeader))) return answeredOutsideCore()\n\n Object.assign(state, draft)\n env.monitors.clear()\n\n for (const [id, monitor] of monitors) env.monitors.set(id, monitor)\n\n env.monitorCounter.next = monitorCounter.next\n\n if (answer instanceof Response) return answer\n\n env.messageLocks.add(answer.holds)\n\n try {\n if (env.conflictWindowMs > 0) await delay(env.conflictWindowMs)\n\n return answer.response\n } finally {\n env.messageLocks.delete(answer.holds)\n }\n}\n\n/**\n * Register every route of the table on the core app, over the generation's state. Each handler\n * re-matches the raw request path against the same table the wrapper ledgers (so parameters are\n * decoded once, failing closed on invalid percent-encoding), checks the route's query allowlist\n * and refuses `If-Match` (no fixture sends one) before the handler runs, then plans, faults, and\n * commits (see `handle`). A handler that throws answers `handlerFailedResponse()` (nothing\n * written), which the wrapper turns into its Graph error envelope 500 with `responseError` in the\n * ledger.\n */\nexport const registerMicrosoftApi = (\n app: Hono,\n state: MicrosoftEmulatorState,\n env: MicrosoftApiEnv,\n decideFault: MicrosoftFaultDecision\n): void => {\n for (const apiRoute of microsoftApiRoutes) {\n app.on(apiRoute.method, corePath(apiRoute.path), async context => {\n try {\n return await handle(context.req.raw, state, env, decideFault)\n } catch {\n return handlerFailedResponse()\n }\n })\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RouteHandler } from "./graph.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/microsoft/calendar.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* `GET .../calendars/{calendarId}/calendarView?startDateTime=&endDateTime=`: the events that
|
|
6
|
+
* overlap `[start, end)` (start before the range end and end after the range start), ordered by
|
|
7
|
+
* start, at most `$top` of them (more is not emulated: no fixture pages a calendar view; `$top`
|
|
8
|
+
* is required, at most `calendarViewMaxTop`).
|
|
9
|
+
*/
|
|
10
|
+
declare const calendarView: RouteHandler;
|
|
11
|
+
/**
|
|
12
|
+
* `POST .../calendars/{calendarId}/events`: create a single-instance, attendee-free event (201
|
|
13
|
+
* with the event; the `createOmitsId` drill knob drops its `id`).
|
|
14
|
+
*/
|
|
15
|
+
declare const createEvent: RouteHandler;
|
|
16
|
+
/** `GET /users/{userId}/events/{eventId}` (`$select`). */
|
|
17
|
+
declare const getEvent: RouteHandler;
|
|
18
|
+
/** `PATCH /users/{userId}/events/{eventId}`: update `subject`; 200 with the event. */
|
|
19
|
+
declare const updateEvent: RouteHandler;
|
|
20
|
+
/** `DELETE /users/{userId}/events/{eventId}`: 204; later reads answer 404. */
|
|
21
|
+
declare const deleteEvent: RouteHandler;
|
|
22
|
+
/**
|
|
23
|
+
* `POST /users/{userId}/events/{eventId}/cancel` (`{ comment? }`): 202 with an empty body, and
|
|
24
|
+
* the event is removed (not kept with `isCancelled: true`), so a later GET or DELETE answers 404,
|
|
25
|
+
* as the cancel fixture records. The emulated events have no attendees: nothing is sent.
|
|
26
|
+
*/
|
|
27
|
+
declare const cancelEvent: RouteHandler;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { calendarView, cancelEvent, createEvent, deleteEvent, getEvent, updateEvent };
|
|
30
|
+
//# sourceMappingURL=calendar.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.d.mts","names":[],"sources":["../../src/microsoft/calendar.ts"],"mappings":";;;;;AA0IA;;;;cAAa,YAAA,EAAc,YA2D1B;AAmHD;;;;AAAA,cAAa,WAAA,EAAa,YA0DzB;AAGD;AAAA,cAAa,QAAA,EAAU,YAuBtB;;cAGY,WAAA,EAAa,YAmCzB;AAtCA;AAAA,cA6CY,WAAA,EAAa,YAgBzB;;;;AAvBA;AAOD;cAuBa,WAAA,EAAa,YA0BzB"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { bodyObject, codes, collection, emptyResponse, entity, invalidValue, jsonResponse, metadataContext, nestedObject, nextChangeKey, notEmulated, nowTimestamp, odataKey, padded, project, resolveUser, selectSuffix, selectedFields, singlePage, textBody } from "./graph.mjs";
|
|
2
|
+
import { formatLocalDateTime, parseInstant } from "./state.mjs";
|
|
3
|
+
import { Predicate } from "effect";
|
|
4
|
+
//#region src/microsoft/calendar.ts
|
|
5
|
+
/**
|
|
6
|
+
* Microsoft Graph emulator calendar routes (internal): `calendarView`, event create, get, update,
|
|
7
|
+
* delete, and cancel. Wire shapes follow the synthetic calendar conformance fixtures.
|
|
8
|
+
*
|
|
9
|
+
* Event times are stored in UTC with seven fractional digits and answered in UTC. Every calendar
|
|
10
|
+
* fixture sends `Prefer: outlook.timezone="UTC"`, so every calendar route needs it; without it, or
|
|
11
|
+
* with another time zone, a request is refused (fail closed). Calendar views need `$top` and answer
|
|
12
|
+
* one page (no `$skip`, no `@odata.nextLink`), as the fixtures record them. Events are answered with
|
|
13
|
+
* the fixture fields only (`@odata.etag`, `id`, `subject`, `start`, `end`, `isCancelled`); a create
|
|
14
|
+
* takes the fixture's fields (`subject`, a text `body`, `start`, `end`, `isReminderOn`, `showAs`)
|
|
15
|
+
* and an update only `subject`; anything else, including unknown nested keys, fails closed.
|
|
16
|
+
*
|
|
17
|
+
* @experimental
|
|
18
|
+
*/
|
|
19
|
+
/** The event fields the fixtures answer (and `$select`). */
|
|
20
|
+
const eventFields = [
|
|
21
|
+
"id",
|
|
22
|
+
"subject",
|
|
23
|
+
"start",
|
|
24
|
+
"end",
|
|
25
|
+
"isCancelled"
|
|
26
|
+
];
|
|
27
|
+
const showAsValues = [
|
|
28
|
+
"free",
|
|
29
|
+
"tentative",
|
|
30
|
+
"busy",
|
|
31
|
+
"oof",
|
|
32
|
+
"workingElsewhere",
|
|
33
|
+
"unknown"
|
|
34
|
+
];
|
|
35
|
+
const isShowAs = (value) => showAsValues.some((candidate) => candidate === value);
|
|
36
|
+
const eventTagPrefix = "DwAAABYAAAAsynthetic";
|
|
37
|
+
const renderDateTime = (env, value) => {
|
|
38
|
+
const ticks = parseInstant(value, "forbidden");
|
|
39
|
+
return {
|
|
40
|
+
dateTime: ticks === void 0 ? value : formatLocalDateTime(ticks, env.drills.timestampPrecisionDigits),
|
|
41
|
+
timeZone: "UTC"
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/** The event as the fixtures answer it, in their key order. */
|
|
45
|
+
const renderEvent = (env, event) => ({
|
|
46
|
+
"@odata.etag": `W/"${event.changeKey}"`,
|
|
47
|
+
id: event.id,
|
|
48
|
+
subject: event.subject,
|
|
49
|
+
start: renderDateTime(env, event.start),
|
|
50
|
+
end: renderDateTime(env, event.end),
|
|
51
|
+
isCancelled: event.isCancelled
|
|
52
|
+
});
|
|
53
|
+
/** `users('{userId}')`, the context prefix of every calendar route. */
|
|
54
|
+
const userContext = (request) => `users${odataKey(request.params.userId ?? "")}`;
|
|
55
|
+
/** `users('{userId}')/calendars('{calendarId}')`. */
|
|
56
|
+
const calendarContext = (request) => `${userContext(request)}/calendars${odataKey(request.params.calendarId ?? "")}`;
|
|
57
|
+
/**
|
|
58
|
+
* Every calendar fixture sends `Prefer: outlook.timezone="UTC"`: a request without it, or with any
|
|
59
|
+
* other time zone, is not emulated.
|
|
60
|
+
*/
|
|
61
|
+
const timezoneProblem = (request) => {
|
|
62
|
+
const timezone = request.prefer.timezone;
|
|
63
|
+
if (timezone === void 0) return notEmulated(request, "calendar requests without Prefer: outlook.timezone=\"UTC\" are not emulated.");
|
|
64
|
+
return timezone.toUpperCase() === "UTC" ? void 0 : notEmulated(request, `outlook.timezone ${timezone} is not emulated (UTC only).`);
|
|
65
|
+
};
|
|
66
|
+
/** `preference-applied` on reads (which asked for UTC), as the fixtures record it. */
|
|
67
|
+
const readHeaders = { "preference-applied": "outlook.timezone=\"UTC\"" };
|
|
68
|
+
const findCalendar = (state, request) => state.calendars.find((calendar) => calendar.id === request.params.calendarId);
|
|
69
|
+
const calendarNotFound = (request) => request.error(404, codes.mailItemNotFound, "The specified object was not found in the store.");
|
|
70
|
+
const eventNotFound = calendarNotFound;
|
|
71
|
+
const findEvent = (state, request) => state.events.find((event) => event.id === request.params.eventId);
|
|
72
|
+
/** Ticks of a stored UTC date-time (always readable: the state schema checks the pattern). */
|
|
73
|
+
const ticksOf = (value) => parseInstant(value, "forbidden") ?? BigInt(0);
|
|
74
|
+
/** The largest calendar view `$top` a fixture sends (`$top=50`, list-range and precision cases). */
|
|
75
|
+
const calendarViewMaxTop = 50;
|
|
76
|
+
/**
|
|
77
|
+
* `GET .../calendars/{calendarId}/calendarView?startDateTime=&endDateTime=`: the events that
|
|
78
|
+
* overlap `[start, end)` (start before the range end and end after the range start), ordered by
|
|
79
|
+
* start, at most `$top` of them (more is not emulated: no fixture pages a calendar view; `$top`
|
|
80
|
+
* is required, at most `calendarViewMaxTop`).
|
|
81
|
+
*/
|
|
82
|
+
const calendarView = (state, request, env) => {
|
|
83
|
+
const user = resolveUser(state, request);
|
|
84
|
+
if (user instanceof Response) return user;
|
|
85
|
+
const zone = timezoneProblem(request);
|
|
86
|
+
if (zone !== void 0) return zone;
|
|
87
|
+
if (findCalendar(state, request) === void 0) return calendarNotFound(request);
|
|
88
|
+
const rawStart = request.query.get("startDateTime");
|
|
89
|
+
const rawEnd = request.query.get("endDateTime");
|
|
90
|
+
if (rawStart === null || rawEnd === null) return invalidValue(request, "calendarView needs startDateTime and endDateTime.");
|
|
91
|
+
const start = parseInstant(rawStart, "optional");
|
|
92
|
+
const end = parseInstant(rawEnd, "optional");
|
|
93
|
+
if (start === void 0 || end === void 0) return invalidValue(request, "startDateTime and endDateTime must be ISO-8601 date-times.");
|
|
94
|
+
const fields = selectedFields(request, eventFields);
|
|
95
|
+
if (fields instanceof Response) return fields;
|
|
96
|
+
const page = singlePage(env.drills.calendarRangeEmpty ? [] : state.events.filter((event) => event.calendarId === request.params.calendarId && ticksOf(event.start) < end && ticksOf(event.end) > start).sort((left, right) => left.start === right.start ? left.id.localeCompare(right.id) : left.start < right.start ? -1 : 1), request, calendarViewMaxTop);
|
|
97
|
+
if (page instanceof Response) return page;
|
|
98
|
+
return jsonResponse(200, collection(metadataContext(env, `${calendarContext(request)}/calendarView${selectSuffix(fields)}`), page.map((event) => project(renderEvent(env, event), fields)), void 0), readHeaders);
|
|
99
|
+
};
|
|
100
|
+
/** A `dateTimeTimeZone` write value in UTC, as a seven-digit local date-time. */
|
|
101
|
+
const writeDateTime = (request, key, value) => {
|
|
102
|
+
const dateTimeZone = nestedObject(request, value, ["dateTime", "timeZone"], `${key} { dateTime, timeZone }`);
|
|
103
|
+
if (dateTimeZone instanceof Response) return dateTimeZone;
|
|
104
|
+
const { dateTime, timeZone } = dateTimeZone;
|
|
105
|
+
if (!Predicate.isString(dateTime) || !Predicate.isString(timeZone)) return invalidValue(request, `${key} must be { dateTime, timeZone } with string values.`);
|
|
106
|
+
if (timeZone.toUpperCase() !== "UTC") return notEmulated(request, `${key}.timeZone ${timeZone} is not emulated (UTC only).`);
|
|
107
|
+
const ticks = parseInstant(dateTime, "forbidden");
|
|
108
|
+
return ticks === void 0 ? invalidValue(request, `${key}.dateTime must be a local date-time without offset.`) : formatLocalDateTime(ticks, 7);
|
|
109
|
+
};
|
|
110
|
+
/** Event create keys, as the create fixture sends them. */
|
|
111
|
+
const createEventKeys = [
|
|
112
|
+
"subject",
|
|
113
|
+
"body",
|
|
114
|
+
"start",
|
|
115
|
+
"end",
|
|
116
|
+
"isReminderOn",
|
|
117
|
+
"showAs"
|
|
118
|
+
];
|
|
119
|
+
/** Event update keys, as the update fixture sends them. */
|
|
120
|
+
const updateEventKeys = ["subject"];
|
|
121
|
+
/** The emulated event fields of a create or update body; attendees and the rest fail closed. */
|
|
122
|
+
const eventWrite = (request, allowed) => {
|
|
123
|
+
const fields = bodyObject(request, allowed);
|
|
124
|
+
if (fields instanceof Response) return fields;
|
|
125
|
+
const write = {};
|
|
126
|
+
for (const [key, value] of Object.entries(fields)) switch (key) {
|
|
127
|
+
case "subject":
|
|
128
|
+
if (!Predicate.isString(value)) return invalidValue(request, "subject must be a string.");
|
|
129
|
+
write.subject = value;
|
|
130
|
+
break;
|
|
131
|
+
case "body": {
|
|
132
|
+
const content = textBody(request, value);
|
|
133
|
+
if (content instanceof Response) return content;
|
|
134
|
+
write.bodyContent = content;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "start":
|
|
138
|
+
case "end": {
|
|
139
|
+
const dateTime = writeDateTime(request, key, value);
|
|
140
|
+
if (dateTime instanceof Response) return dateTime;
|
|
141
|
+
write[key] = dateTime;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "isReminderOn":
|
|
145
|
+
if (!Predicate.isBoolean(value)) return invalidValue(request, "isReminderOn must be a boolean.");
|
|
146
|
+
write.isReminderOn = value;
|
|
147
|
+
break;
|
|
148
|
+
case "showAs":
|
|
149
|
+
if (!isShowAs(value)) return invalidValue(request, "showAs is not a Graph freeBusyStatus.");
|
|
150
|
+
write.showAs = value;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
return write;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* `POST .../calendars/{calendarId}/events`: create a single-instance, attendee-free event (201
|
|
157
|
+
* with the event; the `createOmitsId` drill knob drops its `id`).
|
|
158
|
+
*/
|
|
159
|
+
const createEvent = (state, request, env) => {
|
|
160
|
+
const user = resolveUser(state, request);
|
|
161
|
+
if (user instanceof Response) return user;
|
|
162
|
+
const zone = timezoneProblem(request);
|
|
163
|
+
if (zone !== void 0) return zone;
|
|
164
|
+
if (findCalendar(state, request) === void 0) return calendarNotFound(request);
|
|
165
|
+
const write = eventWrite(request, createEventKeys);
|
|
166
|
+
if (write instanceof Response) return write;
|
|
167
|
+
if (write.start === void 0 || write.end === void 0) return invalidValue(request, "a new event needs start and end.");
|
|
168
|
+
if (write.end < write.start) return invalidValue(request, "the event end is before its start.");
|
|
169
|
+
let number = state.counters.nextEventNumber;
|
|
170
|
+
let id = `AAMkAGI2-synthetic-event-${padded(number, 4)}=`;
|
|
171
|
+
while (state.events.some((event) => event.id === id)) {
|
|
172
|
+
number += 1;
|
|
173
|
+
id = `AAMkAGI2-synthetic-event-${padded(number, 4)}=`;
|
|
174
|
+
}
|
|
175
|
+
state.counters = {
|
|
176
|
+
...state.counters,
|
|
177
|
+
nextEventNumber: number + 1
|
|
178
|
+
};
|
|
179
|
+
const now = nowTimestamp(env);
|
|
180
|
+
const event = {
|
|
181
|
+
id,
|
|
182
|
+
calendarId: request.params.calendarId ?? "",
|
|
183
|
+
subject: write.subject ?? "",
|
|
184
|
+
bodyContentType: "text",
|
|
185
|
+
bodyContent: write.bodyContent ?? "",
|
|
186
|
+
start: write.start,
|
|
187
|
+
end: write.end,
|
|
188
|
+
isCancelled: false,
|
|
189
|
+
isReminderOn: write.isReminderOn ?? true,
|
|
190
|
+
showAs: write.showAs ?? "busy",
|
|
191
|
+
changeKey: nextChangeKey(state, eventTagPrefix),
|
|
192
|
+
createdDateTime: now,
|
|
193
|
+
lastModifiedDateTime: now
|
|
194
|
+
};
|
|
195
|
+
state.events = [...state.events, event];
|
|
196
|
+
const { id: _id, ...withoutId } = renderEvent(env, event);
|
|
197
|
+
return jsonResponse(201, entity(metadataContext(env, `${calendarContext(request)}/events/$entity`), env.drills.createOmitsId ? withoutId : renderEvent(env, event)));
|
|
198
|
+
};
|
|
199
|
+
/** `GET /users/{userId}/events/{eventId}` (`$select`). */
|
|
200
|
+
const getEvent = (state, request, env) => {
|
|
201
|
+
const user = resolveUser(state, request);
|
|
202
|
+
if (user instanceof Response) return user;
|
|
203
|
+
const zone = timezoneProblem(request);
|
|
204
|
+
if (zone !== void 0) return zone;
|
|
205
|
+
const fields = selectedFields(request, eventFields);
|
|
206
|
+
if (fields instanceof Response) return fields;
|
|
207
|
+
const event = findEvent(state, request);
|
|
208
|
+
const context = metadataContext(env, `${userContext(request)}/events${selectSuffix(fields)}/$entity`);
|
|
209
|
+
return event === void 0 ? eventNotFound(request) : jsonResponse(200, entity(context, project(renderEvent(env, event), fields)), readHeaders);
|
|
210
|
+
};
|
|
211
|
+
/** `PATCH /users/{userId}/events/{eventId}`: update `subject`; 200 with the event. */
|
|
212
|
+
const updateEvent = (state, request, env) => {
|
|
213
|
+
const user = resolveUser(state, request);
|
|
214
|
+
if (user instanceof Response) return user;
|
|
215
|
+
const zone = timezoneProblem(request);
|
|
216
|
+
if (zone !== void 0) return zone;
|
|
217
|
+
const event = findEvent(state, request);
|
|
218
|
+
if (event === void 0) return eventNotFound(request);
|
|
219
|
+
const write = eventWrite(request, updateEventKeys);
|
|
220
|
+
if (write instanceof Response) return write;
|
|
221
|
+
const updated = {
|
|
222
|
+
...event,
|
|
223
|
+
...write
|
|
224
|
+
};
|
|
225
|
+
if (updated.end < updated.start) return invalidValue(request, "the event end is before its start.");
|
|
226
|
+
const stored = {
|
|
227
|
+
...updated,
|
|
228
|
+
changeKey: nextChangeKey(state, eventTagPrefix),
|
|
229
|
+
lastModifiedDateTime: nowTimestamp(env)
|
|
230
|
+
};
|
|
231
|
+
state.events = state.events.map((candidate) => candidate.id === stored.id ? stored : candidate);
|
|
232
|
+
return jsonResponse(200, entity(metadataContext(env, `${userContext(request)}/events/$entity`), renderEvent(env, stored)));
|
|
233
|
+
};
|
|
234
|
+
const removeEvent = (state, id) => {
|
|
235
|
+
state.events = state.events.filter((event) => event.id !== id);
|
|
236
|
+
};
|
|
237
|
+
/** `DELETE /users/{userId}/events/{eventId}`: 204; later reads answer 404. */
|
|
238
|
+
const deleteEvent = (state, request) => {
|
|
239
|
+
const user = resolveUser(state, request);
|
|
240
|
+
if (user instanceof Response) return user;
|
|
241
|
+
const zone = timezoneProblem(request);
|
|
242
|
+
if (zone !== void 0) return zone;
|
|
243
|
+
const event = findEvent(state, request);
|
|
244
|
+
if (event === void 0) return eventNotFound(request);
|
|
245
|
+
removeEvent(state, event.id);
|
|
246
|
+
return emptyResponse(204);
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* `POST /users/{userId}/events/{eventId}/cancel` (`{ comment? }`): 202 with an empty body, and
|
|
250
|
+
* the event is removed (not kept with `isCancelled: true`), so a later GET or DELETE answers 404,
|
|
251
|
+
* as the cancel fixture records. The emulated events have no attendees: nothing is sent.
|
|
252
|
+
*/
|
|
253
|
+
const cancelEvent = (state, request) => {
|
|
254
|
+
const user = resolveUser(state, request);
|
|
255
|
+
if (user instanceof Response) return user;
|
|
256
|
+
const zone = timezoneProblem(request);
|
|
257
|
+
if (zone !== void 0) return zone;
|
|
258
|
+
const event = findEvent(state, request);
|
|
259
|
+
if (event === void 0) return eventNotFound(request);
|
|
260
|
+
if (request.body !== void 0) {
|
|
261
|
+
const fields = bodyObject(request, ["comment"]);
|
|
262
|
+
if (fields instanceof Response) return fields;
|
|
263
|
+
if (fields.comment !== void 0 && !Predicate.isString(fields.comment)) return invalidValue(request, "comment must be a string.");
|
|
264
|
+
}
|
|
265
|
+
removeEvent(state, event.id);
|
|
266
|
+
return emptyResponse(202);
|
|
267
|
+
};
|
|
268
|
+
//#endregion
|
|
269
|
+
export { calendarView, cancelEvent, createEvent, deleteEvent, getEvent, updateEvent };
|
|
270
|
+
|
|
271
|
+
//# sourceMappingURL=calendar.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.mjs","names":[],"sources":["../../src/microsoft/calendar.ts"],"sourcesContent":["/**\n * Microsoft Graph emulator calendar routes (internal): `calendarView`, event create, get, update,\n * delete, and cancel. Wire shapes follow the synthetic calendar conformance fixtures.\n *\n * Event times are stored in UTC with seven fractional digits and answered in UTC. Every calendar\n * fixture sends `Prefer: outlook.timezone=\"UTC\"`, so every calendar route needs it; without it, or\n * with another time zone, a request is refused (fail closed). Calendar views need `$top` and answer\n * one page (no `$skip`, no `@odata.nextLink`), as the fixtures record them. Events are answered with\n * the fixture fields only (`@odata.etag`, `id`, `subject`, `start`, `end`, `isCancelled`); a create\n * takes the fixture's fields (`subject`, a text `body`, `start`, `end`, `isReminderOn`, `showAs`)\n * and an update only `subject`; anything else, including unknown nested keys, fails closed.\n *\n * @experimental\n */\nimport { Predicate } from 'effect'\nimport type * as Schema from 'effect/Schema'\nimport {\n bodyObject,\n codes,\n collection,\n emptyResponse,\n entity,\n invalidValue,\n jsonResponse,\n metadataContext,\n nestedObject,\n nextChangeKey,\n notEmulated,\n nowTimestamp,\n odataKey,\n padded,\n project,\n resolveUser,\n selectSuffix,\n selectedFields,\n singlePage,\n textBody,\n type MicrosoftApiEnv,\n type RouteHandler,\n type RouteRequest\n} from './graph.ts'\nimport {\n formatLocalDateTime,\n parseInstant,\n type MicrosoftEmulatorEvent,\n type MicrosoftEmulatorState\n} from './state.ts'\n\n/** The event fields the fixtures answer (and `$select`). */\nconst eventFields: ReadonlyArray<string> = ['id', 'subject', 'start', 'end', 'isCancelled']\n\nconst showAsValues: ReadonlyArray<MicrosoftEmulatorEvent['showAs']> = [\n 'free',\n 'tentative',\n 'busy',\n 'oof',\n 'workingElsewhere',\n 'unknown'\n]\n\nconst isShowAs = (value: unknown): value is MicrosoftEmulatorEvent['showAs'] =>\n showAsValues.some(candidate => candidate === value)\n\nconst eventTagPrefix = 'DwAAABYAAAAsynthetic'\n\nconst renderDateTime = (env: MicrosoftApiEnv, value: string) => {\n const ticks = parseInstant(value, 'forbidden')\n\n return {\n dateTime:\n ticks === undefined ? value : formatLocalDateTime(ticks, env.drills.timestampPrecisionDigits),\n timeZone: 'UTC'\n }\n}\n\n/** The event as the fixtures answer it, in their key order. */\nconst renderEvent = (env: MicrosoftApiEnv, event: MicrosoftEmulatorEvent): Schema.JsonObject => ({\n '@odata.etag': `W/\"${event.changeKey}\"`,\n id: event.id,\n subject: event.subject,\n start: renderDateTime(env, event.start),\n end: renderDateTime(env, event.end),\n isCancelled: event.isCancelled\n})\n\n/** `users('{userId}')`, the context prefix of every calendar route. */\nconst userContext = (request: RouteRequest): string =>\n `users${odataKey(request.params.userId ?? '')}`\n\n/** `users('{userId}')/calendars('{calendarId}')`. */\nconst calendarContext = (request: RouteRequest): string =>\n `${userContext(request)}/calendars${odataKey(request.params.calendarId ?? '')}`\n\n/**\n * Every calendar fixture sends `Prefer: outlook.timezone=\"UTC\"`: a request without it, or with any\n * other time zone, is not emulated.\n */\nconst timezoneProblem = (request: RouteRequest): Response | undefined => {\n const timezone = request.prefer.timezone\n\n if (timezone === undefined) {\n return notEmulated(\n request,\n 'calendar requests without Prefer: outlook.timezone=\"UTC\" are not emulated.'\n )\n }\n\n return timezone.toUpperCase() === 'UTC'\n ? undefined\n : notEmulated(request, `outlook.timezone ${timezone} is not emulated (UTC only).`)\n}\n\n/** `preference-applied` on reads (which asked for UTC), as the fixtures record it. */\nconst readHeaders: HeadersInit = { 'preference-applied': 'outlook.timezone=\"UTC\"' }\n\nconst findCalendar = (state: MicrosoftEmulatorState, request: RouteRequest) =>\n state.calendars.find(calendar => calendar.id === request.params.calendarId)\n\nconst calendarNotFound = (request: RouteRequest): Response =>\n request.error(404, codes.mailItemNotFound, 'The specified object was not found in the store.')\n\nconst eventNotFound = calendarNotFound\n\nconst findEvent = (state: MicrosoftEmulatorState, request: RouteRequest) =>\n state.events.find(event => event.id === request.params.eventId)\n\n/** Ticks of a stored UTC date-time (always readable: the state schema checks the pattern). */\nconst ticksOf = (value: string): bigint => parseInstant(value, 'forbidden') ?? BigInt(0)\n\n/** The largest calendar view `$top` a fixture sends (`$top=50`, list-range and precision cases). */\nconst calendarViewMaxTop = 50\n\n/**\n * `GET .../calendars/{calendarId}/calendarView?startDateTime=&endDateTime=`: the events that\n * overlap `[start, end)` (start before the range end and end after the range start), ordered by\n * start, at most `$top` of them (more is not emulated: no fixture pages a calendar view; `$top`\n * is required, at most `calendarViewMaxTop`).\n */\nexport const calendarView: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const zone = timezoneProblem(request)\n\n if (zone !== undefined) return zone\n\n if (findCalendar(state, request) === undefined) return calendarNotFound(request)\n\n const rawStart = request.query.get('startDateTime')\n const rawEnd = request.query.get('endDateTime')\n\n if (rawStart === null || rawEnd === null) {\n return invalidValue(request, 'calendarView needs startDateTime and endDateTime.')\n }\n\n const start = parseInstant(rawStart, 'optional')\n const end = parseInstant(rawEnd, 'optional')\n\n if (start === undefined || end === undefined) {\n return invalidValue(request, 'startDateTime and endDateTime must be ISO-8601 date-times.')\n }\n\n const fields = selectedFields(request, eventFields)\n\n if (fields instanceof Response) return fields\n\n const matching = env.drills.calendarRangeEmpty\n ? []\n : state.events\n .filter(\n event =>\n event.calendarId === request.params.calendarId &&\n ticksOf(event.start) < end &&\n ticksOf(event.end) > start\n )\n .sort((left, right) =>\n left.start === right.start\n ? left.id.localeCompare(right.id)\n : left.start < right.start\n ? -1\n : 1\n )\n\n const page = singlePage(matching, request, calendarViewMaxTop)\n\n if (page instanceof Response) return page\n\n return jsonResponse(\n 200,\n collection(\n metadataContext(env, `${calendarContext(request)}/calendarView${selectSuffix(fields)}`),\n page.map(event => project(renderEvent(env, event), fields)),\n undefined\n ),\n readHeaders\n )\n}\n\n/** A `dateTimeTimeZone` write value in UTC, as a seven-digit local date-time. */\nconst writeDateTime = (\n request: RouteRequest,\n key: string,\n value: Schema.Json\n): string | Response => {\n const dateTimeZone = nestedObject(\n request,\n value,\n ['dateTime', 'timeZone'],\n `${key} { dateTime, timeZone }`\n )\n\n if (dateTimeZone instanceof Response) return dateTimeZone\n\n const { dateTime, timeZone } = dateTimeZone\n\n if (!Predicate.isString(dateTime) || !Predicate.isString(timeZone)) {\n return invalidValue(request, `${key} must be { dateTime, timeZone } with string values.`)\n }\n\n if (timeZone.toUpperCase() !== 'UTC') {\n return notEmulated(request, `${key}.timeZone ${timeZone} is not emulated (UTC only).`)\n }\n\n const ticks = parseInstant(dateTime, 'forbidden')\n\n return ticks === undefined\n ? invalidValue(request, `${key}.dateTime must be a local date-time without offset.`)\n : formatLocalDateTime(ticks, 7)\n}\n\ntype EventWrite = {\n subject?: string\n bodyContent?: string\n start?: string\n end?: string\n isReminderOn?: boolean\n showAs?: MicrosoftEmulatorEvent['showAs']\n}\n\n/** Event create keys, as the create fixture sends them. */\nconst createEventKeys: ReadonlyArray<string> = [\n 'subject',\n 'body',\n 'start',\n 'end',\n 'isReminderOn',\n 'showAs'\n]\n\n/** Event update keys, as the update fixture sends them. */\nconst updateEventKeys: ReadonlyArray<string> = ['subject']\n\n/** The emulated event fields of a create or update body; attendees and the rest fail closed. */\nconst eventWrite = (\n request: RouteRequest,\n allowed: ReadonlyArray<string>\n): EventWrite | Response => {\n const fields = bodyObject(request, allowed)\n\n if (fields instanceof Response) return fields\n\n const write: EventWrite = {}\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 'start':\n case 'end': {\n const dateTime = writeDateTime(request, key, value)\n\n if (dateTime instanceof Response) return dateTime\n\n write[key] = dateTime\n break\n }\n\n case 'isReminderOn':\n if (!Predicate.isBoolean(value)) {\n return invalidValue(request, 'isReminderOn must be a boolean.')\n }\n\n write.isReminderOn = value\n break\n case 'showAs':\n if (!isShowAs(value)) return invalidValue(request, 'showAs is not a Graph freeBusyStatus.')\n\n write.showAs = value\n break\n }\n }\n\n return write\n}\n\n/**\n * `POST .../calendars/{calendarId}/events`: create a single-instance, attendee-free event (201\n * with the event; the `createOmitsId` drill knob drops its `id`).\n */\nexport const createEvent: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const zone = timezoneProblem(request)\n\n if (zone !== undefined) return zone\n\n if (findCalendar(state, request) === undefined) return calendarNotFound(request)\n\n const write = eventWrite(request, createEventKeys)\n\n if (write instanceof Response) return write\n\n if (write.start === undefined || write.end === undefined) {\n return invalidValue(request, 'a new event needs start and end.')\n }\n\n if (write.end < write.start) return invalidValue(request, 'the event end is before its start.')\n\n let number = state.counters.nextEventNumber\n let id = `AAMkAGI2-synthetic-event-${padded(number, 4)}=`\n\n while (state.events.some(event => event.id === id)) {\n number += 1\n id = `AAMkAGI2-synthetic-event-${padded(number, 4)}=`\n }\n\n state.counters = { ...state.counters, nextEventNumber: number + 1 }\n\n const now = nowTimestamp(env)\n\n const event: MicrosoftEmulatorEvent = {\n id,\n calendarId: request.params.calendarId ?? '',\n subject: write.subject ?? '',\n bodyContentType: 'text',\n bodyContent: write.bodyContent ?? '',\n start: write.start,\n end: write.end,\n isCancelled: false,\n isReminderOn: write.isReminderOn ?? true,\n showAs: write.showAs ?? 'busy',\n changeKey: nextChangeKey(state, eventTagPrefix),\n createdDateTime: now,\n lastModifiedDateTime: now\n }\n\n state.events = [...state.events, event]\n\n const { id: _id, ...withoutId } = renderEvent(env, event)\n const context = metadataContext(env, `${calendarContext(request)}/events/$entity`)\n\n return jsonResponse(\n 201,\n entity(context, env.drills.createOmitsId ? withoutId : renderEvent(env, event))\n )\n}\n\n/** `GET /users/{userId}/events/{eventId}` (`$select`). */\nexport const getEvent: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const zone = timezoneProblem(request)\n\n if (zone !== undefined) return zone\n\n const fields = selectedFields(request, eventFields)\n\n if (fields instanceof Response) return fields\n\n const event = findEvent(state, request)\n\n const context = metadataContext(\n env,\n `${userContext(request)}/events${selectSuffix(fields)}/$entity`\n )\n\n return event === undefined\n ? eventNotFound(request)\n : jsonResponse(200, entity(context, project(renderEvent(env, event), fields)), readHeaders)\n}\n\n/** `PATCH /users/{userId}/events/{eventId}`: update `subject`; 200 with the event. */\nexport const updateEvent: RouteHandler = (state, request, env) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const zone = timezoneProblem(request)\n\n if (zone !== undefined) return zone\n\n const event = findEvent(state, request)\n\n if (event === undefined) return eventNotFound(request)\n\n const write = eventWrite(request, updateEventKeys)\n\n if (write instanceof Response) return write\n\n const updated: MicrosoftEmulatorEvent = { ...event, ...write }\n\n if (updated.end < updated.start) {\n return invalidValue(request, 'the event end is before its start.')\n }\n\n const stored: MicrosoftEmulatorEvent = {\n ...updated,\n changeKey: nextChangeKey(state, eventTagPrefix),\n lastModifiedDateTime: nowTimestamp(env)\n }\n\n state.events = state.events.map(candidate => (candidate.id === stored.id ? stored : candidate))\n\n return jsonResponse(\n 200,\n entity(metadataContext(env, `${userContext(request)}/events/$entity`), renderEvent(env, stored))\n )\n}\n\nconst removeEvent = (state: MicrosoftEmulatorState, id: string): void => {\n state.events = state.events.filter(event => event.id !== id)\n}\n\n/** `DELETE /users/{userId}/events/{eventId}`: 204; later reads answer 404. */\nexport const deleteEvent: RouteHandler = (state, request) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const zone = timezoneProblem(request)\n\n if (zone !== undefined) return zone\n\n const event = findEvent(state, request)\n\n if (event === undefined) return eventNotFound(request)\n\n removeEvent(state, event.id)\n\n return emptyResponse(204)\n}\n\n/**\n * `POST /users/{userId}/events/{eventId}/cancel` (`{ comment? }`): 202 with an empty body, and\n * the event is removed (not kept with `isCancelled: true`), so a later GET or DELETE answers 404,\n * as the cancel fixture records. The emulated events have no attendees: nothing is sent.\n */\nexport const cancelEvent: RouteHandler = (state, request) => {\n const user = resolveUser(state, request)\n\n if (user instanceof Response) return user\n\n const zone = timezoneProblem(request)\n\n if (zone !== undefined) return zone\n\n const event = findEvent(state, request)\n\n if (event === undefined) return eventNotFound(request)\n\n if (request.body !== undefined) {\n const fields = bodyObject(request, ['comment'])\n\n if (fields instanceof Response) return fields\n\n if (fields.comment !== undefined && !Predicate.isString(fields.comment)) {\n return invalidValue(request, 'comment must be a string.')\n }\n }\n\n removeEvent(state, event.id)\n\n return emptyResponse(202)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RouteHandler } from "./graph.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/microsoft/drive.d.ts
|
|
4
|
+
/** `GET /drives/{driveId}/items/{itemId}` (`$select`). */
|
|
5
|
+
declare const getItem: RouteHandler;
|
|
6
|
+
/**
|
|
7
|
+
* `GET /drives/{driveId}/items/{itemId}/children`: by name, at most `$top` (one page only;
|
|
8
|
+
* `$top` required, at most `childrenMaxTop`).
|
|
9
|
+
*/
|
|
10
|
+
declare const listChildren: RouteHandler;
|
|
11
|
+
/**
|
|
12
|
+
* `POST /drives/{driveId}/items/{itemId}/children` with a `folder` facet and
|
|
13
|
+
* `@microsoft.graph.conflictBehavior: fail`: 201 with the folder. A taken name, and creating
|
|
14
|
+
* files here, are not emulated.
|
|
15
|
+
*/
|
|
16
|
+
declare const createFolder: RouteHandler;
|
|
17
|
+
/** `DELETE /drives/{driveId}/items/{itemId}`: 204; the item and its subtree are removed. */
|
|
18
|
+
declare const deleteItem: RouteHandler;
|
|
19
|
+
/**
|
|
20
|
+
* `POST /drives/{driveId}/items/{itemId}/copy` (`{ parentReference: { driveId, id } }`, query
|
|
21
|
+
* `@microsoft.graph.conflictBehavior=fail`): 202 with an empty body and one monitor `Location`.
|
|
22
|
+
* The copy itself runs when the monitor reports completion. Only what the copy fixture sends is
|
|
23
|
+
* emulated: a file source, a destination on the same drive named by `driveId` and `id`, and the
|
|
24
|
+
* source's name (a new `name`, a folder source, or a missing `driveId` fail closed).
|
|
25
|
+
*/
|
|
26
|
+
declare const copyItem: RouteHandler;
|
|
27
|
+
/**
|
|
28
|
+
* `GET /personal/{site}/_api/v2.0/monitor/{monitorId}` on the SharePoint origin (no credentials
|
|
29
|
+
* needed, like the real capability URL): `inProgress` (202) while polls remain, then the copy runs
|
|
30
|
+
* once and every later poll answers `completed` (200, `resourceId`). A copy that cannot run
|
|
31
|
+
* answers 400 not emulated (failed copies are not emulated) and stays pending.
|
|
32
|
+
*/
|
|
33
|
+
declare const copyMonitor: RouteHandler;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { copyItem, copyMonitor, createFolder, deleteItem, getItem, listChildren };
|
|
36
|
+
//# sourceMappingURL=drive.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drive.d.mts","names":[],"sources":["../../src/microsoft/drive.ts"],"mappings":";;;;cAkLa,OAAA,EAAS,YAmBrB;AAnBD;;;;AAAA,cAgCa,YAAA,EAAc,YA2B1B;AA3BD;;;;AA2BC;AA3BD,cA4Ea,YAAA,EAAc,YA0D1B;;cAGY,UAAA,EAAY,YAiBxB;;AApBA;AAGD;;;;AAiBC;cAYY,QAAA,EAAU,YAuEtB;;;;AAAA;AAuDD;;cAAa,WAAA,EAAa,YAsCzB"}
|