@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,456 @@
1
+ /**
2
+ * Todoist emulator state: the typed entities, the seed input, the default seed, and the profiles
3
+ * (internal; re-exported by `src/todoist.ts`).
4
+ *
5
+ * Entity shapes and the default entities are copied as data from the synthetic Todoist
6
+ * conformance fixtures (the same user, project, task, and label ids as
7
+ * `todoistConformanceFixtureSeeds`), never imported from SDK code. Where no fixture records a
8
+ * value, the default is synthesized and says so: the work and paging projects themselves (the
9
+ * fixtures name only their ids) are the fixture project shape with synthetic names and
10
+ * `parent_id: null`.
11
+ *
12
+ * @experimental
13
+ */
14
+ import { Result } from 'effect'
15
+ import * as Schema from 'effect/Schema'
16
+
17
+ /** A task due as the fixtures answer it. */
18
+ export const TodoistEmulatorDue = Schema.Struct({
19
+ date: Schema.String,
20
+ timezone: Schema.NullOr(Schema.String),
21
+ string: Schema.String,
22
+ lang: Schema.String,
23
+ is_recurring: Schema.Boolean
24
+ })
25
+
26
+ export type TodoistEmulatorDue = typeof TodoistEmulatorDue.Type
27
+
28
+ /** A stored project, in the fixtures' API v1 wire shape. Deleted projects are removed. */
29
+ export const TodoistEmulatorProject = Schema.Struct({
30
+ id: Schema.String,
31
+ name: Schema.String,
32
+ parent_id: Schema.NullOr(Schema.String),
33
+ child_order: Schema.Int,
34
+ color: Schema.String,
35
+ description: Schema.String,
36
+ is_archived: Schema.Boolean,
37
+ is_deleted: Schema.Boolean,
38
+ is_favorite: Schema.Boolean,
39
+ is_frozen: Schema.Boolean,
40
+ is_shared: Schema.Boolean,
41
+ is_collapsed: Schema.Boolean,
42
+ can_assign_tasks: Schema.Boolean,
43
+ inbox_project: Schema.Boolean,
44
+ view_style: Schema.String,
45
+ default_order: Schema.Int,
46
+ created_at: Schema.String,
47
+ updated_at: Schema.String
48
+ })
49
+
50
+ export type TodoistEmulatorProject = typeof TodoistEmulatorProject.Type
51
+
52
+ /** A stored task, in the fixtures' API v1 wire shape. */
53
+ export const TodoistEmulatorTask = Schema.Struct({
54
+ id: Schema.String,
55
+ user_id: Schema.String,
56
+ project_id: Schema.String,
57
+ section_id: Schema.Null,
58
+ parent_id: Schema.Null,
59
+ added_by_uid: Schema.String,
60
+ assigned_by_uid: Schema.Null,
61
+ responsible_uid: Schema.Null,
62
+ /** Label NAMES, as the fixtures answer them. */
63
+ labels: Schema.Array(Schema.String),
64
+ deadline: Schema.Null,
65
+ duration: Schema.Null,
66
+ /** `true` once closed: a closed task is no longer active. */
67
+ checked: Schema.Boolean,
68
+ is_deleted: Schema.Boolean,
69
+ added_at: Schema.String,
70
+ completed_at: Schema.NullOr(Schema.String),
71
+ updated_at: Schema.String,
72
+ due: Schema.NullOr(TodoistEmulatorDue),
73
+ priority: Schema.Int,
74
+ child_order: Schema.Int,
75
+ content: Schema.String,
76
+ description: Schema.String,
77
+ note_count: Schema.Int,
78
+ day_order: Schema.Int,
79
+ is_collapsed: Schema.Boolean
80
+ })
81
+
82
+ export type TodoistEmulatorTask = typeof TodoistEmulatorTask.Type
83
+
84
+ /** A personal label, in the fixtures' wire shape. */
85
+ export const TodoistEmulatorLabel = Schema.Struct({
86
+ id: Schema.String,
87
+ name: Schema.String,
88
+ color: Schema.String,
89
+ order: Schema.Int,
90
+ is_favorite: Schema.Boolean
91
+ })
92
+
93
+ export type TodoistEmulatorLabel = typeof TodoistEmulatorLabel.Type
94
+
95
+ const Counter = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))
96
+
97
+ const Counters = Schema.Struct({
98
+ /** Next number in created project ids (`6XEmuProject0001`). */
99
+ nextProjectNumber: Counter,
100
+ /** Next number in created task ids (`6XEmuTask0000001`). */
101
+ nextTaskNumber: Counter,
102
+ /** Next `error_extra.event_id` number of a 404 answer. */
103
+ nextEventNumber: Counter
104
+ })
105
+
106
+ /** The whole emulator state (JSON-compatible; what `snapshot()` returns). */
107
+ export const TodoistEmulatorStateSchema = Schema.Struct({
108
+ userId: Schema.String,
109
+ projects: Schema.Array(TodoistEmulatorProject),
110
+ tasks: Schema.Array(TodoistEmulatorTask),
111
+ labels: Schema.Array(TodoistEmulatorLabel),
112
+ counters: Counters
113
+ })
114
+
115
+ /**
116
+ * The emulator state. The container is mutable (route handlers replace whole entity lists);
117
+ * every entity is replaced, never edited in place.
118
+ */
119
+ export type TodoistEmulatorState = {
120
+ userId: string
121
+ projects: ReadonlyArray<TodoistEmulatorProject>
122
+ tasks: ReadonlyArray<TodoistEmulatorTask>
123
+ labels: ReadonlyArray<TodoistEmulatorLabel>
124
+ counters: typeof Counters.Type
125
+ }
126
+
127
+ /** A seeded project: `id` and `name` are required; the rest default to the fixture values. */
128
+ export const TodoistEmulatorProjectSeed = Schema.Struct({
129
+ id: Schema.String,
130
+ name: Schema.String,
131
+ parent_id: Schema.optionalKey(Schema.NullOr(Schema.String)),
132
+ child_order: Schema.optionalKey(Schema.Int),
133
+ created_at: Schema.optionalKey(Schema.String),
134
+ updated_at: Schema.optionalKey(Schema.String)
135
+ })
136
+
137
+ export type TodoistEmulatorProjectSeed = typeof TodoistEmulatorProjectSeed.Type
138
+
139
+ /** A seeded active task: `id`, `project_id`, and `content` are required. */
140
+ export const TodoistEmulatorTaskSeed = Schema.Struct({
141
+ id: Schema.String,
142
+ project_id: Schema.String,
143
+ content: Schema.String,
144
+ labels: Schema.optionalKey(Schema.Array(Schema.String)),
145
+ child_order: Schema.optionalKey(Schema.Int),
146
+ added_at: Schema.optionalKey(Schema.String),
147
+ updated_at: Schema.optionalKey(Schema.String),
148
+ due: Schema.optionalKey(Schema.NullOr(TodoistEmulatorDue))
149
+ })
150
+
151
+ export type TodoistEmulatorTaskSeed = typeof TodoistEmulatorTaskSeed.Type
152
+
153
+ /** Account-variance profiles for the default seed. */
154
+ export const TodoistEmulatorProfile = Schema.Literals(['default', 'empty'])
155
+
156
+ export type TodoistEmulatorProfile = typeof TodoistEmulatorProfile.Type
157
+
158
+ /**
159
+ * A typed seed. Start from `profile` (default `'default'`, the fixture entities; `'empty'` keeps
160
+ * only the work project); every other key, when given, replaces that part of the profile.
161
+ */
162
+ export const TodoistEmulatorSeed = Schema.Struct({
163
+ profile: Schema.optionalKey(TodoistEmulatorProfile),
164
+ userId: Schema.optionalKey(Schema.String),
165
+ projects: Schema.optionalKey(Schema.Array(TodoistEmulatorProjectSeed)),
166
+ tasks: Schema.optionalKey(Schema.Array(TodoistEmulatorTaskSeed)),
167
+ labels: Schema.optionalKey(Schema.Array(TodoistEmulatorLabel))
168
+ })
169
+
170
+ export type TodoistEmulatorSeed = typeof TodoistEmulatorSeed.Type
171
+
172
+ const strict = { onExcessProperty: 'error' } as const
173
+
174
+ const decodeSeedInput = Schema.decodeUnknownResult(TodoistEmulatorSeed, strict)
175
+
176
+ const decodeStateInput = Schema.decodeUnknownResult(TodoistEmulatorStateSchema, strict)
177
+
178
+ const issueMessage = (issue: Schema.SchemaError['issue']): string =>
179
+ new Schema.SchemaError(issue).message
180
+
181
+ /** Ids are opaque strings in the characters the conformance seeds accept. */
182
+ export const todoistIdPattern = /^[A-Za-z0-9_-]{1,64}$/
183
+
184
+ /** Prefix of every id the emulator mints; seeds may not use it, so ids never collide. */
185
+ export const todoistReservedIdPrefix = '6XEmu'
186
+
187
+ /** Prefix of project ids the emulator mints (`6XEmuProject0001`). */
188
+ export const todoistMintedProjectPrefix = `${todoistReservedIdPrefix}Project`
189
+
190
+ /** Prefix of task ids the emulator mints (`6XEmuTask0000001`). */
191
+ export const todoistMintedTaskPrefix = `${todoistReservedIdPrefix}Task`
192
+
193
+ /** True for an id minted with `prefix`, that is, created through the recorded create flow. */
194
+ export const isTodoistMintedId = (id: string, prefix: string): boolean => id.startsWith(prefix)
195
+
196
+ // Default entities, copied from the fixtures.
197
+
198
+ const userId = '10000001'
199
+
200
+ const workProjectId = '6XSyntheticWork0'
201
+
202
+ /**
203
+ * The paging project the paging fixture lists with `limit=2` (the only seeded project whose task
204
+ * listing a fixture records).
205
+ */
206
+ export const todoistPagingProjectId = '6XSyntheticPage0'
207
+
208
+ const pagingProjectId = todoistPagingProjectId
209
+
210
+ /** Fixture timestamp of the seeded tasks (`tasks-paging.ts`, `task-labels.ts`). */
211
+ const seededAt = '2026-09-20T10:00:00.000000Z'
212
+
213
+ const defaultProjects: ReadonlyArray<TodoistEmulatorProjectSeed> = [
214
+ // Synthesized: the fixtures name only the ids of these two projects.
215
+ { id: workProjectId, name: 'Synthetic work project', child_order: 1 },
216
+ { id: pagingProjectId, name: 'Synthetic paging project', child_order: 2 }
217
+ ]
218
+
219
+ const pagingTask = (id: string, content: string, order: number): TodoistEmulatorTaskSeed => ({
220
+ id,
221
+ project_id: pagingProjectId,
222
+ content,
223
+ child_order: order
224
+ })
225
+
226
+ const defaultTasks: ReadonlyArray<TodoistEmulatorTaskSeed> = [
227
+ pagingTask('6XSynPagingTask1', 'Synthetic paging task one', 1),
228
+ pagingTask('6XSynPagingTask2', 'Synthetic paging task two', 2),
229
+ pagingTask('6XSynPagingTask3', 'Synthetic paging task three', 3),
230
+ {
231
+ id: '6XSyntheticLabel',
232
+ project_id: workProjectId,
233
+ content: 'Synthetic labeled task',
234
+ labels: ['synthetic-errand', 'synthetic-waiting'],
235
+ child_order: 1
236
+ }
237
+ ]
238
+
239
+ const label = (id: string, name: string, order: number): TodoistEmulatorLabel => ({
240
+ id,
241
+ name,
242
+ color: 'charcoal',
243
+ order,
244
+ is_favorite: false
245
+ })
246
+
247
+ const defaultLabels: ReadonlyArray<TodoistEmulatorLabel> = [
248
+ label('2100000001', 'synthetic-errand', 1),
249
+ label('2100000002', 'synthetic-waiting', 2),
250
+ label('2100000003', 'synthetic-someday', 3)
251
+ ]
252
+
253
+ type ProfileEntities = {
254
+ readonly userId: string
255
+ readonly projects: ReadonlyArray<TodoistEmulatorProjectSeed>
256
+ readonly tasks: ReadonlyArray<TodoistEmulatorTaskSeed>
257
+ readonly labels: ReadonlyArray<TodoistEmulatorLabel>
258
+ }
259
+
260
+ const profileEntities = (profile: TodoistEmulatorProfile): ProfileEntities => {
261
+ switch (profile) {
262
+ case 'default':
263
+ return { userId, projects: defaultProjects, tasks: defaultTasks, labels: defaultLabels }
264
+ case 'empty':
265
+ return { userId, projects: defaultProjects.slice(0, 1), tasks: [], labels: [] }
266
+ }
267
+ }
268
+
269
+ /** A project in wire key order (the fixtures' order and default values). */
270
+ export const makeTodoistProject = (fields: {
271
+ readonly id: string
272
+ readonly name: string
273
+ readonly parentId: string | null
274
+ readonly childOrder: number
275
+ readonly createdAt: string
276
+ readonly updatedAt: string
277
+ }): TodoistEmulatorProject => ({
278
+ id: fields.id,
279
+ name: fields.name,
280
+ parent_id: fields.parentId,
281
+ child_order: fields.childOrder,
282
+ color: 'charcoal',
283
+ description: '',
284
+ is_archived: false,
285
+ is_deleted: false,
286
+ is_favorite: false,
287
+ is_frozen: false,
288
+ is_shared: false,
289
+ is_collapsed: false,
290
+ can_assign_tasks: false,
291
+ inbox_project: false,
292
+ view_style: 'list',
293
+ default_order: 0,
294
+ created_at: fields.createdAt,
295
+ updated_at: fields.updatedAt
296
+ })
297
+
298
+ /** An active task in wire key order (the fixtures' order and default values). */
299
+ export const makeTodoistTask = (fields: {
300
+ readonly id: string
301
+ readonly userId: string
302
+ readonly projectId: string
303
+ readonly content: string
304
+ readonly labels: ReadonlyArray<string>
305
+ readonly addedAt: string
306
+ readonly updatedAt: string
307
+ readonly due: TodoistEmulatorDue | null
308
+ readonly childOrder: number
309
+ }): TodoistEmulatorTask => ({
310
+ id: fields.id,
311
+ user_id: fields.userId,
312
+ project_id: fields.projectId,
313
+ section_id: null,
314
+ parent_id: null,
315
+ added_by_uid: fields.userId,
316
+ assigned_by_uid: null,
317
+ responsible_uid: null,
318
+ labels: fields.labels,
319
+ deadline: null,
320
+ duration: null,
321
+ checked: false,
322
+ is_deleted: false,
323
+ added_at: fields.addedAt,
324
+ completed_at: null,
325
+ updated_at: fields.updatedAt,
326
+ due: fields.due,
327
+ priority: 1,
328
+ child_order: fields.childOrder,
329
+ content: fields.content,
330
+ description: '',
331
+ note_count: 0,
332
+ day_order: -1,
333
+ is_collapsed: false
334
+ })
335
+
336
+ const duplicate = (values: ReadonlyArray<string>): string | undefined =>
337
+ values.find((value, index) => values.indexOf(value) !== index)
338
+
339
+ /** Integrity problems a decoded seed can still have (ids, duplicates, dangling references). */
340
+ const seedProblem = (entities: ProfileEntities): string | undefined => {
341
+ const projectIds = entities.projects.map(project => project.id)
342
+ const taskIds = entities.tasks.map(task => task.id)
343
+ const labelIds = entities.labels.map(item => item.id)
344
+
345
+ const invalid = [...projectIds, ...taskIds, ...labelIds].find(id => !todoistIdPattern.test(id))
346
+
347
+ if (invalid !== undefined) return `id ${JSON.stringify(invalid)} is not 1-64 of [A-Za-z0-9_-]`
348
+
349
+ // Minted ids come from counters that start at 1: a seeded id in their namespace could collide.
350
+ const reserved = [...projectIds, ...taskIds, ...labelIds].find(id =>
351
+ id.startsWith(todoistReservedIdPrefix)
352
+ )
353
+
354
+ if (reserved !== undefined) {
355
+ return (
356
+ `id ${reserved} uses the prefix ${todoistReservedIdPrefix}, ` +
357
+ 'reserved for ids the emulator mints'
358
+ )
359
+ }
360
+
361
+ const duplicates: ReadonlyArray<readonly [string, string | undefined]> = [
362
+ ['project id', duplicate(projectIds)],
363
+ ['task id', duplicate(taskIds)],
364
+ ['label id', duplicate(labelIds)],
365
+ ['label name', duplicate(entities.labels.map(item => item.name))]
366
+ ]
367
+
368
+ for (const [name, value] of duplicates) {
369
+ if (value !== undefined) return `duplicate ${name} ${value}`
370
+ }
371
+
372
+ const orphan = entities.projects.find(
373
+ project =>
374
+ project.parent_id !== undefined &&
375
+ project.parent_id !== null &&
376
+ !projectIds.includes(project.parent_id)
377
+ )
378
+
379
+ if (orphan !== undefined) {
380
+ return `project ${orphan.id} references a missing parent project`
381
+ }
382
+
383
+ const task = entities.tasks.find(candidate => !projectIds.includes(candidate.project_id))
384
+
385
+ if (task !== undefined) return `task ${task.id} references missing project ${task.project_id}`
386
+
387
+ const names = entities.labels.map(item => item.name)
388
+
389
+ const unlabeled = entities.tasks.find(candidate =>
390
+ (candidate.labels ?? []).some(name => !names.includes(name))
391
+ )
392
+
393
+ return unlabeled === undefined
394
+ ? undefined
395
+ : `task ${unlabeled.id} carries a label that is not a seeded personal label`
396
+ }
397
+
398
+ const stateFromSeed = (seed: TodoistEmulatorSeed): TodoistEmulatorState | string => {
399
+ const profile = profileEntities(seed.profile ?? 'default')
400
+
401
+ const entities: ProfileEntities = {
402
+ userId: seed.userId ?? profile.userId,
403
+ projects: seed.projects ?? profile.projects,
404
+ tasks: seed.tasks ?? profile.tasks,
405
+ labels: seed.labels ?? profile.labels
406
+ }
407
+
408
+ const problem = seedProblem(entities)
409
+
410
+ if (problem !== undefined) return problem
411
+
412
+ return {
413
+ userId: entities.userId,
414
+ projects: entities.projects.map((project, index) =>
415
+ makeTodoistProject({
416
+ id: project.id,
417
+ name: project.name,
418
+ parentId: project.parent_id ?? null,
419
+ childOrder: project.child_order ?? index + 1,
420
+ createdAt: project.created_at ?? seededAt,
421
+ updatedAt: project.updated_at ?? project.created_at ?? seededAt
422
+ })
423
+ ),
424
+ tasks: entities.tasks.map((task, index) =>
425
+ makeTodoistTask({
426
+ id: task.id,
427
+ userId: entities.userId,
428
+ projectId: task.project_id,
429
+ content: task.content,
430
+ labels: task.labels ?? [],
431
+ addedAt: task.added_at ?? seededAt,
432
+ updatedAt: task.updated_at ?? task.added_at ?? seededAt,
433
+ due: task.due ?? null,
434
+ childOrder: task.child_order ?? index + 1
435
+ })
436
+ ),
437
+ labels: entities.labels,
438
+ counters: { nextProjectNumber: 1, nextTaskNumber: 1, nextEventNumber: 1 }
439
+ }
440
+ }
441
+
442
+ /** Decode and build a seed; a string is the reason it is invalid. */
443
+ export const buildTodoistSeedState = (input: unknown): TodoistEmulatorState | string => {
444
+ const decoded = decodeSeedInput(input)
445
+
446
+ return Result.isFailure(decoded)
447
+ ? issueMessage(decoded.failure.issue)
448
+ : stateFromSeed(decoded.success)
449
+ }
450
+
451
+ /** Decode a full state (a restored snapshot); a string is the reason it is invalid. */
452
+ export const decodeTodoistState = (input: unknown): TodoistEmulatorState | string => {
453
+ const decoded = decodeStateInput(input)
454
+
455
+ return Result.isFailure(decoded) ? issueMessage(decoded.failure.issue) : { ...decoded.success }
456
+ }