@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,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
|
+
}
|