@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.
Files changed (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2135 -0
  3. package/dist/anthropic.d.mts +269 -0
  4. package/dist/anthropic.d.mts.map +1 -0
  5. package/dist/anthropic.mjs +177 -0
  6. package/dist/anthropic.mjs.map +1 -0
  7. package/dist/chat-completions.d.mts +288 -0
  8. package/dist/chat-completions.d.mts.map +1 -0
  9. package/dist/chat-completions.mjs +451 -0
  10. package/dist/chat-completions.mjs.map +1 -0
  11. package/dist/codex.d.mts +265 -0
  12. package/dist/codex.d.mts.map +1 -0
  13. package/dist/codex.mjs +199 -0
  14. package/dist/codex.mjs.map +1 -0
  15. package/dist/dropbox/api.d.mts +63 -0
  16. package/dist/dropbox/api.d.mts.map +1 -0
  17. package/dist/dropbox/api.mjs +565 -0
  18. package/dist/dropbox/api.mjs.map +1 -0
  19. package/dist/dropbox/state.d.mts +136 -0
  20. package/dist/dropbox/state.d.mts.map +1 -0
  21. package/dist/dropbox/state.mjs +209 -0
  22. package/dist/dropbox/state.mjs.map +1 -0
  23. package/dist/dropbox.d.mts +79 -0
  24. package/dist/dropbox.d.mts.map +1 -0
  25. package/dist/dropbox.mjs +124 -0
  26. package/dist/dropbox.mjs.map +1 -0
  27. package/dist/email-fixtures.d.mts +36 -0
  28. package/dist/email-fixtures.d.mts.map +1 -0
  29. package/dist/email-fixtures.mjs +1080 -0
  30. package/dist/email-fixtures.mjs.map +1 -0
  31. package/dist/email.d.mts +160 -0
  32. package/dist/email.d.mts.map +1 -0
  33. package/dist/email.mjs +608 -0
  34. package/dist/email.mjs.map +1 -0
  35. package/dist/emulator-compose.d.mts +40 -0
  36. package/dist/emulator-compose.d.mts.map +1 -0
  37. package/dist/emulator-compose.mjs +91 -0
  38. package/dist/emulator-compose.mjs.map +1 -0
  39. package/dist/emulator-http.d.mts +53 -0
  40. package/dist/emulator-http.d.mts.map +1 -0
  41. package/dist/emulator-http.mjs +117 -0
  42. package/dist/emulator-http.mjs.map +1 -0
  43. package/dist/emulator-kernel.d.mts +176 -0
  44. package/dist/emulator-kernel.d.mts.map +1 -0
  45. package/dist/emulator-kernel.mjs +413 -0
  46. package/dist/emulator-kernel.mjs.map +1 -0
  47. package/dist/fixture-route.d.mts +129 -0
  48. package/dist/fixture-route.d.mts.map +1 -0
  49. package/dist/fixture-route.mjs +257 -0
  50. package/dist/fixture-route.mjs.map +1 -0
  51. package/dist/fortnox/api.d.mts +92 -0
  52. package/dist/fortnox/api.d.mts.map +1 -0
  53. package/dist/fortnox/api.mjs +750 -0
  54. package/dist/fortnox/api.mjs.map +1 -0
  55. package/dist/fortnox/state.d.mts +533 -0
  56. package/dist/fortnox/state.d.mts.map +1 -0
  57. package/dist/fortnox/state.mjs +612 -0
  58. package/dist/fortnox/state.mjs.map +1 -0
  59. package/dist/fortnox.d.mts +128 -0
  60. package/dist/fortnox.d.mts.map +1 -0
  61. package/dist/fortnox.mjs +403 -0
  62. package/dist/fortnox.mjs.map +1 -0
  63. package/dist/gateway-evaluate-recordings.d.mts +12 -0
  64. package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
  65. package/dist/gateway-evaluate-recordings.mjs +128 -0
  66. package/dist/gateway-evaluate-recordings.mjs.map +1 -0
  67. package/dist/gateway.d.mts +272 -0
  68. package/dist/gateway.d.mts.map +1 -0
  69. package/dist/gateway.mjs +400 -0
  70. package/dist/gateway.mjs.map +1 -0
  71. package/dist/github/api.d.mts +63 -0
  72. package/dist/github/api.d.mts.map +1 -0
  73. package/dist/github/api.mjs +527 -0
  74. package/dist/github/api.mjs.map +1 -0
  75. package/dist/github/state.d.mts +272 -0
  76. package/dist/github/state.d.mts.map +1 -0
  77. package/dist/github/state.mjs +303 -0
  78. package/dist/github/state.mjs.map +1 -0
  79. package/dist/github.d.mts +77 -0
  80. package/dist/github.d.mts.map +1 -0
  81. package/dist/github.mjs +180 -0
  82. package/dist/github.mjs.map +1 -0
  83. package/dist/google/calendar.d.mts +10 -0
  84. package/dist/google/calendar.d.mts.map +1 -0
  85. package/dist/google/calendar.mjs +252 -0
  86. package/dist/google/calendar.mjs.map +1 -0
  87. package/dist/google/drive.d.mts +8 -0
  88. package/dist/google/drive.d.mts.map +1 -0
  89. package/dist/google/drive.mjs +252 -0
  90. package/dist/google/drive.mjs.map +1 -0
  91. package/dist/google/gmail.d.mts +10 -0
  92. package/dist/google/gmail.d.mts.map +1 -0
  93. package/dist/google/gmail.mjs +591 -0
  94. package/dist/google/gmail.mjs.map +1 -0
  95. package/dist/google/shared.d.mts +107 -0
  96. package/dist/google/shared.d.mts.map +1 -0
  97. package/dist/google/shared.mjs +146 -0
  98. package/dist/google/shared.mjs.map +1 -0
  99. package/dist/google/state.d.mts +385 -0
  100. package/dist/google/state.d.mts.map +1 -0
  101. package/dist/google/state.mjs +496 -0
  102. package/dist/google/state.mjs.map +1 -0
  103. package/dist/google.d.mts +75 -0
  104. package/dist/google.d.mts.map +1 -0
  105. package/dist/google.mjs +210 -0
  106. package/dist/google.mjs.map +1 -0
  107. package/dist/linkedin-search/api.d.mts +45 -0
  108. package/dist/linkedin-search/api.d.mts.map +1 -0
  109. package/dist/linkedin-search/api.mjs +181 -0
  110. package/dist/linkedin-search/api.mjs.map +1 -0
  111. package/dist/linkedin-search/state.d.mts +123 -0
  112. package/dist/linkedin-search/state.d.mts.map +1 -0
  113. package/dist/linkedin-search/state.mjs +261 -0
  114. package/dist/linkedin-search/state.mjs.map +1 -0
  115. package/dist/linkedin-search.d.mts +69 -0
  116. package/dist/linkedin-search.d.mts.map +1 -0
  117. package/dist/linkedin-search.mjs +158 -0
  118. package/dist/linkedin-search.mjs.map +1 -0
  119. package/dist/mcp/api.d.mts +44 -0
  120. package/dist/mcp/api.d.mts.map +1 -0
  121. package/dist/mcp/api.mjs +557 -0
  122. package/dist/mcp/api.mjs.map +1 -0
  123. package/dist/mcp/recordings.d.mts +40 -0
  124. package/dist/mcp/recordings.d.mts.map +1 -0
  125. package/dist/mcp/recordings.mjs +2390 -0
  126. package/dist/mcp/recordings.mjs.map +1 -0
  127. package/dist/mcp/state.d.mts +71 -0
  128. package/dist/mcp/state.d.mts.map +1 -0
  129. package/dist/mcp/state.mjs +83 -0
  130. package/dist/mcp/state.mjs.map +1 -0
  131. package/dist/mcp.d.mts +84 -0
  132. package/dist/mcp.d.mts.map +1 -0
  133. package/dist/mcp.mjs +241 -0
  134. package/dist/mcp.mjs.map +1 -0
  135. package/dist/messages.d.mts +183 -0
  136. package/dist/messages.d.mts.map +1 -0
  137. package/dist/messages.mjs +532 -0
  138. package/dist/messages.mjs.map +1 -0
  139. package/dist/microsoft/api.d.mts +47 -0
  140. package/dist/microsoft/api.d.mts.map +1 -0
  141. package/dist/microsoft/api.mjs +178 -0
  142. package/dist/microsoft/api.mjs.map +1 -0
  143. package/dist/microsoft/calendar.d.mts +30 -0
  144. package/dist/microsoft/calendar.d.mts.map +1 -0
  145. package/dist/microsoft/calendar.mjs +271 -0
  146. package/dist/microsoft/calendar.mjs.map +1 -0
  147. package/dist/microsoft/drive.d.mts +36 -0
  148. package/dist/microsoft/drive.d.mts.map +1 -0
  149. package/dist/microsoft/drive.mjs +298 -0
  150. package/dist/microsoft/drive.mjs.map +1 -0
  151. package/dist/microsoft/graph.d.mts +198 -0
  152. package/dist/microsoft/graph.d.mts.map +1 -0
  153. package/dist/microsoft/graph.mjs +264 -0
  154. package/dist/microsoft/graph.mjs.map +1 -0
  155. package/dist/microsoft/mail.d.mts +48 -0
  156. package/dist/microsoft/mail.d.mts.map +1 -0
  157. package/dist/microsoft/mail.mjs +488 -0
  158. package/dist/microsoft/mail.mjs.map +1 -0
  159. package/dist/microsoft/state.d.mts +480 -0
  160. package/dist/microsoft/state.d.mts.map +1 -0
  161. package/dist/microsoft/state.mjs +625 -0
  162. package/dist/microsoft/state.mjs.map +1 -0
  163. package/dist/microsoft.d.mts +168 -0
  164. package/dist/microsoft.d.mts.map +1 -0
  165. package/dist/microsoft.mjs +483 -0
  166. package/dist/microsoft.mjs.map +1 -0
  167. package/dist/node.d.mts +47 -0
  168. package/dist/node.d.mts.map +1 -0
  169. package/dist/node.mjs +178 -0
  170. package/dist/node.mjs.map +1 -0
  171. package/dist/notion/api.d.mts +51 -0
  172. package/dist/notion/api.d.mts.map +1 -0
  173. package/dist/notion/api.mjs +507 -0
  174. package/dist/notion/api.mjs.map +1 -0
  175. package/dist/notion/state.d.mts +329 -0
  176. package/dist/notion/state.d.mts.map +1 -0
  177. package/dist/notion/state.mjs +432 -0
  178. package/dist/notion/state.mjs.map +1 -0
  179. package/dist/notion.d.mts +72 -0
  180. package/dist/notion.d.mts.map +1 -0
  181. package/dist/notion.mjs +127 -0
  182. package/dist/notion.mjs.map +1 -0
  183. package/dist/openai.d.mts +165 -0
  184. package/dist/openai.d.mts.map +1 -0
  185. package/dist/openai.mjs +141 -0
  186. package/dist/openai.mjs.map +1 -0
  187. package/dist/opencode-recordings.d.mts +7 -0
  188. package/dist/opencode-recordings.d.mts.map +1 -0
  189. package/dist/opencode-recordings.mjs +222 -0
  190. package/dist/opencode-recordings.mjs.map +1 -0
  191. package/dist/opencode.d.mts +70 -0
  192. package/dist/opencode.d.mts.map +1 -0
  193. package/dist/opencode.mjs +217 -0
  194. package/dist/opencode.mjs.map +1 -0
  195. package/dist/r2-fixtures.d.mts +36 -0
  196. package/dist/r2-fixtures.d.mts.map +1 -0
  197. package/dist/r2-fixtures.mjs +245 -0
  198. package/dist/r2-fixtures.mjs.map +1 -0
  199. package/dist/r2-guard.d.mts +43 -0
  200. package/dist/r2-guard.d.mts.map +1 -0
  201. package/dist/r2-guard.mjs +215 -0
  202. package/dist/r2-guard.mjs.map +1 -0
  203. package/dist/r2.d.mts +166 -0
  204. package/dist/r2.d.mts.map +1 -0
  205. package/dist/r2.mjs +517 -0
  206. package/dist/r2.mjs.map +1 -0
  207. package/dist/responses.d.mts +231 -0
  208. package/dist/responses.d.mts.map +1 -0
  209. package/dist/responses.mjs +556 -0
  210. package/dist/responses.mjs.map +1 -0
  211. package/dist/route-evidence.d.mts +45 -0
  212. package/dist/route-evidence.d.mts.map +1 -0
  213. package/dist/route-evidence.mjs +56 -0
  214. package/dist/route-evidence.mjs.map +1 -0
  215. package/dist/router.d.mts +93 -0
  216. package/dist/router.d.mts.map +1 -0
  217. package/dist/router.mjs +259 -0
  218. package/dist/router.mjs.map +1 -0
  219. package/dist/stateful-core.d.mts +17 -0
  220. package/dist/stateful-core.d.mts.map +1 -0
  221. package/dist/stateful-core.mjs +41 -0
  222. package/dist/stateful-core.mjs.map +1 -0
  223. package/dist/stateful-emulator.d.mts +565 -0
  224. package/dist/stateful-emulator.d.mts.map +1 -0
  225. package/dist/stateful-emulator.mjs +1228 -0
  226. package/dist/stateful-emulator.mjs.map +1 -0
  227. package/dist/stateful-secrets.d.mts +84 -0
  228. package/dist/stateful-secrets.d.mts.map +1 -0
  229. package/dist/stateful-secrets.mjs +216 -0
  230. package/dist/stateful-secrets.mjs.map +1 -0
  231. package/dist/subscription-usage-recordings.d.mts +9 -0
  232. package/dist/subscription-usage-recordings.d.mts.map +1 -0
  233. package/dist/subscription-usage-recordings.mjs +53 -0
  234. package/dist/subscription-usage-recordings.mjs.map +1 -0
  235. package/dist/subscription-usage.d.mts +53 -0
  236. package/dist/subscription-usage.d.mts.map +1 -0
  237. package/dist/subscription-usage.mjs +21 -0
  238. package/dist/subscription-usage.mjs.map +1 -0
  239. package/dist/telegram/api.d.mts +34 -0
  240. package/dist/telegram/api.d.mts.map +1 -0
  241. package/dist/telegram/api.mjs +257 -0
  242. package/dist/telegram/api.mjs.map +1 -0
  243. package/dist/telegram/state.d.mts +128 -0
  244. package/dist/telegram/state.d.mts.map +1 -0
  245. package/dist/telegram/state.mjs +154 -0
  246. package/dist/telegram/state.mjs.map +1 -0
  247. package/dist/telegram.d.mts +62 -0
  248. package/dist/telegram.d.mts.map +1 -0
  249. package/dist/telegram.mjs +127 -0
  250. package/dist/telegram.mjs.map +1 -0
  251. package/dist/todoist/api.d.mts +55 -0
  252. package/dist/todoist/api.d.mts.map +1 -0
  253. package/dist/todoist/api.mjs +415 -0
  254. package/dist/todoist/api.mjs.map +1 -0
  255. package/dist/todoist/state.d.mts +275 -0
  256. package/dist/todoist/state.d.mts.map +1 -0
  257. package/dist/todoist/state.mjs +345 -0
  258. package/dist/todoist/state.mjs.map +1 -0
  259. package/dist/todoist.d.mts +68 -0
  260. package/dist/todoist.d.mts.map +1 -0
  261. package/dist/todoist.mjs +181 -0
  262. package/dist/todoist.mjs.map +1 -0
  263. package/dist/xai.d.mts +267 -0
  264. package/dist/xai.d.mts.map +1 -0
  265. package/dist/xai.mjs +252 -0
  266. package/dist/xai.mjs.map +1 -0
  267. package/package.json +157 -0
  268. package/src/anthropic.ts +289 -0
  269. package/src/chat-completions.ts +924 -0
  270. package/src/codex.ts +296 -0
  271. package/src/dropbox/api.ts +1084 -0
  272. package/src/dropbox/state.ts +364 -0
  273. package/src/dropbox.ts +203 -0
  274. package/src/email-fixtures.ts +1297 -0
  275. package/src/email.ts +1108 -0
  276. package/src/emulator-compose.ts +147 -0
  277. package/src/emulator-http.ts +184 -0
  278. package/src/emulator-kernel.ts +844 -0
  279. package/src/fixture-route.ts +561 -0
  280. package/src/fortnox/api.ts +1352 -0
  281. package/src/fortnox/state.ts +798 -0
  282. package/src/fortnox.ts +801 -0
  283. package/src/gateway-evaluate-recordings.ts +153 -0
  284. package/src/gateway.ts +530 -0
  285. package/src/github/api.ts +986 -0
  286. package/src/github/state.ts +439 -0
  287. package/src/github.ts +271 -0
  288. package/src/google/calendar.ts +486 -0
  289. package/src/google/drive.ts +471 -0
  290. package/src/google/gmail.ts +1011 -0
  291. package/src/google/shared.ts +321 -0
  292. package/src/google/state.ts +686 -0
  293. package/src/google.ts +298 -0
  294. package/src/linkedin-search/api.ts +363 -0
  295. package/src/linkedin-search/state.ts +373 -0
  296. package/src/linkedin-search.ts +241 -0
  297. package/src/mcp/api.ts +995 -0
  298. package/src/mcp/recordings.ts +2665 -0
  299. package/src/mcp/state.ts +125 -0
  300. package/src/mcp.ts +319 -0
  301. package/src/messages.ts +844 -0
  302. package/src/microsoft/api.ts +426 -0
  303. package/src/microsoft/calendar.ts +491 -0
  304. package/src/microsoft/drive.ts +541 -0
  305. package/src/microsoft/graph.ts +550 -0
  306. package/src/microsoft/mail.ts +833 -0
  307. package/src/microsoft/state.ts +822 -0
  308. package/src/microsoft.ts +982 -0
  309. package/src/node.ts +293 -0
  310. package/src/notion/api.ts +976 -0
  311. package/src/notion/state.ts +512 -0
  312. package/src/notion.ts +210 -0
  313. package/src/openai.ts +198 -0
  314. package/src/opencode-recordings.ts +257 -0
  315. package/src/opencode.ts +271 -0
  316. package/src/r2-fixtures.ts +295 -0
  317. package/src/r2-guard.ts +323 -0
  318. package/src/r2.ts +890 -0
  319. package/src/responses.ts +901 -0
  320. package/src/route-evidence.ts +90 -0
  321. package/src/router.ts +490 -0
  322. package/src/stateful-core.ts +70 -0
  323. package/src/stateful-emulator.ts +2571 -0
  324. package/src/stateful-secrets.ts +299 -0
  325. package/src/subscription-usage-recordings.ts +77 -0
  326. package/src/subscription-usage.ts +68 -0
  327. package/src/telegram/api.ts +437 -0
  328. package/src/telegram/state.ts +229 -0
  329. package/src/telegram.ts +227 -0
  330. package/src/todoist/api.ts +736 -0
  331. package/src/todoist/state.ts +456 -0
  332. package/src/todoist.ts +316 -0
  333. 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
+ }