@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,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Graph emulator API: the route table (with its evidence and query allowlist), route
|
|
3
|
+
* matching, and the registration of the stateful handlers on the `@emulators/core` app
|
|
4
|
+
* (internal; re-exported by `src/microsoft.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Only the routes the eleven Microsoft conformance cases need are emulated, plus the copy monitor
|
|
7
|
+
* URL they require; everything else fails closed. Each route lists the cases whose claims it
|
|
8
|
+
* follows.
|
|
9
|
+
*
|
|
10
|
+
* @experimental
|
|
11
|
+
*/
|
|
12
|
+
import type { Hono } from '@emulators/core'
|
|
13
|
+
import { answeredOutsideCore, emulatorJobHeader, handlerFailedResponse } from '../emulator-http.ts'
|
|
14
|
+
import type { EmulatorRouteEvidence } from '../route-evidence.ts'
|
|
15
|
+
import {
|
|
16
|
+
calendarView,
|
|
17
|
+
cancelEvent,
|
|
18
|
+
createEvent,
|
|
19
|
+
deleteEvent,
|
|
20
|
+
getEvent,
|
|
21
|
+
updateEvent
|
|
22
|
+
} from './calendar.ts'
|
|
23
|
+
import { copyItem, copyMonitor, createFolder, deleteItem, getItem, listChildren } from './drive.ts'
|
|
24
|
+
import {
|
|
25
|
+
codes,
|
|
26
|
+
decodeSegment,
|
|
27
|
+
errorContext,
|
|
28
|
+
graphError,
|
|
29
|
+
microsoftEmulatorBasePath,
|
|
30
|
+
parseJsonText,
|
|
31
|
+
parsePreferences,
|
|
32
|
+
type MicrosoftApiEnv,
|
|
33
|
+
type RouteHandler,
|
|
34
|
+
type RouteRequest
|
|
35
|
+
} from './graph.ts'
|
|
36
|
+
import {
|
|
37
|
+
batch,
|
|
38
|
+
createDraft,
|
|
39
|
+
getAttachment,
|
|
40
|
+
listAttachments,
|
|
41
|
+
listFolderMessages,
|
|
42
|
+
moveMessage,
|
|
43
|
+
updateMessage
|
|
44
|
+
} from './mail.ts'
|
|
45
|
+
import type { MicrosoftEmulatorState } from './state.ts'
|
|
46
|
+
|
|
47
|
+
export { microsoftEmulatorBasePath } from './graph.ts'
|
|
48
|
+
|
|
49
|
+
type MicrosoftApiRoute = EmulatorRouteEvidence & {
|
|
50
|
+
/**
|
|
51
|
+
* Query parameters the route emulates; any other key answers 400 before the handler runs, so
|
|
52
|
+
* a rejected request never writes. Empty by default.
|
|
53
|
+
*/
|
|
54
|
+
readonly queryKeys: ReadonlyArray<string>
|
|
55
|
+
/** `false` only for the copy monitor, a pre-authenticated capability URL. */
|
|
56
|
+
readonly auth: boolean
|
|
57
|
+
readonly handler: RouteHandler
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const listRangeCase = 'microsoft.calendar.list-range-returns-events'
|
|
61
|
+
|
|
62
|
+
const precisionCase = 'microsoft.calendar.timestamp-precision'
|
|
63
|
+
|
|
64
|
+
const createEventCase = 'microsoft.calendar.create-returns-event-id'
|
|
65
|
+
|
|
66
|
+
const cancelCase = 'microsoft.calendar.cancel-semantics'
|
|
67
|
+
|
|
68
|
+
const attachmentsCase = 'microsoft.outlook.attachments-listing'
|
|
69
|
+
|
|
70
|
+
const contentIdCase = 'microsoft.outlook.attachment-content-id'
|
|
71
|
+
|
|
72
|
+
const pagingCase = 'microsoft.outlook.paging-next-link'
|
|
73
|
+
|
|
74
|
+
const immutableIdCase = 'microsoft.outlook.immutable-id-survives-move'
|
|
75
|
+
|
|
76
|
+
const concurrentCase = 'microsoft.outlook.concurrent-writes-same-message'
|
|
77
|
+
|
|
78
|
+
const folderCase = 'microsoft.onedrive.create-folder-roundtrip'
|
|
79
|
+
|
|
80
|
+
const copyCase = 'microsoft.onedrive.copy-accepted-monitor'
|
|
81
|
+
|
|
82
|
+
const route = (
|
|
83
|
+
method: string,
|
|
84
|
+
path: string,
|
|
85
|
+
write: boolean,
|
|
86
|
+
caseIds: ReadonlyArray<string>,
|
|
87
|
+
handler: RouteHandler,
|
|
88
|
+
queryKeys: ReadonlyArray<string> = [],
|
|
89
|
+
auth = true
|
|
90
|
+
): MicrosoftApiRoute => ({
|
|
91
|
+
method,
|
|
92
|
+
path,
|
|
93
|
+
kind: 'connector',
|
|
94
|
+
write,
|
|
95
|
+
caseIds,
|
|
96
|
+
evidence: 'unverified',
|
|
97
|
+
queryKeys,
|
|
98
|
+
auth,
|
|
99
|
+
handler
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const graph = (path: string) => `${microsoftEmulatorBasePath}${path}`
|
|
103
|
+
|
|
104
|
+
/** Folder message listing: the paging fixture's `$select`, `$top`, and `$skip`. */
|
|
105
|
+
const paging: ReadonlyArray<string> = ['$select', '$top', '$skip']
|
|
106
|
+
|
|
107
|
+
/** Calendar views and children listings: their fixtures send `$select` and `$top`, never `$skip`. */
|
|
108
|
+
const onePage: ReadonlyArray<string> = ['$select', '$top']
|
|
109
|
+
|
|
110
|
+
/** The route table: evidence plus handler. `microsoftEmulatorRoutes` is its evidence part. */
|
|
111
|
+
export const microsoftApiRoutes: ReadonlyArray<MicrosoftApiRoute> = [
|
|
112
|
+
route(
|
|
113
|
+
'GET',
|
|
114
|
+
graph('/users/{userId}/calendars/{calendarId}/calendarView'),
|
|
115
|
+
false,
|
|
116
|
+
[listRangeCase, precisionCase],
|
|
117
|
+
calendarView,
|
|
118
|
+
['startDateTime', 'endDateTime', ...onePage]
|
|
119
|
+
),
|
|
120
|
+
route(
|
|
121
|
+
'POST',
|
|
122
|
+
graph('/users/{userId}/calendars/{calendarId}/events'),
|
|
123
|
+
true,
|
|
124
|
+
[createEventCase, cancelCase],
|
|
125
|
+
createEvent
|
|
126
|
+
),
|
|
127
|
+
route(
|
|
128
|
+
'GET',
|
|
129
|
+
graph('/users/{userId}/events/{eventId}'),
|
|
130
|
+
false,
|
|
131
|
+
[precisionCase, createEventCase, cancelCase],
|
|
132
|
+
getEvent,
|
|
133
|
+
['$select']
|
|
134
|
+
),
|
|
135
|
+
route('PATCH', graph('/users/{userId}/events/{eventId}'), true, [createEventCase], updateEvent),
|
|
136
|
+
route(
|
|
137
|
+
'DELETE',
|
|
138
|
+
graph('/users/{userId}/events/{eventId}'),
|
|
139
|
+
true,
|
|
140
|
+
[createEventCase, cancelCase],
|
|
141
|
+
deleteEvent
|
|
142
|
+
),
|
|
143
|
+
route('POST', graph('/users/{userId}/events/{eventId}/cancel'), true, [cancelCase], cancelEvent),
|
|
144
|
+
route(
|
|
145
|
+
'GET',
|
|
146
|
+
graph('/users/{userId}/mailFolders/{folderId}/messages'),
|
|
147
|
+
false,
|
|
148
|
+
[pagingCase],
|
|
149
|
+
listFolderMessages,
|
|
150
|
+
paging
|
|
151
|
+
),
|
|
152
|
+
route(
|
|
153
|
+
'POST',
|
|
154
|
+
graph('/users/{userId}/messages'),
|
|
155
|
+
true,
|
|
156
|
+
[immutableIdCase, concurrentCase],
|
|
157
|
+
createDraft
|
|
158
|
+
),
|
|
159
|
+
route(
|
|
160
|
+
'PATCH',
|
|
161
|
+
graph('/users/{userId}/messages/{messageId}'),
|
|
162
|
+
true,
|
|
163
|
+
[immutableIdCase, concurrentCase],
|
|
164
|
+
updateMessage
|
|
165
|
+
),
|
|
166
|
+
route(
|
|
167
|
+
'POST',
|
|
168
|
+
graph('/users/{userId}/messages/{messageId}/move'),
|
|
169
|
+
true,
|
|
170
|
+
[immutableIdCase],
|
|
171
|
+
moveMessage
|
|
172
|
+
),
|
|
173
|
+
route(
|
|
174
|
+
'GET',
|
|
175
|
+
graph('/users/{userId}/messages/{messageId}/attachments'),
|
|
176
|
+
false,
|
|
177
|
+
[attachmentsCase, contentIdCase],
|
|
178
|
+
listAttachments,
|
|
179
|
+
['$select']
|
|
180
|
+
),
|
|
181
|
+
route(
|
|
182
|
+
'GET',
|
|
183
|
+
graph('/users/{userId}/messages/{messageId}/attachments/{attachmentId}'),
|
|
184
|
+
false,
|
|
185
|
+
[attachmentsCase, contentIdCase],
|
|
186
|
+
getAttachment
|
|
187
|
+
),
|
|
188
|
+
route('POST', graph('/$batch'), true, [immutableIdCase, concurrentCase], batch),
|
|
189
|
+
route('GET', graph('/drives/{driveId}/items/{itemId}'), false, [folderCase, copyCase], getItem, [
|
|
190
|
+
'$select'
|
|
191
|
+
]),
|
|
192
|
+
route(
|
|
193
|
+
'GET',
|
|
194
|
+
graph('/drives/{driveId}/items/{itemId}/children'),
|
|
195
|
+
false,
|
|
196
|
+
[folderCase, copyCase],
|
|
197
|
+
listChildren,
|
|
198
|
+
onePage
|
|
199
|
+
),
|
|
200
|
+
route(
|
|
201
|
+
'POST',
|
|
202
|
+
graph('/drives/{driveId}/items/{itemId}/children'),
|
|
203
|
+
true,
|
|
204
|
+
[folderCase, copyCase],
|
|
205
|
+
createFolder
|
|
206
|
+
),
|
|
207
|
+
route(
|
|
208
|
+
'DELETE',
|
|
209
|
+
graph('/drives/{driveId}/items/{itemId}'),
|
|
210
|
+
true,
|
|
211
|
+
[folderCase, copyCase],
|
|
212
|
+
deleteItem
|
|
213
|
+
),
|
|
214
|
+
route('POST', graph('/drives/{driveId}/items/{itemId}/copy'), true, [copyCase], copyItem, [
|
|
215
|
+
'@microsoft.graph.conflictBehavior'
|
|
216
|
+
]),
|
|
217
|
+
route(
|
|
218
|
+
'GET',
|
|
219
|
+
'/personal/{site}/_api/v2.0/monitor/{monitorId}',
|
|
220
|
+
false,
|
|
221
|
+
[copyCase],
|
|
222
|
+
copyMonitor,
|
|
223
|
+
[],
|
|
224
|
+
false
|
|
225
|
+
)
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
const templatePattern = (template: string): RegExp =>
|
|
229
|
+
new RegExp(
|
|
230
|
+
`^${template
|
|
231
|
+
.split(/(\{[A-Za-z]+\})/)
|
|
232
|
+
.map(part =>
|
|
233
|
+
/^\{[A-Za-z]+\}$/.test(part) ? '([^/]+)' : part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
234
|
+
)
|
|
235
|
+
.join('')}$`
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
const templateNames = (template: string): ReadonlyArray<string> =>
|
|
239
|
+
[...template.matchAll(/\{([A-Za-z]+)\}/g)].map(match => match[1] ?? '')
|
|
240
|
+
|
|
241
|
+
const compiledRoutes = microsoftApiRoutes.map(candidate => ({
|
|
242
|
+
route: candidate,
|
|
243
|
+
pattern: templatePattern(candidate.path),
|
|
244
|
+
names: templateNames(candidate.path)
|
|
245
|
+
}))
|
|
246
|
+
|
|
247
|
+
export type MatchedRoute = {
|
|
248
|
+
readonly route: MicrosoftApiRoute
|
|
249
|
+
/** Decoded path parameters. */
|
|
250
|
+
readonly params: Readonly<Record<string, string>>
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The route answering `method` + raw (percent-encoded) `path` with its decoded parameters, or
|
|
255
|
+
* `undefined` (fail closed; also for a parameter that is not valid percent-encoding).
|
|
256
|
+
*/
|
|
257
|
+
export const matchMicrosoftRoute = (method: string, path: string): MatchedRoute | undefined => {
|
|
258
|
+
for (const candidate of compiledRoutes) {
|
|
259
|
+
if (candidate.route.method !== method.toUpperCase()) continue
|
|
260
|
+
|
|
261
|
+
const match = candidate.pattern.exec(path)
|
|
262
|
+
|
|
263
|
+
if (match === null) continue
|
|
264
|
+
|
|
265
|
+
const params: Record<string, string> = {}
|
|
266
|
+
|
|
267
|
+
for (const [index, name] of candidate.names.entries()) {
|
|
268
|
+
const value = decodeSegment(match[index + 1] ?? '')
|
|
269
|
+
|
|
270
|
+
if (value === undefined) return undefined
|
|
271
|
+
|
|
272
|
+
params[name] = value
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { route: candidate.route, params }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return undefined
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Header the wrapper sets on core requests: the ledger sequence number (for error ids). */
|
|
282
|
+
export const requestSeqHeader = 'x-emulator-request-seq'
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The wrapper's fault decision for the core request of job `jobId` (its `emulatorJobHeader`),
|
|
286
|
+
* asked only once the route would answer successfully: `true` when a fault answers instead (it is
|
|
287
|
+
* used up, and the wrapper sends its answer), `false` when none applies.
|
|
288
|
+
*/
|
|
289
|
+
export type MicrosoftFaultDecision = (jobId: string | null) => boolean
|
|
290
|
+
|
|
291
|
+
const delay = (ms: number): Promise<void> =>
|
|
292
|
+
new Promise(resolve => {
|
|
293
|
+
setTimeout(resolve, ms)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
/** The first query key the route does not emulate, or `undefined`. */
|
|
297
|
+
const unsupportedQueryKey = (
|
|
298
|
+
query: URLSearchParams,
|
|
299
|
+
allowed: ReadonlyArray<string>
|
|
300
|
+
): string | undefined => [...query.keys()].find(candidate => !allowed.includes(candidate))
|
|
301
|
+
|
|
302
|
+
/** `{Name}` path templates become `:Name` core route parameters. */
|
|
303
|
+
const corePath = (template: string): string => template.replace(/\{([A-Za-z]+)\}/g, ':$1')
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* One core request: re-match, check the query allowlist and `If-Match`, then plan, fault, and
|
|
307
|
+
* commit. The handler runs on drafts: a shallow copy of the state (handlers replace whole lists
|
|
308
|
+
* and counters, never edit them in place) and copies of the copy monitors and their counter. A
|
|
309
|
+
* refusal (any answer that is not 2xx) is sent as is and uses up no fault; a successful answer asks
|
|
310
|
+
* `decideFault`, and only an unfaulted one commits the drafts (and then holds a written message
|
|
311
|
+
* for `conflictWindowMs`). Plan, fault decision, and commit run synchronously together, so no
|
|
312
|
+
* other request interleaves.
|
|
313
|
+
*/
|
|
314
|
+
const handle = async (
|
|
315
|
+
raw: Request,
|
|
316
|
+
state: MicrosoftEmulatorState,
|
|
317
|
+
env: MicrosoftApiEnv,
|
|
318
|
+
decideFault: MicrosoftFaultDecision
|
|
319
|
+
): Promise<Response> => {
|
|
320
|
+
const url = new URL(raw.url)
|
|
321
|
+
const headers = raw.headers
|
|
322
|
+
const seq = Number(headers.get(requestSeqHeader) ?? '0')
|
|
323
|
+
|
|
324
|
+
// Built on demand, so the clock is read only when an error is answered.
|
|
325
|
+
const error = (status: number, code: string, message: string) =>
|
|
326
|
+
graphError(
|
|
327
|
+
errorContext(
|
|
328
|
+
env.now(),
|
|
329
|
+
Number.isSafeInteger(seq) ? seq : 0,
|
|
330
|
+
headers.get('client-request-id')
|
|
331
|
+
),
|
|
332
|
+
status,
|
|
333
|
+
code,
|
|
334
|
+
message
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
const matched = matchMicrosoftRoute(raw.method, url.pathname)
|
|
338
|
+
|
|
339
|
+
if (matched === undefined) {
|
|
340
|
+
return error(404, codes.unknownRoute, 'Synthetic: no emulated Microsoft Graph route.')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const unsupported = unsupportedQueryKey(url.searchParams, matched.route.queryKeys)
|
|
344
|
+
|
|
345
|
+
if (unsupported !== undefined) {
|
|
346
|
+
return error(
|
|
347
|
+
400,
|
|
348
|
+
codes.unsupportedQuery,
|
|
349
|
+
`Synthetic: query parameter ${unsupported} is not emulated on this route.`
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (headers.has('if-match')) {
|
|
354
|
+
return error(
|
|
355
|
+
400,
|
|
356
|
+
codes.unsupportedValue,
|
|
357
|
+
'Synthetic: conditional requests (If-Match) are not emulated.'
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const text = await raw.text()
|
|
362
|
+
|
|
363
|
+
const request: RouteRequest = {
|
|
364
|
+
params: matched.params,
|
|
365
|
+
path: url.pathname,
|
|
366
|
+
query: url.searchParams,
|
|
367
|
+
body: text === '' ? undefined : parseJsonText(text),
|
|
368
|
+
prefer: parsePreferences(headers.get('prefer')),
|
|
369
|
+
error
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const draft: MicrosoftEmulatorState = { ...state }
|
|
373
|
+
const monitors = new Map([...env.monitors].map(([id, monitor]) => [id, { ...monitor }]))
|
|
374
|
+
const monitorCounter = { next: env.monitorCounter.next }
|
|
375
|
+
const answer = matched.route.handler(draft, request, { ...env, monitors, monitorCounter })
|
|
376
|
+
const response = answer instanceof Response ? answer : answer.response
|
|
377
|
+
|
|
378
|
+
if (!response.ok) return response
|
|
379
|
+
|
|
380
|
+
if (decideFault(headers.get(emulatorJobHeader))) return answeredOutsideCore()
|
|
381
|
+
|
|
382
|
+
Object.assign(state, draft)
|
|
383
|
+
env.monitors.clear()
|
|
384
|
+
|
|
385
|
+
for (const [id, monitor] of monitors) env.monitors.set(id, monitor)
|
|
386
|
+
|
|
387
|
+
env.monitorCounter.next = monitorCounter.next
|
|
388
|
+
|
|
389
|
+
if (answer instanceof Response) return answer
|
|
390
|
+
|
|
391
|
+
env.messageLocks.add(answer.holds)
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
if (env.conflictWindowMs > 0) await delay(env.conflictWindowMs)
|
|
395
|
+
|
|
396
|
+
return answer.response
|
|
397
|
+
} finally {
|
|
398
|
+
env.messageLocks.delete(answer.holds)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Register every route of the table on the core app, over the generation's state. Each handler
|
|
404
|
+
* re-matches the raw request path against the same table the wrapper ledgers (so parameters are
|
|
405
|
+
* decoded once, failing closed on invalid percent-encoding), checks the route's query allowlist
|
|
406
|
+
* and refuses `If-Match` (no fixture sends one) before the handler runs, then plans, faults, and
|
|
407
|
+
* commits (see `handle`). A handler that throws answers `handlerFailedResponse()` (nothing
|
|
408
|
+
* written), which the wrapper turns into its Graph error envelope 500 with `responseError` in the
|
|
409
|
+
* ledger.
|
|
410
|
+
*/
|
|
411
|
+
export const registerMicrosoftApi = (
|
|
412
|
+
app: Hono,
|
|
413
|
+
state: MicrosoftEmulatorState,
|
|
414
|
+
env: MicrosoftApiEnv,
|
|
415
|
+
decideFault: MicrosoftFaultDecision
|
|
416
|
+
): void => {
|
|
417
|
+
for (const apiRoute of microsoftApiRoutes) {
|
|
418
|
+
app.on(apiRoute.method, corePath(apiRoute.path), async context => {
|
|
419
|
+
try {
|
|
420
|
+
return await handle(context.req.raw, state, env, decideFault)
|
|
421
|
+
} catch {
|
|
422
|
+
return handlerFailedResponse()
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
}
|