@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,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion emulator state: the typed bot user, pages, blocks, paginated property items, databases,
|
|
3
|
+
* and data sources, the seed input, the default seed, and the profiles (internal; re-exported by
|
|
4
|
+
* `src/notion.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Entity shapes and the default entities follow the synthetic Notion conformance fixtures (API
|
|
7
|
+
* version 2025-09-03), copied as data (the same ids, titles, properties, timestamps, and URLs as
|
|
8
|
+
* the fixtures and `notionConformanceFixtureSeeds`), never imported from SDK code. Pages a fixture
|
|
9
|
+
* only names by id (the blocks page, the write case's parent page, the database's parent page, and
|
|
10
|
+
* the second data source row the query fixture's `next_cursor` names) are `impliedPages`: their ids
|
|
11
|
+
* resolve where a fixture names them, but no content is invented for them, so any answer that
|
|
12
|
+
* would render one is not emulated.
|
|
13
|
+
*
|
|
14
|
+
* Minted page ids never repeat seeded ones: the page counter starts above the highest seeded id
|
|
15
|
+
* in the minted form (`1f0000e0-0000-4000-8000-NNNNNNNNNNNN`).
|
|
16
|
+
*
|
|
17
|
+
* Pages and other objects are stored with their wire fields (`properties`, `parent`, rich text)
|
|
18
|
+
* as JSON, answered as stored. A page's `properties` value is what the page object answers; the
|
|
19
|
+
* items the property endpoint pages through are stored separately (`propertyItems`), as the
|
|
20
|
+
* fixtures show them (the page object of the property page carries only the first segment).
|
|
21
|
+
*
|
|
22
|
+
* @experimental
|
|
23
|
+
*/
|
|
24
|
+
import { Result } from 'effect'
|
|
25
|
+
import * as Schema from 'effect/Schema'
|
|
26
|
+
|
|
27
|
+
/** A Notion id as stored and answered: a lower-case dashed UUID. */
|
|
28
|
+
export const notionIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
29
|
+
|
|
30
|
+
const NotionId = Schema.String.check(Schema.isPattern(notionIdPattern))
|
|
31
|
+
|
|
32
|
+
const Timestamp = Schema.String
|
|
33
|
+
|
|
34
|
+
const JsonObject = Schema.Record(Schema.String, Schema.Json)
|
|
35
|
+
|
|
36
|
+
/** The integration's bot user (`GET /v1/users/me`). */
|
|
37
|
+
export const NotionEmulatorBotUser = Schema.Struct({
|
|
38
|
+
id: NotionId,
|
|
39
|
+
name: Schema.String,
|
|
40
|
+
avatarUrl: Schema.NullOr(Schema.String),
|
|
41
|
+
workspaceName: Schema.String
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export type NotionEmulatorBotUser = typeof NotionEmulatorBotUser.Type
|
|
45
|
+
|
|
46
|
+
/** A page parent as Notion answers it. */
|
|
47
|
+
export const NotionEmulatorParent = Schema.Union([
|
|
48
|
+
Schema.Struct({ type: Schema.Literal('workspace'), workspace: Schema.Literal(true) }),
|
|
49
|
+
Schema.Struct({ type: Schema.Literal('page_id'), page_id: NotionId }),
|
|
50
|
+
Schema.Struct({
|
|
51
|
+
type: Schema.Literal('data_source_id'),
|
|
52
|
+
data_source_id: NotionId,
|
|
53
|
+
database_id: NotionId
|
|
54
|
+
})
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
export type NotionEmulatorParent = typeof NotionEmulatorParent.Type
|
|
58
|
+
|
|
59
|
+
/** A stored page. `times` is `null` where the fixture omits both timestamps (data source rows). */
|
|
60
|
+
export const NotionEmulatorPage = Schema.Struct({
|
|
61
|
+
id: NotionId,
|
|
62
|
+
times: Schema.NullOr(Schema.Struct({ created: Timestamp, lastEdited: Timestamp })),
|
|
63
|
+
parent: NotionEmulatorParent,
|
|
64
|
+
archived: Schema.Boolean,
|
|
65
|
+
inTrash: Schema.Boolean,
|
|
66
|
+
/** The page object's `properties`, answered as stored. */
|
|
67
|
+
properties: JsonObject,
|
|
68
|
+
url: Schema.String
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export type NotionEmulatorPage = typeof NotionEmulatorPage.Type
|
|
72
|
+
|
|
73
|
+
/** A stored block, a child of a page. */
|
|
74
|
+
export const NotionEmulatorBlock = Schema.Struct({
|
|
75
|
+
id: NotionId,
|
|
76
|
+
pageId: NotionId,
|
|
77
|
+
createdTime: Timestamp,
|
|
78
|
+
lastEditedTime: Timestamp,
|
|
79
|
+
hasChildren: Schema.Boolean,
|
|
80
|
+
archived: Schema.Boolean,
|
|
81
|
+
inTrash: Schema.Boolean,
|
|
82
|
+
/** Block type, for example `paragraph`. */
|
|
83
|
+
type: Schema.String,
|
|
84
|
+
/** The type-specific value (`paragraph: { rich_text, color }`). */
|
|
85
|
+
value: JsonObject
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export type NotionEmulatorBlock = typeof NotionEmulatorBlock.Type
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The items of one paginated page property, as the property endpoint pages through them. Each
|
|
92
|
+
* item answers `{ object: "property_item", id, type, [type]: value }`.
|
|
93
|
+
*/
|
|
94
|
+
export const NotionEmulatorPropertyItems = Schema.Struct({
|
|
95
|
+
pageId: NotionId,
|
|
96
|
+
/** The property id exactly as the page object returns it (for example `Syn%3Ap`). */
|
|
97
|
+
propertyId: Schema.String,
|
|
98
|
+
type: Schema.String,
|
|
99
|
+
values: Schema.Array(Schema.Json)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
export type NotionEmulatorPropertyItems = typeof NotionEmulatorPropertyItems.Type
|
|
103
|
+
|
|
104
|
+
export const NotionEmulatorDatabase = Schema.Struct({
|
|
105
|
+
id: NotionId,
|
|
106
|
+
title: Schema.Array(Schema.Json),
|
|
107
|
+
description: Schema.Array(Schema.Json),
|
|
108
|
+
parent: Schema.Struct({ type: Schema.Literal('page_id'), page_id: NotionId }),
|
|
109
|
+
isInline: Schema.Boolean,
|
|
110
|
+
inTrash: Schema.Boolean,
|
|
111
|
+
archived: Schema.Boolean,
|
|
112
|
+
createdTime: Timestamp,
|
|
113
|
+
lastEditedTime: Timestamp,
|
|
114
|
+
url: Schema.String
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export type NotionEmulatorDatabase = typeof NotionEmulatorDatabase.Type
|
|
118
|
+
|
|
119
|
+
/** A data source of a database: its schema; its rows are pages with a `data_source_id` parent. */
|
|
120
|
+
export const NotionEmulatorDataSource = Schema.Struct({
|
|
121
|
+
id: NotionId,
|
|
122
|
+
databaseId: NotionId,
|
|
123
|
+
/** Rich text title; its plain text is the name the database lists. */
|
|
124
|
+
title: Schema.Array(Schema.Json),
|
|
125
|
+
properties: JsonObject,
|
|
126
|
+
archived: Schema.Boolean,
|
|
127
|
+
inTrash: Schema.Boolean
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
export type NotionEmulatorDataSource = typeof NotionEmulatorDataSource.Type
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A page a fixture names by id but never shows (no title, properties, or timestamps): accepted
|
|
134
|
+
* where a fixture names it (a block parent, a create parent, a database parent, a query row), never
|
|
135
|
+
* rendered. `parent` is set only for a data source row.
|
|
136
|
+
*/
|
|
137
|
+
export const NotionEmulatorImpliedPage = Schema.Struct({
|
|
138
|
+
id: NotionId,
|
|
139
|
+
parent: Schema.NullOr(NotionEmulatorParent)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
export type NotionEmulatorImpliedPage = typeof NotionEmulatorImpliedPage.Type
|
|
143
|
+
|
|
144
|
+
const Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))
|
|
145
|
+
|
|
146
|
+
const Counters = Schema.Struct({
|
|
147
|
+
/** Next number in created page ids (`1f0000e0-0000-4000-8000-000000000001`). */
|
|
148
|
+
nextPageNumber: Counter
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
/** The whole emulator state (JSON-compatible; what `snapshot()` returns). */
|
|
152
|
+
export const NotionEmulatorStateSchema = Schema.Struct({
|
|
153
|
+
botUser: NotionEmulatorBotUser,
|
|
154
|
+
pages: Schema.Array(NotionEmulatorPage),
|
|
155
|
+
impliedPages: Schema.Array(NotionEmulatorImpliedPage),
|
|
156
|
+
blocks: Schema.Array(NotionEmulatorBlock),
|
|
157
|
+
propertyItems: Schema.Array(NotionEmulatorPropertyItems),
|
|
158
|
+
databases: Schema.Array(NotionEmulatorDatabase),
|
|
159
|
+
dataSources: Schema.Array(NotionEmulatorDataSource),
|
|
160
|
+
counters: Counters
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The emulator state. The container is mutable (routes replace whole lists); every entity is
|
|
165
|
+
* replaced, never edited in place.
|
|
166
|
+
*/
|
|
167
|
+
export type NotionEmulatorState = {
|
|
168
|
+
botUser: NotionEmulatorBotUser
|
|
169
|
+
pages: ReadonlyArray<NotionEmulatorPage>
|
|
170
|
+
impliedPages: ReadonlyArray<NotionEmulatorImpliedPage>
|
|
171
|
+
blocks: ReadonlyArray<NotionEmulatorBlock>
|
|
172
|
+
propertyItems: ReadonlyArray<NotionEmulatorPropertyItems>
|
|
173
|
+
databases: ReadonlyArray<NotionEmulatorDatabase>
|
|
174
|
+
dataSources: ReadonlyArray<NotionEmulatorDataSource>
|
|
175
|
+
counters: typeof Counters.Type
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Account-variance profiles for the default seed. */
|
|
179
|
+
export const NotionEmulatorProfile = Schema.Literals(['default', 'empty'])
|
|
180
|
+
|
|
181
|
+
export type NotionEmulatorProfile = typeof NotionEmulatorProfile.Type
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* A typed seed. Start from `profile` (default `'default'`, the fixture entities); every other key,
|
|
185
|
+
* when given, replaces that part of the profile.
|
|
186
|
+
*/
|
|
187
|
+
export const NotionEmulatorSeed = Schema.Struct({
|
|
188
|
+
profile: Schema.optionalKey(NotionEmulatorProfile),
|
|
189
|
+
botUser: Schema.optionalKey(NotionEmulatorBotUser),
|
|
190
|
+
pages: Schema.optionalKey(Schema.Array(NotionEmulatorPage)),
|
|
191
|
+
impliedPages: Schema.optionalKey(Schema.Array(NotionEmulatorImpliedPage)),
|
|
192
|
+
blocks: Schema.optionalKey(Schema.Array(NotionEmulatorBlock)),
|
|
193
|
+
propertyItems: Schema.optionalKey(Schema.Array(NotionEmulatorPropertyItems)),
|
|
194
|
+
databases: Schema.optionalKey(Schema.Array(NotionEmulatorDatabase)),
|
|
195
|
+
dataSources: Schema.optionalKey(Schema.Array(NotionEmulatorDataSource))
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
export type NotionEmulatorSeed = typeof NotionEmulatorSeed.Type
|
|
199
|
+
|
|
200
|
+
const strict = { onExcessProperty: 'error' } as const
|
|
201
|
+
|
|
202
|
+
const decodeSeedInput = Schema.decodeUnknownResult(NotionEmulatorSeed, strict)
|
|
203
|
+
|
|
204
|
+
const decodeStateInput = Schema.decodeUnknownResult(NotionEmulatorStateSchema, strict)
|
|
205
|
+
|
|
206
|
+
const issueMessage = (issue: Schema.SchemaError['issue']): string =>
|
|
207
|
+
new Schema.SchemaError(issue).message
|
|
208
|
+
|
|
209
|
+
/** A plain rich text item as the fixtures write it (no annotations). */
|
|
210
|
+
export const plainRichText = (text: string): Schema.JsonObject => ({
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: { content: text, link: null },
|
|
213
|
+
plain_text: text,
|
|
214
|
+
href: null
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
/** A page `title` property as the fixtures write it. */
|
|
218
|
+
export const titleProperty = (items: ReadonlyArray<Schema.Json>): Schema.JsonObject => ({
|
|
219
|
+
id: 'title',
|
|
220
|
+
type: 'title',
|
|
221
|
+
title: [...items]
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Default entities, copied from the fixtures: the bot user, the two search pages, the title page
|
|
225
|
+
// with its two annotated title items, the three paragraph blocks, the property page with its
|
|
226
|
+
// `Notes` property and three rich-text segments, the database, its data source, and its first row.
|
|
227
|
+
// Implied (named by a fixture, never shown): the blocks page, the write case's parent page, the
|
|
228
|
+
// database's parent page, and the second row.
|
|
229
|
+
|
|
230
|
+
const seededAt = '2026-09-20T09:00:00.000Z'
|
|
231
|
+
|
|
232
|
+
const workspace: NotionEmulatorParent = { type: 'workspace', workspace: true }
|
|
233
|
+
|
|
234
|
+
const annotations = (bold: boolean): Schema.JsonObject => ({
|
|
235
|
+
bold,
|
|
236
|
+
italic: false,
|
|
237
|
+
strikethrough: false,
|
|
238
|
+
underline: false,
|
|
239
|
+
code: false,
|
|
240
|
+
color: 'default'
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const workspacePage = (
|
|
244
|
+
id: string,
|
|
245
|
+
title: ReadonlyArray<Schema.Json>,
|
|
246
|
+
url: string,
|
|
247
|
+
extra: Schema.JsonObject = {}
|
|
248
|
+
): NotionEmulatorPage => ({
|
|
249
|
+
id,
|
|
250
|
+
times: { created: seededAt, lastEdited: seededAt },
|
|
251
|
+
parent: workspace,
|
|
252
|
+
archived: false,
|
|
253
|
+
inTrash: false,
|
|
254
|
+
properties: { ...extra, title: titleProperty(title) },
|
|
255
|
+
url
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const searchPage = (index: number): NotionEmulatorPage =>
|
|
259
|
+
workspacePage(
|
|
260
|
+
`1f0000a0-0000-4000-8000-00000000000${index}`,
|
|
261
|
+
[plainRichText(`yolk-search-probe ${index}`)],
|
|
262
|
+
// The search fixture's URL carries a shortened id (30 hex digits); copied as is.
|
|
263
|
+
`https://www.notion.so/yolk-search-probe-${index}-1f0000a0000040008000000000000${index}`
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
/** A rich text item with annotations, as the title fixture writes it. */
|
|
267
|
+
const annotatedRichText = (text: string, bold: boolean): Schema.JsonObject => ({
|
|
268
|
+
type: 'text',
|
|
269
|
+
text: { content: text, link: null },
|
|
270
|
+
annotations: annotations(bold),
|
|
271
|
+
plain_text: text,
|
|
272
|
+
href: null
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
const titlePage = workspacePage(
|
|
276
|
+
'1f000000-0000-4000-8000-000000000001',
|
|
277
|
+
[annotatedRichText('Synthetic ', false), annotatedRichText('Title Page', true)],
|
|
278
|
+
'https://www.notion.so/Synthetic-Title-Page-1f000000000040008000000000000001'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const blocksPageId = '1f000000-0000-4000-8000-000000000002'
|
|
282
|
+
|
|
283
|
+
const propertyPageId = '1f000000-0000-4000-8000-000000000003'
|
|
284
|
+
|
|
285
|
+
const propertyId = 'Syn%3Ap'
|
|
286
|
+
|
|
287
|
+
const segment = (index: number) => plainRichText(`Synthetic segment ${index} `)
|
|
288
|
+
|
|
289
|
+
const propertyPage = workspacePage(
|
|
290
|
+
propertyPageId,
|
|
291
|
+
[plainRichText('Synthetic Property Page')],
|
|
292
|
+
'https://www.notion.so/Synthetic-Property-Page-1f000000000040008000000000000003',
|
|
293
|
+
{ Notes: { id: propertyId, type: 'rich_text', rich_text: [segment(1)] } }
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const parentPageId = '1f000000-0000-4000-8000-000000000005'
|
|
297
|
+
|
|
298
|
+
const databaseId = '1f000000-0000-4000-8000-000000000004'
|
|
299
|
+
|
|
300
|
+
const dataSourceId = '1f0000d0-0000-4000-8000-000000000001'
|
|
301
|
+
|
|
302
|
+
const databaseParentId = '1f0000d0-0000-4000-8000-0000000000aa'
|
|
303
|
+
|
|
304
|
+
const rowParent: NotionEmulatorParent = {
|
|
305
|
+
type: 'data_source_id',
|
|
306
|
+
data_source_id: dataSourceId,
|
|
307
|
+
database_id: databaseId
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** The query fixture's row (its row object carries no timestamps). */
|
|
311
|
+
const firstRow: NotionEmulatorPage = {
|
|
312
|
+
id: '1f0000d0-0000-4000-8000-000000000101',
|
|
313
|
+
times: null,
|
|
314
|
+
parent: rowParent,
|
|
315
|
+
archived: false,
|
|
316
|
+
inTrash: false,
|
|
317
|
+
properties: {
|
|
318
|
+
Name: { id: 'title', type: 'title', title: [plainRichText('Synthetic task 1')] },
|
|
319
|
+
Done: { id: 'Syn%3Ad', type: 'checkbox', checkbox: false }
|
|
320
|
+
},
|
|
321
|
+
url: 'https://www.notion.so/Synthetic-task-1-1f0000d0000040008000000000000101'
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const paragraph = (index: number): NotionEmulatorBlock => ({
|
|
325
|
+
id: `1f0000c0-0000-4000-8000-00000000000${index}`,
|
|
326
|
+
pageId: blocksPageId,
|
|
327
|
+
createdTime: seededAt,
|
|
328
|
+
lastEditedTime: seededAt,
|
|
329
|
+
hasChildren: false,
|
|
330
|
+
archived: false,
|
|
331
|
+
inTrash: false,
|
|
332
|
+
type: 'paragraph',
|
|
333
|
+
value: { rich_text: [plainRichText(`Synthetic paragraph ${index}`)], color: 'default' }
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const defaultBotUser: NotionEmulatorBotUser = {
|
|
337
|
+
id: '1f0000b0-0000-4000-8000-000000000001',
|
|
338
|
+
name: 'Synthetic Integration',
|
|
339
|
+
avatarUrl: null,
|
|
340
|
+
workspaceName: 'Synthetic Workspace'
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const tasksTitle = [plainRichText('Synthetic Tasks')]
|
|
344
|
+
|
|
345
|
+
type ProfileEntities = Omit<NotionEmulatorState, 'counters'>
|
|
346
|
+
|
|
347
|
+
const profileEntities = (profile: NotionEmulatorProfile): ProfileEntities =>
|
|
348
|
+
profile === 'default'
|
|
349
|
+
? {
|
|
350
|
+
botUser: defaultBotUser,
|
|
351
|
+
pages: [searchPage(1), searchPage(2), titlePage, propertyPage, firstRow],
|
|
352
|
+
impliedPages: [
|
|
353
|
+
{ id: blocksPageId, parent: null },
|
|
354
|
+
{ id: parentPageId, parent: null },
|
|
355
|
+
{ id: databaseParentId, parent: null },
|
|
356
|
+
{ id: '1f0000d0-0000-4000-8000-000000000102', parent: rowParent }
|
|
357
|
+
],
|
|
358
|
+
blocks: [paragraph(1), paragraph(2), paragraph(3)],
|
|
359
|
+
propertyItems: [
|
|
360
|
+
{
|
|
361
|
+
pageId: propertyPageId,
|
|
362
|
+
propertyId,
|
|
363
|
+
type: 'rich_text',
|
|
364
|
+
values: [segment(1), segment(2), segment(3)]
|
|
365
|
+
}
|
|
366
|
+
],
|
|
367
|
+
databases: [
|
|
368
|
+
{
|
|
369
|
+
id: databaseId,
|
|
370
|
+
title: tasksTitle,
|
|
371
|
+
description: [],
|
|
372
|
+
parent: { type: 'page_id', page_id: databaseParentId },
|
|
373
|
+
isInline: false,
|
|
374
|
+
inTrash: false,
|
|
375
|
+
archived: false,
|
|
376
|
+
createdTime: seededAt,
|
|
377
|
+
lastEditedTime: seededAt,
|
|
378
|
+
url: 'https://www.notion.so/1f000000000040008000000000000004'
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
dataSources: [
|
|
382
|
+
{
|
|
383
|
+
id: dataSourceId,
|
|
384
|
+
databaseId,
|
|
385
|
+
title: tasksTitle,
|
|
386
|
+
properties: {
|
|
387
|
+
Name: { id: 'title', name: 'Name', type: 'title', title: {} },
|
|
388
|
+
Done: { id: 'Syn%3Ad', name: 'Done', type: 'checkbox', checkbox: {} }
|
|
389
|
+
},
|
|
390
|
+
archived: false,
|
|
391
|
+
inTrash: false
|
|
392
|
+
}
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
: {
|
|
396
|
+
botUser: defaultBotUser,
|
|
397
|
+
pages: [],
|
|
398
|
+
impliedPages: [],
|
|
399
|
+
blocks: [],
|
|
400
|
+
propertyItems: [],
|
|
401
|
+
databases: [],
|
|
402
|
+
dataSources: []
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const duplicate = (values: ReadonlyArray<string>): string | undefined =>
|
|
406
|
+
values.find((value, index) => values.indexOf(value) !== index)
|
|
407
|
+
|
|
408
|
+
/** The minted page id form (`1f0000e0-0000-4000-8000-000000000001`). */
|
|
409
|
+
const mintedPageIdPattern = /^1f0000e0-0000-4000-8000-(\d{12})$/
|
|
410
|
+
|
|
411
|
+
/** Integrity problems a decoded seed can still have (duplicates, dangling references). */
|
|
412
|
+
const seedProblem = (entities: ProfileEntities): string | undefined => {
|
|
413
|
+
const pageIds = entities.pages.map(page => page.id)
|
|
414
|
+
const knownPageIds = [...pageIds, ...entities.impliedPages.map(page => page.id)]
|
|
415
|
+
const databaseIds = entities.databases.map(database => database.id)
|
|
416
|
+
const dataSourceIds = entities.dataSources.map(source => source.id)
|
|
417
|
+
|
|
418
|
+
const duplicates: ReadonlyArray<readonly [string, string | undefined]> = [
|
|
419
|
+
['object id', duplicate([...knownPageIds, ...databaseIds, ...dataSourceIds])],
|
|
420
|
+
['block id', duplicate(entities.blocks.map(block => block.id))],
|
|
421
|
+
[
|
|
422
|
+
'property item list',
|
|
423
|
+
duplicate(entities.propertyItems.map(items => `${items.pageId} ${items.propertyId}`))
|
|
424
|
+
]
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
for (const [label, value] of duplicates) {
|
|
428
|
+
if (value !== undefined) return `duplicate ${label} ${value}`
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (const page of [...entities.pages, ...entities.impliedPages]) {
|
|
432
|
+
const parent = page.parent
|
|
433
|
+
|
|
434
|
+
if (parent?.type === 'page_id' && !knownPageIds.includes(parent.page_id)) {
|
|
435
|
+
return `page ${page.id} references missing parent page ${parent.page_id}`
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
parent?.type === 'data_source_id' &&
|
|
440
|
+
!entities.dataSources.some(
|
|
441
|
+
source => source.id === parent.data_source_id && source.databaseId === parent.database_id
|
|
442
|
+
)
|
|
443
|
+
) {
|
|
444
|
+
return `page ${page.id} references missing data source ${parent.data_source_id}`
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const block = entities.blocks.find(candidate => !knownPageIds.includes(candidate.pageId))
|
|
449
|
+
|
|
450
|
+
if (block !== undefined) return `block ${block.id} references missing page ${block.pageId}`
|
|
451
|
+
|
|
452
|
+
const items = entities.propertyItems.find(candidate => !pageIds.includes(candidate.pageId))
|
|
453
|
+
|
|
454
|
+
if (items !== undefined) return `property items reference missing page ${items.pageId}`
|
|
455
|
+
|
|
456
|
+
const database = entities.databases.find(
|
|
457
|
+
candidate => !knownPageIds.includes(candidate.parent.page_id)
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if (database !== undefined) {
|
|
461
|
+
return `database ${database.id} references missing parent page ${database.parent.page_id}`
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const source = entities.dataSources.find(candidate => !databaseIds.includes(candidate.databaseId))
|
|
465
|
+
|
|
466
|
+
return source === undefined
|
|
467
|
+
? undefined
|
|
468
|
+
: `data source ${source.id} references missing database ${source.databaseId}`
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Build the emulator state for a decoded seed; a string is an integrity problem. */
|
|
472
|
+
const stateFromSeed = (seed: NotionEmulatorSeed): NotionEmulatorState | string => {
|
|
473
|
+
const profile = profileEntities(seed.profile ?? 'default')
|
|
474
|
+
|
|
475
|
+
const entities: ProfileEntities = {
|
|
476
|
+
botUser: seed.botUser ?? profile.botUser,
|
|
477
|
+
pages: seed.pages ?? profile.pages,
|
|
478
|
+
impliedPages: seed.impliedPages ?? profile.impliedPages,
|
|
479
|
+
blocks: seed.blocks ?? profile.blocks,
|
|
480
|
+
propertyItems: seed.propertyItems ?? profile.propertyItems,
|
|
481
|
+
databases: seed.databases ?? profile.databases,
|
|
482
|
+
dataSources: seed.dataSources ?? profile.dataSources
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const problem = seedProblem(entities)
|
|
486
|
+
|
|
487
|
+
// Minted ids start above every seeded id in the minted form, so none repeats.
|
|
488
|
+
const highest = Math.max(
|
|
489
|
+
0,
|
|
490
|
+
...[...entities.pages, ...entities.impliedPages].map(page =>
|
|
491
|
+
Number(mintedPageIdPattern.exec(page.id)?.[1] ?? 0)
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
return problem ?? { ...entities, counters: { nextPageNumber: highest + 1 } }
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Decode and build a seed; a string is the reason it is invalid. */
|
|
499
|
+
export const buildSeedState = (input: unknown): NotionEmulatorState | string => {
|
|
500
|
+
const decoded = decodeSeedInput(input)
|
|
501
|
+
|
|
502
|
+
return Result.isFailure(decoded)
|
|
503
|
+
? issueMessage(decoded.failure.issue)
|
|
504
|
+
: stateFromSeed(decoded.success)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Decode a full state (a restored snapshot); a string is the reason it is invalid. */
|
|
508
|
+
export const decodeState = (input: unknown): NotionEmulatorState | string => {
|
|
509
|
+
const decoded = decodeStateInput(input)
|
|
510
|
+
|
|
511
|
+
return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }
|
|
512
|
+
}
|