@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,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropbox emulator state: the typed entries, deleted-entry records, the seed input, the default
|
|
3
|
+
* seed, and the profiles (internal; re-exported by `src/dropbox.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Entry shapes and the default entries follow the synthetic Dropbox conformance fixtures, copied
|
|
6
|
+
* as data (the same paths, ids, revs, sizes, content hashes, and timestamps as the fixtures and
|
|
7
|
+
* `dropboxConformanceFixtureSeeds`), never imported from SDK code. The parent folders the seeded
|
|
8
|
+
* paths need (`/Conformance`, `/Conformance/Paging`, `/Conformance/Search`, `/Conformance/Work`)
|
|
9
|
+
* are `implied`: no fixture shows their metadata, so they hold paths together but any answer that
|
|
10
|
+
* would render one is not emulated.
|
|
11
|
+
*
|
|
12
|
+
* Minted values never repeat seeded ones: the id and rev counters start above the highest seeded
|
|
13
|
+
* id (`id:SyntheticEntryNNNNNNNN`) and rev (`a1b2c3d4e5f6NNNN`) in the minted form.
|
|
14
|
+
*
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
17
|
+
import { Predicate, Result } from 'effect'
|
|
18
|
+
import * as Schema from 'effect/Schema'
|
|
19
|
+
|
|
20
|
+
/** A Dropbox revision: lower-case hex, at least nine digits (what the connector accepts). */
|
|
21
|
+
const Rev = Schema.String.check(Schema.isPattern(/^[0-9a-f]{9,}$/))
|
|
22
|
+
|
|
23
|
+
/** A Dropbox file or folder id (`id:` form). */
|
|
24
|
+
const EntryId = Schema.String.check(Schema.isPattern(/^id:\S+$/))
|
|
25
|
+
|
|
26
|
+
/** A Dropbox timestamp as the fixtures write it (`2026-09-20T10:00:00Z`). */
|
|
27
|
+
const Timestamp = Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/))
|
|
28
|
+
|
|
29
|
+
/** One path component: non-empty, no `/`, not `.` or `..`. */
|
|
30
|
+
const EntryName = Schema.String.check(
|
|
31
|
+
Schema.makeFilter(name =>
|
|
32
|
+
name.length > 0 && !name.includes('/') && name !== '.' && name !== '..'
|
|
33
|
+
? true
|
|
34
|
+
: 'an entry name is one non-empty path component'
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
/** File fields (absent on folders). */
|
|
39
|
+
export const DropboxEmulatorFile = Schema.Struct({
|
|
40
|
+
rev: Rev,
|
|
41
|
+
size: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
42
|
+
contentHash: Schema.String,
|
|
43
|
+
clientModified: Timestamp,
|
|
44
|
+
serverModified: Timestamp
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export type DropboxEmulatorFile = typeof DropboxEmulatorFile.Type
|
|
48
|
+
|
|
49
|
+
/** A stored file or folder. Its path is its parents' names and its own, in stored casing. */
|
|
50
|
+
export const DropboxEmulatorEntry = Schema.Struct({
|
|
51
|
+
id: EntryId,
|
|
52
|
+
/** Parent folder id; `null` in the root folder. */
|
|
53
|
+
parentId: Schema.NullOr(EntryId),
|
|
54
|
+
name: EntryName,
|
|
55
|
+
/** `null` for a folder. */
|
|
56
|
+
file: Schema.NullOr(DropboxEmulatorFile),
|
|
57
|
+
/**
|
|
58
|
+
* `true` for an entry no fixture shows, held only because a seeded path needs it: lookups pass
|
|
59
|
+
* through it, but an answer that would render it is not emulated.
|
|
60
|
+
*/
|
|
61
|
+
implied: Schema.Boolean
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
export type DropboxEmulatorEntry = typeof DropboxEmulatorEntry.Type
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* What Dropbox keeps of a deleted entry (`include_deleted` lookups answer it): its name and paths
|
|
68
|
+
* as they were. `delete_v2` records one only for an empty folder, the only delete whose
|
|
69
|
+
* `include_deleted` answer a fixture records.
|
|
70
|
+
*/
|
|
71
|
+
export const DropboxEmulatorDeleted = Schema.Struct({
|
|
72
|
+
name: EntryName,
|
|
73
|
+
pathLower: Schema.String,
|
|
74
|
+
pathDisplay: Schema.String
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export type DropboxEmulatorDeleted = typeof DropboxEmulatorDeleted.Type
|
|
78
|
+
|
|
79
|
+
const Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))
|
|
80
|
+
|
|
81
|
+
const Counters = Schema.Struct({
|
|
82
|
+
/** Next number in created entry ids (`id:SyntheticEntry00000001`). */
|
|
83
|
+
nextIdNumber: Counter,
|
|
84
|
+
/** Next number in minted revs (`a1b2c3d4e5f60101`). */
|
|
85
|
+
nextRevNumber: Counter
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */
|
|
89
|
+
export const DropboxEmulatorStateSchema = Schema.Struct({
|
|
90
|
+
entries: Schema.Array(DropboxEmulatorEntry),
|
|
91
|
+
deleted: Schema.Array(DropboxEmulatorDeleted),
|
|
92
|
+
counters: Counters
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The emulator state. The container is mutable (routes replace whole lists); every entity is
|
|
97
|
+
* replaced, never edited in place.
|
|
98
|
+
*/
|
|
99
|
+
export type DropboxEmulatorState = {
|
|
100
|
+
entries: ReadonlyArray<DropboxEmulatorEntry>
|
|
101
|
+
deleted: ReadonlyArray<DropboxEmulatorDeleted>
|
|
102
|
+
counters: typeof Counters.Type
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** An absolute path in display casing: `/`-separated non-empty components, no trailing `/`. */
|
|
106
|
+
const SeedPath = Schema.String.check(Schema.isPattern(/^(?:\/[^/]+)+$/))
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A seeded entry, addressed by its display path (its parent folder must be seeded too). File
|
|
110
|
+
* fields default to synthetic values; a folder takes none.
|
|
111
|
+
*/
|
|
112
|
+
export const DropboxEmulatorEntrySeed = Schema.Struct({
|
|
113
|
+
path: SeedPath,
|
|
114
|
+
id: EntryId,
|
|
115
|
+
kind: Schema.Literals(['file', 'folder']),
|
|
116
|
+
rev: Schema.optionalKey(Rev),
|
|
117
|
+
size: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
118
|
+
contentHash: Schema.optionalKey(Schema.String),
|
|
119
|
+
clientModified: Schema.optionalKey(Timestamp),
|
|
120
|
+
serverModified: Schema.optionalKey(Timestamp),
|
|
121
|
+
/** An entry no fixture shows (default `false`); see `DropboxEmulatorEntry.implied`. */
|
|
122
|
+
implied: Schema.optionalKey(Schema.Boolean)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
export type DropboxEmulatorEntrySeed = typeof DropboxEmulatorEntrySeed.Type
|
|
126
|
+
|
|
127
|
+
/** Account-variance profiles for the default seed. */
|
|
128
|
+
export const DropboxEmulatorProfile = Schema.Literals(['default', 'empty'])
|
|
129
|
+
|
|
130
|
+
export type DropboxEmulatorProfile = typeof DropboxEmulatorProfile.Type
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A typed seed. Start from `profile` (default `'default'`, the fixture entries); `entries` and
|
|
134
|
+
* `deleted`, when given, replace that part of the profile.
|
|
135
|
+
*/
|
|
136
|
+
export const DropboxEmulatorSeed = Schema.Struct({
|
|
137
|
+
profile: Schema.optionalKey(DropboxEmulatorProfile),
|
|
138
|
+
entries: Schema.optionalKey(Schema.Array(DropboxEmulatorEntrySeed)),
|
|
139
|
+
deleted: Schema.optionalKey(Schema.Array(DropboxEmulatorDeleted))
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
export type DropboxEmulatorSeed = typeof DropboxEmulatorSeed.Type
|
|
143
|
+
|
|
144
|
+
const strict = { onExcessProperty: 'error' } as const
|
|
145
|
+
|
|
146
|
+
const decodeSeedInput = Schema.decodeUnknownResult(DropboxEmulatorSeed, strict)
|
|
147
|
+
|
|
148
|
+
const decodeStateInput = Schema.decodeUnknownResult(DropboxEmulatorStateSchema, strict)
|
|
149
|
+
|
|
150
|
+
const issueMessage = (issue: Schema.SchemaError['issue']): string =>
|
|
151
|
+
new Schema.SchemaError(issue).message
|
|
152
|
+
|
|
153
|
+
/** A synthetic 64-digit content hash ending in `suffix`, as the fixtures write them. */
|
|
154
|
+
export const syntheticContentHash = (suffix: string): string => suffix.padStart(64, '0')
|
|
155
|
+
|
|
156
|
+
// Default entries. Fixture-derived: every file, the `paging-two` folder, and every path, id, rev,
|
|
157
|
+
// size, content hash, and timestamp of them. Implied (never rendered): the `/Conformance`,
|
|
158
|
+
// `/Conformance/Paging`, `/Conformance/Search`, and `/Conformance/Work` folders.
|
|
159
|
+
|
|
160
|
+
const file = (
|
|
161
|
+
path: string,
|
|
162
|
+
id: string,
|
|
163
|
+
revSuffix: string,
|
|
164
|
+
size: number,
|
|
165
|
+
hashSuffix: string,
|
|
166
|
+
clientModified: string,
|
|
167
|
+
serverModified: string = clientModified
|
|
168
|
+
): DropboxEmulatorEntrySeed => ({
|
|
169
|
+
path,
|
|
170
|
+
id,
|
|
171
|
+
kind: 'file',
|
|
172
|
+
rev: `a1b2c3d4e5f6${revSuffix}`,
|
|
173
|
+
size,
|
|
174
|
+
contentHash: syntheticContentHash(hashSuffix),
|
|
175
|
+
clientModified,
|
|
176
|
+
serverModified
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const folder = (path: string, id: string): DropboxEmulatorEntrySeed => ({
|
|
180
|
+
path,
|
|
181
|
+
id,
|
|
182
|
+
kind: 'folder'
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
/** A parent folder no fixture shows. */
|
|
186
|
+
const impliedFolder = (path: string, id: string): DropboxEmulatorEntrySeed => ({
|
|
187
|
+
...folder(path, id),
|
|
188
|
+
implied: true
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const defaultEntries: ReadonlyArray<DropboxEmulatorEntrySeed> = [
|
|
192
|
+
impliedFolder('/Conformance', 'id:SyntheticConformanceFolder'),
|
|
193
|
+
impliedFolder('/Conformance/Paging', 'id:SyntheticPagingFolder0000'),
|
|
194
|
+
file(
|
|
195
|
+
'/Conformance/Paging/paging-one.txt',
|
|
196
|
+
'id:SyntheticPagingFile0001',
|
|
197
|
+
'0001',
|
|
198
|
+
12,
|
|
199
|
+
'1',
|
|
200
|
+
'2026-09-20T10:00:00Z'
|
|
201
|
+
),
|
|
202
|
+
folder('/Conformance/Paging/paging-two', 'id:SyntheticPagingFolder0002'),
|
|
203
|
+
file(
|
|
204
|
+
'/Conformance/Paging/paging-three.txt',
|
|
205
|
+
'id:SyntheticPagingFile0003',
|
|
206
|
+
'0003',
|
|
207
|
+
14,
|
|
208
|
+
'3',
|
|
209
|
+
'2026-09-20T10:05:00Z'
|
|
210
|
+
),
|
|
211
|
+
file(
|
|
212
|
+
'/Conformance/Mixed Case Notes.txt',
|
|
213
|
+
'id:SyntheticMixedCaseFile01',
|
|
214
|
+
'0010',
|
|
215
|
+
20,
|
|
216
|
+
'10',
|
|
217
|
+
'2026-09-20T11:00:00Z'
|
|
218
|
+
),
|
|
219
|
+
impliedFolder('/Conformance/Search', 'id:SyntheticSearchFolder0000'),
|
|
220
|
+
file(
|
|
221
|
+
'/Conformance/Search/yolk-search-probe-1.txt',
|
|
222
|
+
'id:SyntheticSearchFile0001',
|
|
223
|
+
'0021',
|
|
224
|
+
16,
|
|
225
|
+
'21',
|
|
226
|
+
'2026-09-20T12:00:00Z'
|
|
227
|
+
),
|
|
228
|
+
file(
|
|
229
|
+
'/Conformance/Search/yolk-search-probe-2.txt',
|
|
230
|
+
'id:SyntheticSearchFile0002',
|
|
231
|
+
'0022',
|
|
232
|
+
16,
|
|
233
|
+
'22',
|
|
234
|
+
'2026-09-20T12:00:00Z'
|
|
235
|
+
),
|
|
236
|
+
impliedFolder('/Conformance/Work', 'id:SyntheticWorkFolder00000'),
|
|
237
|
+
file(
|
|
238
|
+
'/Conformance/copy-source.txt',
|
|
239
|
+
'id:SyntheticCopySource001',
|
|
240
|
+
'0030',
|
|
241
|
+
42,
|
|
242
|
+
'42',
|
|
243
|
+
'2026-09-20T13:00:00Z',
|
|
244
|
+
'2026-09-29T13:00:00Z'
|
|
245
|
+
)
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
const profileEntries = (
|
|
249
|
+
profile: DropboxEmulatorProfile
|
|
250
|
+
): ReadonlyArray<DropboxEmulatorEntrySeed> => (profile === 'default' ? defaultEntries : [])
|
|
251
|
+
|
|
252
|
+
const defaultTimestamp = '2026-09-20T10:00:00Z'
|
|
253
|
+
|
|
254
|
+
/** The form of minted ids (`id:SyntheticEntry00000001`) and revs (`a1b2c3d4e5f60101`). */
|
|
255
|
+
const mintedIdPattern = /^id:SyntheticEntry(\d+)$/
|
|
256
|
+
|
|
257
|
+
const mintedRevPattern = /^a1b2c3d4e5f6(\d+)$/
|
|
258
|
+
|
|
259
|
+
/** The highest number among `values` in a minted form (0 when none is). */
|
|
260
|
+
const highestMinted = (values: ReadonlyArray<string>, pattern: RegExp): number =>
|
|
261
|
+
Math.max(0, ...values.map(value => Number(pattern.exec(value)?.[1] ?? 0)))
|
|
262
|
+
|
|
263
|
+
const duplicate = (values: ReadonlyArray<string>): string | undefined =>
|
|
264
|
+
values.find((value, index) => values.indexOf(value) !== index)
|
|
265
|
+
|
|
266
|
+
const parentPath = (path: string): string => path.slice(0, path.lastIndexOf('/'))
|
|
267
|
+
|
|
268
|
+
const lastName = (path: string): string => path.slice(path.lastIndexOf('/') + 1)
|
|
269
|
+
|
|
270
|
+
/** Build the emulator state for a decoded seed; a string is an integrity problem. */
|
|
271
|
+
const stateFromSeed = (seed: DropboxEmulatorSeed): DropboxEmulatorState | string => {
|
|
272
|
+
const seeded = seed.entries ?? profileEntries(seed.profile ?? 'default')
|
|
273
|
+
|
|
274
|
+
const duplicates: ReadonlyArray<readonly [string, string | undefined]> = [
|
|
275
|
+
['entry id', duplicate(seeded.map(entry => entry.id))],
|
|
276
|
+
['entry path', duplicate(seeded.map(entry => entry.path.toLowerCase()))]
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
for (const [label, value] of duplicates) {
|
|
280
|
+
if (value !== undefined) return `duplicate ${label} ${value}`
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const byPath = new Map(seeded.map(entry => [entry.path.toLowerCase(), entry]))
|
|
284
|
+
const entries: Array<DropboxEmulatorEntry> = []
|
|
285
|
+
|
|
286
|
+
for (const entry of seeded) {
|
|
287
|
+
const name = lastName(entry.path)
|
|
288
|
+
|
|
289
|
+
if (name === '.' || name === '..') return `entry ${entry.id} has an invalid path`
|
|
290
|
+
|
|
291
|
+
const parent = parentPath(entry.path)
|
|
292
|
+
const parentEntry = parent === '' ? undefined : byPath.get(parent.toLowerCase())
|
|
293
|
+
|
|
294
|
+
if (parent !== '' && (parentEntry === undefined || parentEntry.kind !== 'folder')) {
|
|
295
|
+
return `entry ${entry.path} needs a seeded parent folder ${parent}`
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const hasFileFields = [
|
|
299
|
+
entry.rev,
|
|
300
|
+
entry.size,
|
|
301
|
+
entry.contentHash,
|
|
302
|
+
entry.clientModified,
|
|
303
|
+
entry.serverModified
|
|
304
|
+
].some(Predicate.isNotUndefined)
|
|
305
|
+
|
|
306
|
+
if (entry.kind === 'folder' && hasFileFields) {
|
|
307
|
+
return `folder ${entry.path} cannot take file fields`
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const clientModified = entry.clientModified ?? defaultTimestamp
|
|
311
|
+
|
|
312
|
+
entries.push({
|
|
313
|
+
id: entry.id,
|
|
314
|
+
parentId: parentEntry?.id ?? null,
|
|
315
|
+
name,
|
|
316
|
+
implied: entry.implied ?? false,
|
|
317
|
+
file:
|
|
318
|
+
entry.kind === 'folder'
|
|
319
|
+
? null
|
|
320
|
+
: {
|
|
321
|
+
rev: entry.rev ?? 'a1b2c3d4e5f60000',
|
|
322
|
+
size: entry.size ?? 0,
|
|
323
|
+
contentHash: entry.contentHash ?? syntheticContentHash('0'),
|
|
324
|
+
clientModified,
|
|
325
|
+
serverModified: entry.serverModified ?? clientModified
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
entries,
|
|
332
|
+
deleted: seed.deleted ?? [],
|
|
333
|
+
counters: {
|
|
334
|
+
nextIdNumber:
|
|
335
|
+
highestMinted(
|
|
336
|
+
entries.map(entry => entry.id),
|
|
337
|
+
mintedIdPattern
|
|
338
|
+
) + 1,
|
|
339
|
+
nextRevNumber: Math.max(
|
|
340
|
+
101,
|
|
341
|
+
highestMinted(
|
|
342
|
+
entries.flatMap(entry => entry.file?.rev ?? []),
|
|
343
|
+
mintedRevPattern
|
|
344
|
+
) + 1
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Decode and build a seed; a string is the reason it is invalid. */
|
|
351
|
+
export const buildSeedState = (input: unknown): DropboxEmulatorState | string => {
|
|
352
|
+
const decoded = decodeSeedInput(input)
|
|
353
|
+
|
|
354
|
+
return Result.isFailure(decoded)
|
|
355
|
+
? issueMessage(decoded.failure.issue)
|
|
356
|
+
: stateFromSeed(decoded.success)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */
|
|
360
|
+
export const decodeState = (input: unknown): DropboxEmulatorState | string => {
|
|
361
|
+
const decoded = decodeStateInput(input)
|
|
362
|
+
|
|
363
|
+
return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }
|
|
364
|
+
}
|
package/src/dropbox.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stateful Dropbox emulator (the `/2` RPC routes on `https://api.dropboxapi.com` and the upload
|
|
3
|
+
* route on `https://content.dropboxapi.com`), built on the upstream `@emulators/core` custom
|
|
4
|
+
* runtime, with a request ledger, status faults, and an `/_emulate/*` control plane.
|
|
5
|
+
*
|
|
6
|
+
* It never imports SDK code: its wire shapes, error envelopes, and default seed are copied as data
|
|
7
|
+
* from the synthetic Dropbox conformance fixtures, and every route names the conformance cases it
|
|
8
|
+
* follows in `dropboxEmulatorRoutes`. Only the routes those eight cases (with their cleanup) send
|
|
9
|
+
* are emulated, each on the origin its fixtures record. Response behaviour comes only from the
|
|
10
|
+
* fixtures: anything they do not show answers one ledgered 400 not-emulated, writes nothing, and
|
|
11
|
+
* uses up no fault. That includes a listing of an empty folder, so the read-only leftover lookup
|
|
12
|
+
* fails against the default seed's empty work folder (the live runner prints its lookup-failed
|
|
13
|
+
* WARN).
|
|
14
|
+
*
|
|
15
|
+
* Request-shape latitude (`/dropbox`, the only accepted deviations): any bearer value (never
|
|
16
|
+
* checked or stored); extra request headers; JSON key order; `content-type` media-type
|
|
17
|
+
* parameters; any path and search query (looked up in the state); any `list_folder` `limit` from
|
|
18
|
+
* 1 to 2000 and any `search_v2` `options.max_results` from 1 to 1000; and any upload body bytes.
|
|
19
|
+
* Everything else (other keys, booleans, modes, query parameters, another origin, ids or revs
|
|
20
|
+
* where the fixtures send paths, the root folder, and cursors not issued since the last reset or
|
|
21
|
+
* whose listing changed) is not emulated.
|
|
22
|
+
*
|
|
23
|
+
* Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by
|
|
24
|
+
* `makeDropboxEmulator` (importing this module has no side effects).
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
import { Data, Predicate } from 'effect'
|
|
29
|
+
import {
|
|
30
|
+
dropboxApiRoutes,
|
|
31
|
+
dropboxEmulatorDrillKnobs,
|
|
32
|
+
type DropboxApiEnv,
|
|
33
|
+
type DropboxCursor,
|
|
34
|
+
type DropboxEmulatorDrills
|
|
35
|
+
} from './dropbox/api.ts'
|
|
36
|
+
import {
|
|
37
|
+
buildSeedState,
|
|
38
|
+
decodeState,
|
|
39
|
+
type DropboxEmulatorSeed,
|
|
40
|
+
type DropboxEmulatorState
|
|
41
|
+
} from './dropbox/state.ts'
|
|
42
|
+
import type { EmulatorRouteEvidence } from './route-evidence.ts'
|
|
43
|
+
import {
|
|
44
|
+
StatefulFault,
|
|
45
|
+
StatefulFaultMatch,
|
|
46
|
+
checkBooleanDrills,
|
|
47
|
+
makeStatefulEmulator,
|
|
48
|
+
routeEvidence,
|
|
49
|
+
type StatefulCoverage,
|
|
50
|
+
type StatefulEmulatorApi,
|
|
51
|
+
type StatefulFaultState,
|
|
52
|
+
type StatefulInputKind,
|
|
53
|
+
type StatefulLedgerEntry
|
|
54
|
+
} from './stateful-emulator.ts'
|
|
55
|
+
import { statefulCoreRuntime } from './stateful-core.ts'
|
|
56
|
+
|
|
57
|
+
export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
|
|
58
|
+
|
|
59
|
+
export { emulatorEvidenceHeader } from './route-evidence.ts'
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
dropboxEmulatorApiOrigin,
|
|
63
|
+
dropboxEmulatorBasePath,
|
|
64
|
+
dropboxEmulatorContentOrigin,
|
|
65
|
+
dropboxEmulatorErrorBodies,
|
|
66
|
+
type DropboxEmulatorDrills
|
|
67
|
+
} from './dropbox/api.ts'
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
DropboxEmulatorDeleted,
|
|
71
|
+
DropboxEmulatorEntry,
|
|
72
|
+
DropboxEmulatorEntrySeed,
|
|
73
|
+
DropboxEmulatorFile,
|
|
74
|
+
DropboxEmulatorProfile,
|
|
75
|
+
DropboxEmulatorSeed,
|
|
76
|
+
DropboxEmulatorStateSchema,
|
|
77
|
+
type DropboxEmulatorState
|
|
78
|
+
} from './dropbox/state.ts'
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Route evidence manifest: every emulated route, whether it writes, and the conformance cases
|
|
82
|
+
* whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
|
|
83
|
+
* by construction (both come from one route table).
|
|
84
|
+
*/
|
|
85
|
+
export const dropboxEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> =
|
|
86
|
+
dropboxApiRoutes.map(routeEvidence)
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
|
|
90
|
+
* `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
|
|
91
|
+
* manifest (another value is rejected when the fault is added).
|
|
92
|
+
*/
|
|
93
|
+
export const DropboxFaultMatch = StatefulFaultMatch
|
|
94
|
+
|
|
95
|
+
export type DropboxFaultMatch = typeof StatefulFaultMatch.Type
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A status fault (400-599): answer matching requests with this status, headers, and body instead
|
|
99
|
+
* of the route's write, so nothing is written. The body defaults to
|
|
100
|
+
* `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
|
|
101
|
+
* Only a request the emulator would answer reaches a fault: a request that is not emulated, by
|
|
102
|
+
* its shape or by the state, never uses one up.
|
|
103
|
+
*/
|
|
104
|
+
export const DropboxFault = StatefulFault
|
|
105
|
+
|
|
106
|
+
export type DropboxFault = StatefulFault
|
|
107
|
+
|
|
108
|
+
export type DropboxFaultState = StatefulFaultState
|
|
109
|
+
|
|
110
|
+
export type DropboxLedgerEntry = StatefulLedgerEntry
|
|
111
|
+
|
|
112
|
+
export type DropboxCoverage = StatefulCoverage
|
|
113
|
+
|
|
114
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
115
|
+
export class DropboxEmulatorInputInvalid extends Data.TaggedError('DropboxEmulatorInputInvalid')<{
|
|
116
|
+
readonly input: StatefulInputKind
|
|
117
|
+
readonly reason: string
|
|
118
|
+
}> {
|
|
119
|
+
override get message(): string {
|
|
120
|
+
return `Invalid Dropbox emulator ${this.input}: ${this.reason}`
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A cursor as reported by `cursors()` and `/_emulate/state`. */
|
|
125
|
+
export type DropboxCursorState = {
|
|
126
|
+
readonly cursor: string
|
|
127
|
+
readonly kind: DropboxCursor['kind']
|
|
128
|
+
/** Position of the next page in the listing or search. */
|
|
129
|
+
readonly offset: number
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type DropboxEmulatorOptions = {
|
|
133
|
+
/** Typed seed; defaults to the fixture entries (`profile: 'default'`). */
|
|
134
|
+
readonly seed?: DropboxEmulatorSeed
|
|
135
|
+
/** Clock in epoch milliseconds (upload timestamps). Defaults to `Date.now`. */
|
|
136
|
+
readonly now?: () => number
|
|
137
|
+
/** Drill knobs (tests only): make the emulator disagree with one conformance claim. */
|
|
138
|
+
readonly drills?: DropboxEmulatorDrills
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type DropboxEmulator = StatefulEmulatorApi<DropboxEmulatorState, DropboxEmulatorSeed> & {
|
|
142
|
+
/** The list and search cursors issued since the last reset or seed (runtime data). */
|
|
143
|
+
readonly cursors: () => ReadonlyArray<DropboxCursorState>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const inputInvalid = (input: StatefulInputKind, reason: string) =>
|
|
147
|
+
new DropboxEmulatorInputInvalid({ input, reason })
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a stateful Dropbox emulator on the `@emulators/core` custom runtime. Each call has its
|
|
151
|
+
* own state, ledger, faults, and cursors. Rejects with `DropboxEmulatorInputInvalid` for an
|
|
152
|
+
* invalid seed or option. See `src/stateful-emulator.ts` for the request precedence.
|
|
153
|
+
*/
|
|
154
|
+
export const makeDropboxEmulator = async (
|
|
155
|
+
options: DropboxEmulatorOptions = {}
|
|
156
|
+
): Promise<DropboxEmulator> => {
|
|
157
|
+
const initial = buildSeedState(options.seed ?? {})
|
|
158
|
+
|
|
159
|
+
if (Predicate.isString(initial)) {
|
|
160
|
+
throw inputInvalid('seed', initial)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
checkBooleanDrills(options.drills, dropboxEmulatorDrillKnobs, inputInvalid)
|
|
164
|
+
|
|
165
|
+
const drills = options.drills ?? {}
|
|
166
|
+
|
|
167
|
+
const env: DropboxApiEnv = {
|
|
168
|
+
now: options.now ?? (() => Date.now()),
|
|
169
|
+
drills: {
|
|
170
|
+
listFolderSinglePage: drills.listFolderSinglePage === true,
|
|
171
|
+
getMetadataCaseSensitive: drills.getMetadataCaseSensitive === true,
|
|
172
|
+
searchRepeatsMatches: drills.searchRepeatsMatches === true,
|
|
173
|
+
notFoundAsPathLookup: drills.notFoundAsPathLookup === true,
|
|
174
|
+
folderConflictAsFile: drills.folderConflictAsFile === true,
|
|
175
|
+
deleteLeavesNoTombstone: drills.deleteLeavesNoTombstone === true,
|
|
176
|
+
moveMintsNewId: drills.moveMintsNewId === true,
|
|
177
|
+
uploadIgnoresRev: drills.uploadIgnoresRev === true
|
|
178
|
+
},
|
|
179
|
+
cursors: new Map(),
|
|
180
|
+
cursorCounters: { list: 1, search: 1 }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const cursors = (): ReadonlyArray<DropboxCursorState> =>
|
|
184
|
+
[...env.cursors].map(([cursor, state]) => ({ cursor, kind: state.kind, offset: state.offset }))
|
|
185
|
+
|
|
186
|
+
const api = await makeStatefulEmulator<DropboxEmulatorState, DropboxApiEnv, DropboxEmulatorSeed>(
|
|
187
|
+
{
|
|
188
|
+
routes: dropboxApiRoutes,
|
|
189
|
+
env,
|
|
190
|
+
initial,
|
|
191
|
+
buildSeed: buildSeedState,
|
|
192
|
+
recordHeaders: [{ name: 'dropbox-api-arg', json: true }],
|
|
193
|
+
// Cursor numbers keep advancing, so a cursor issued before a reset is never issued again.
|
|
194
|
+
clearRuntime: () => env.cursors.clear(),
|
|
195
|
+
runtimeState: () => ({ cursors: cursors() }),
|
|
196
|
+
seedSummary: state => ({ entries: state.entries.length }),
|
|
197
|
+
inputInvalid
|
|
198
|
+
},
|
|
199
|
+
statefulCoreRuntime({ name: 'dropbox', initial, decodeState, inputInvalid })
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return { ...api, cursors }
|
|
203
|
+
}
|