monday-cli 0.3.0 → 0.5.0
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/CHANGELOG.md +719 -0
- package/README.md +208 -36
- package/dist/api/assets.d.ts +326 -0
- package/dist/api/assets.d.ts.map +1 -0
- package/dist/api/assets.js +519 -0
- package/dist/api/assets.js.map +1 -0
- package/dist/api/column-types.d.ts +13 -7
- package/dist/api/column-types.d.ts.map +1 -1
- package/dist/api/column-types.js +7 -3
- package/dist/api/column-types.js.map +1 -1
- package/dist/api/column-values.d.ts +8 -1
- package/dist/api/column-values.d.ts.map +1 -1
- package/dist/api/column-values.js +16 -6
- package/dist/api/column-values.js.map +1 -1
- package/dist/api/documents.d.ts +1652 -0
- package/dist/api/documents.d.ts.map +1 -0
- package/dist/api/documents.js +2411 -0
- package/dist/api/documents.js.map +1 -0
- package/dist/api/item-watch.d.ts +263 -0
- package/dist/api/item-watch.d.ts.map +1 -0
- package/dist/api/item-watch.js +709 -0
- package/dist/api/item-watch.js.map +1 -0
- package/dist/api/multipart-transport.d.ts +223 -0
- package/dist/api/multipart-transport.d.ts.map +1 -0
- package/dist/api/multipart-transport.js +274 -0
- package/dist/api/multipart-transport.js.map +1 -0
- package/dist/api/parallel-dispatch.d.ts +155 -0
- package/dist/api/parallel-dispatch.d.ts.map +1 -0
- package/dist/api/parallel-dispatch.js +243 -0
- package/dist/api/parallel-dispatch.js.map +1 -0
- package/dist/api/partial-success-bulk.d.ts +118 -60
- package/dist/api/partial-success-bulk.d.ts.map +1 -1
- package/dist/api/partial-success-bulk.js +137 -79
- package/dist/api/partial-success-bulk.js.map +1 -1
- package/dist/api/partial-success-mutation.d.ts +13 -1
- package/dist/api/partial-success-mutation.d.ts.map +1 -1
- package/dist/api/partial-success-mutation.js +5 -1
- package/dist/api/partial-success-mutation.js.map +1 -1
- package/dist/api/raw-write.d.ts +13 -4
- package/dist/api/raw-write.d.ts.map +1 -1
- package/dist/api/raw-write.js +22 -11
- package/dist/api/raw-write.js.map +1 -1
- package/dist/api/resolve-client.d.ts +11 -0
- package/dist/api/resolve-client.d.ts.map +1 -1
- package/dist/api/resolve-client.js +9 -1
- package/dist/api/resolve-client.js.map +1 -1
- package/dist/api/teams.d.ts +657 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +880 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/cli/run.d.ts +20 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +1 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/commands/board/column-create.d.ts +6 -5
- package/dist/commands/board/column-create.d.ts.map +1 -1
- package/dist/commands/board/column-create.js +9 -6
- package/dist/commands/board/column-create.js.map +1 -1
- package/dist/commands/completion.d.ts +188 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +418 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/doc/append-markdown.d.ts +117 -0
- package/dist/commands/doc/append-markdown.d.ts.map +1 -0
- package/dist/commands/doc/append-markdown.js +253 -0
- package/dist/commands/doc/append-markdown.js.map +1 -0
- package/dist/commands/doc/block-create.d.ts +114 -0
- package/dist/commands/doc/block-create.d.ts.map +1 -0
- package/dist/commands/doc/block-create.js +206 -0
- package/dist/commands/doc/block-create.js.map +1 -0
- package/dist/commands/doc/block-delete.d.ts +72 -0
- package/dist/commands/doc/block-delete.d.ts.map +1 -0
- package/dist/commands/doc/block-delete.js +161 -0
- package/dist/commands/doc/block-delete.js.map +1 -0
- package/dist/commands/doc/block-update.d.ts +75 -0
- package/dist/commands/doc/block-update.d.ts.map +1 -0
- package/dist/commands/doc/block-update.js +162 -0
- package/dist/commands/doc/block-update.js.map +1 -0
- package/dist/commands/doc/create-in-workspace.d.ts +76 -0
- package/dist/commands/doc/create-in-workspace.d.ts.map +1 -0
- package/dist/commands/doc/create-in-workspace.js +164 -0
- package/dist/commands/doc/create-in-workspace.js.map +1 -0
- package/dist/commands/doc/create-on-column.d.ts +71 -0
- package/dist/commands/doc/create-on-column.d.ts.map +1 -0
- package/dist/commands/doc/create-on-column.js +146 -0
- package/dist/commands/doc/create-on-column.js.map +1 -0
- package/dist/commands/doc/delete.d.ts +68 -0
- package/dist/commands/doc/delete.d.ts.map +1 -0
- package/dist/commands/doc/delete.js +146 -0
- package/dist/commands/doc/delete.js.map +1 -0
- package/dist/commands/doc/duplicate.d.ts +101 -0
- package/dist/commands/doc/duplicate.d.ts.map +1 -0
- package/dist/commands/doc/duplicate.js +191 -0
- package/dist/commands/doc/duplicate.js.map +1 -0
- package/dist/commands/doc/get.d.ts +46 -0
- package/dist/commands/doc/get.d.ts.map +1 -0
- package/dist/commands/doc/get.js +95 -0
- package/dist/commands/doc/get.js.map +1 -0
- package/dist/commands/doc/import-html.d.ts +125 -0
- package/dist/commands/doc/import-html.d.ts.map +1 -0
- package/dist/commands/doc/import-html.js +273 -0
- package/dist/commands/doc/import-html.js.map +1 -0
- package/dist/commands/doc/list.d.ts +86 -0
- package/dist/commands/doc/list.d.ts.map +1 -0
- package/dist/commands/doc/list.js +217 -0
- package/dist/commands/doc/list.js.map +1 -0
- package/dist/commands/doc/rename.d.ts +60 -0
- package/dist/commands/doc/rename.d.ts.map +1 -0
- package/dist/commands/doc/rename.js +135 -0
- package/dist/commands/doc/rename.js.map +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +162 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/item/create.js +2 -2
- package/dist/commands/item/update.d.ts +1 -0
- package/dist/commands/item/update.d.ts.map +1 -1
- package/dist/commands/item/update.js +61 -0
- package/dist/commands/item/update.js.map +1 -1
- package/dist/commands/item/upload.d.ts +108 -0
- package/dist/commands/item/upload.d.ts.map +1 -0
- package/dist/commands/item/upload.js +370 -0
- package/dist/commands/item/upload.js.map +1 -0
- package/dist/commands/item/watch.d.ts +90 -0
- package/dist/commands/item/watch.d.ts.map +1 -0
- package/dist/commands/item/watch.js +342 -0
- package/dist/commands/item/watch.js.map +1 -0
- package/dist/commands/update/create.d.ts.map +1 -1
- package/dist/commands/update/create.js +6 -4
- package/dist/commands/update/create.js.map +1 -1
- package/dist/commands/update/edit.d.ts +4 -2
- package/dist/commands/update/edit.d.ts.map +1 -1
- package/dist/commands/update/edit.js +10 -6
- package/dist/commands/update/edit.js.map +1 -1
- package/dist/commands/update/reply.d.ts +4 -2
- package/dist/commands/update/reply.d.ts.map +1 -1
- package/dist/commands/update/reply.js +10 -6
- package/dist/commands/update/reply.js.map +1 -1
- package/dist/commands/update/upload.d.ts +69 -0
- package/dist/commands/update/upload.d.ts.map +1 -0
- package/dist/commands/update/upload.js +235 -0
- package/dist/commands/update/upload.js.map +1 -0
- package/dist/commands/user/_team-membership.d.ts +10 -0
- package/dist/commands/user/_team-membership.d.ts.map +1 -0
- package/dist/commands/user/_team-membership.js +88 -0
- package/dist/commands/user/_team-membership.js.map +1 -0
- package/dist/commands/user/team-add-members.d.ts +81 -0
- package/dist/commands/user/team-add-members.d.ts.map +1 -0
- package/dist/commands/user/team-add-members.js +186 -0
- package/dist/commands/user/team-add-members.js.map +1 -0
- package/dist/commands/user/team-create.d.ts +82 -0
- package/dist/commands/user/team-create.d.ts.map +1 -0
- package/dist/commands/user/team-create.js +206 -0
- package/dist/commands/user/team-create.js.map +1 -0
- package/dist/commands/user/team-delete.d.ts +56 -0
- package/dist/commands/user/team-delete.d.ts.map +1 -0
- package/dist/commands/user/team-delete.js +137 -0
- package/dist/commands/user/team-delete.js.map +1 -0
- package/dist/commands/user/team-get.d.ts +41 -0
- package/dist/commands/user/team-get.d.ts.map +1 -0
- package/dist/commands/user/team-get.js +87 -0
- package/dist/commands/user/team-get.js.map +1 -0
- package/dist/commands/user/team-list.d.ts +39 -0
- package/dist/commands/user/team-list.d.ts.map +1 -0
- package/dist/commands/user/team-list.js +90 -0
- package/dist/commands/user/team-list.js.map +1 -0
- package/dist/commands/user/team-remove-members.d.ts +71 -0
- package/dist/commands/user/team-remove-members.d.ts.map +1 -0
- package/dist/commands/user/team-remove-members.js +176 -0
- package/dist/commands/user/team-remove-members.js.map +1 -0
- package/dist/types/ids.d.ts +8 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +53 -5
- package/dist/types/ids.js.map +1 -1
- package/dist/utils/mime.d.ts +24 -0
- package/dist/utils/mime.d.ts.map +1 -0
- package/dist/utils/mime.js +64 -0
- package/dist/utils/mime.js.map +1 -0
- package/dist/utils/output/envelope.d.ts +30 -0
- package/dist/utils/output/envelope.d.ts.map +1 -1
- package/dist/utils/output/envelope.js +26 -0
- package/dist/utils/output/envelope.js.map +1 -1
- package/dist/utils/output/ndjson.d.ts +25 -0
- package/dist/utils/output/ndjson.d.ts.map +1 -1
- package/dist/utils/output/ndjson.js +12 -0
- package/dist/utils/output/ndjson.js.map +1 -1
- package/dist/utils/parse-brand-list.d.ts +95 -0
- package/dist/utils/parse-brand-list.d.ts.map +1 -0
- package/dist/utils/parse-brand-list.js +96 -0
- package/dist/utils/parse-brand-list.js.map +1 -0
- package/dist/utils/signal.d.ts +42 -0
- package/dist/utils/signal.d.ts.map +1 -0
- package/dist/utils/signal.js +45 -0
- package/dist/utils/signal.js.map +1 -0
- package/dist/utils/source-content.d.ts +93 -0
- package/dist/utils/source-content.d.ts.map +1 -0
- package/dist/utils/source-content.js +120 -0
- package/dist/utils/source-content.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,2411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workdocs read + mutation surface for the v0.4-M32 `monday doc
|
|
3
|
+
* list/get` verbs, the v0.5-M35 doc-level CRUD verbs, the v0.5-M36
|
|
4
|
+
* per-block CRUD verbs, and the v0.5-M37 doc-content import verbs
|
|
5
|
+
* (`cli-design.md` §2.7 + §4.3 + §13 v0.4/v0.5 entries;
|
|
6
|
+
* `v0.4-plan.md` §3 M32; `v0.5-plan.md` §3 M35 + §8 D7-D9;
|
|
7
|
+
* `v0.5-plan.md` §3 M36 + §8 D10-D11; `v0.5-plan.md` §3 M37 + §8
|
|
8
|
+
* D12-D13).
|
|
9
|
+
*
|
|
10
|
+
* **Wire surface (empirical probe 2026-05-14, API `2026-01`).** Two
|
|
11
|
+
* Monday GraphQL operations land here, both against `Query.docs(...)`:
|
|
12
|
+
*
|
|
13
|
+
* - **List variant** — `Query.docs(workspace_ids: [ID],
|
|
14
|
+
* order_by: DocsOrderBy, limit: Int, page: Int) → [Document]`.
|
|
15
|
+
* Page/limit pagination (NOT cursor — Monday's workdocs surface
|
|
16
|
+
* has no `items_page`-style cursor). Default `limit: 25` on the
|
|
17
|
+
* wire side; CLI caps `--limit` at `MAX_DOC_LIST_LIMIT = 100`
|
|
18
|
+
* to keep response sizes bounded (each Document is rich-text
|
|
19
|
+
* plus metadata, and a `--limit 500` request could blow past
|
|
20
|
+
* Monday's complexity budget on doc-heavy accounts). `--page`
|
|
21
|
+
* is 1-based.
|
|
22
|
+
* - **Get variant** — `Query.docs(ids: [ID!]) → [Document]` with
|
|
23
|
+
* the per-doc `blocks` selection hydrated. Returns at most one
|
|
24
|
+
* Document (single-id list). The CLI extracts the singleton
|
|
25
|
+
* index 0; an empty array surfaces `not_found` (D8 — Monday's
|
|
26
|
+
* wire collapses doesn't-exist + not-accessible into the same
|
|
27
|
+
* shape), while a null `docs` root surfaces `internal_error`
|
|
28
|
+
* with a drift hint (Monday's documented shape is `[Document]`,
|
|
29
|
+
* possibly empty, never null — null indicates wire-shape
|
|
30
|
+
* regression worth surfacing loudly). The
|
|
31
|
+
* `Document.blocks: [DocumentBlock]` selection adds significant
|
|
32
|
+
* payload, which is why `doc list` ships WITHOUT `blocks` and
|
|
33
|
+
* `doc get` is the per-doc body-hydrating path.
|
|
34
|
+
*
|
|
35
|
+
* **`Document` object — 14 fields.** Per the M32 empirical probe:
|
|
36
|
+
* `id` (ID!), `object_id` (ID!), `blocks` ([DocumentBlock]; null
|
|
37
|
+
* unless hydrated), `created_at` (Date, nullable), `created_by`
|
|
38
|
+
* (User, nullable — projected to the slim `{id, name}` shape for
|
|
39
|
+
* envelope compactness), `doc_folder_id` (ID, nullable),
|
|
40
|
+
* `doc_kind` (BoardKind!, returning `'public'`/`'private'`/
|
|
41
|
+
* `'share'` per the DocKind probe at API `2026-01` — non-null on
|
|
42
|
+
* the wire; the standalone `DocKind` enum exists but isn't
|
|
43
|
+
* returned by `Document.doc_kind`), `name` (String!),
|
|
44
|
+
* `relative_url` (String, nullable),
|
|
45
|
+
* `settings` (JSON, nullable), `updated_at` (Date, nullable),
|
|
46
|
+
* `url` (String, nullable absolute URL), `workspace` (Workspace,
|
|
47
|
+
* nullable — projected to `{id, name}`), `workspace_id` (ID,
|
|
48
|
+
* nullable). The `object_id` is Monday's internal opaque object
|
|
49
|
+
* identifier (distinct from `id`); both flow through verbatim.
|
|
50
|
+
*
|
|
51
|
+
* **BoardKind reuse for `Document.doc_kind`.** Monday's wire schema
|
|
52
|
+
* types `Document.doc_kind` as `BoardKind!` (NOT `DocKind!`) — the
|
|
53
|
+
* `BoardKind` enum is reused across `Board.kind` and
|
|
54
|
+
* `Document.doc_kind`, returning the same three string values
|
|
55
|
+
* (`public`/`private`/`share`). The standalone `DocKind` enum exists
|
|
56
|
+
* on the schema but isn't returned by `Document.doc_kind`; it's a
|
|
57
|
+
* wire-side detail with no agent-visible asymmetry (the CLI surface
|
|
58
|
+
* mirrors the wire string values verbatim). Not a new R-NEW-41
|
|
59
|
+
* consumer — the wire-vs-CLI projection is symmetric.
|
|
60
|
+
*
|
|
61
|
+
* **`DocumentBlock` — 9 fields.** Per the M32 probe: `id` (String!),
|
|
62
|
+
* `type` (String, nullable — block type like `'text'` / `'heading'`
|
|
63
|
+
* / `'list'` / etc., values stay verbatim from Monday's wire),
|
|
64
|
+
* `content` (JSON, nullable — block payload), `position` (Float,
|
|
65
|
+
* nullable — fractional ordering within doc), `parent_block_id`
|
|
66
|
+
* (String, nullable — for nested blocks), `doc_id` (ID, nullable),
|
|
67
|
+
* `created_at` (Date, nullable), `created_by` (User, nullable —
|
|
68
|
+
* projected to slim `{id, name}`), `updated_at` (Date, nullable).
|
|
69
|
+
* Block-content schema validity is NOT cross-checked by the CLI;
|
|
70
|
+
* Monday's wire is the source of truth for what blocks look like.
|
|
71
|
+
*
|
|
72
|
+
* **`DocsOrderBy` enum — 2 values.** `created_at` (most-recently-
|
|
73
|
+
* created first; Monday's documented `desc` ordering) and `used_at`
|
|
74
|
+
* (most-recently-viewed-by-current-user first; also `desc`). No
|
|
75
|
+
* ascending variant on Monday's wire — agents that need ascending
|
|
76
|
+
* sort the projection client-side. Default CLI behaviour: `created_
|
|
77
|
+
* at` (matches Monday's wire default at API `2026-01`).
|
|
78
|
+
*
|
|
79
|
+
* **No new ERROR_CODES (29 stays — D8 closure).** Doc-read failures
|
|
80
|
+
* route through existing codes:
|
|
81
|
+
*
|
|
82
|
+
* - `not_found` — `doc get <did>` against a non-existent or
|
|
83
|
+
* inaccessible doc ID. Monday's wire returns an empty `docs`
|
|
84
|
+
* array for both "doesn't exist" and "exists but token can't
|
|
85
|
+
* read it"; the CLI surfaces both as `not_found` with
|
|
86
|
+
* `details.doc_id`.
|
|
87
|
+
* - `usage_error` — argv-parse rejections (out-of-range `--limit`
|
|
88
|
+
* / `--page`, malformed `--workspace`, unknown `--order-by`).
|
|
89
|
+
* Caught at parse boundary BEFORE any wire call.
|
|
90
|
+
* - `validation_failed` — Monday-side rejection (very rare for
|
|
91
|
+
* reads; included for completeness).
|
|
92
|
+
* - `forbidden` / `unauthorized` — token lacks workdoc read scope.
|
|
93
|
+
*
|
|
94
|
+
* **Docs are live-only at v0.4-M32.** Per cli-design §8 cache scope,
|
|
95
|
+
* workdocs aren't cached — the `doc list` + `doc get` paths emit
|
|
96
|
+
* `meta.source: "live"` with `cache_age_seconds: null`. Workdocs
|
|
97
|
+
* are content-heavy + frequently human-edited, so caching would
|
|
98
|
+
* regularly surface stale prose. Mirrors `monday usage` (M22) +
|
|
99
|
+
* `monday status` (M22) + webhook list (M27) — diagnostics /
|
|
100
|
+
* volatile surfaces don't cache.
|
|
101
|
+
*
|
|
102
|
+
* **Runtime bodies landed at v0.4-M32 IMPL.** `listDocuments` +
|
|
103
|
+
* `getDocument` each issue a single `client.raw` round-trip with
|
|
104
|
+
* `operationName: 'ListDocs'` / `'GetDoc'` pinned literally at the
|
|
105
|
+
* fetcher boundary (R-NEW-37 W2 audit-point — operationNames are
|
|
106
|
+
* NOT caller-overridable). Responses parse through
|
|
107
|
+
* {@link documentSchema} / {@link documentWithBlocksSchema} via
|
|
108
|
+
* `unwrapOrThrow`, so payload drift surfaces `internal_error` with
|
|
109
|
+
* `details.issues`. The `doc get` empty-array case rewraps to
|
|
110
|
+
* `not_found` with `details.doc_id` per D8 (Monday's wire collapses
|
|
111
|
+
* "doesn't exist" + "not visible to token" into the same shape).
|
|
112
|
+
*
|
|
113
|
+
* **v0.5-M35 mutation surface (empirical probe 2026-05-15, API
|
|
114
|
+
* `2026-01`; v0.5 kickoff rounds 1-3).** Five CLI verbs land here
|
|
115
|
+
* at v0.5-M35, backed by four Monday GraphQL mutations —
|
|
116
|
+
* `create_doc` (2 CLI verbs because Monday's `CreateDocInput` is
|
|
117
|
+
* mutually-exclusive `board` vs `workspace` per D7) +
|
|
118
|
+
* `update_doc_name` + `delete_doc` + `duplicate_doc`. All four
|
|
119
|
+
* wire mutations are synchronous on Monday's wire — the v0.4-W1
|
|
120
|
+
* `dispatchPollingLoop` watch-item DOES NOT fire here.
|
|
121
|
+
*
|
|
122
|
+
* - **Create-in-workspace variant** — `create_doc(location:
|
|
123
|
+
* CreateDocInput!) → Document` with `location: { workspace:
|
|
124
|
+
* CreateDocWorkspaceInput { workspace_id!, name!, kind?:
|
|
125
|
+
* BoardKind, folder_id? } }`. CLI envelope OMITS the wire's
|
|
126
|
+
* `blocks` slot entirely (the base 13-field
|
|
127
|
+
* {@link documentSchema} M32 pin is the create projection).
|
|
128
|
+
* Monday returns `blocks: null` on a fresh create — agents
|
|
129
|
+
* needing block hydration (rare on a fresh create — usually
|
|
130
|
+
* the next step is content authoring) call `monday doc get
|
|
131
|
+
* <new-doc-id>` per the M32 read cadence.
|
|
132
|
+
* - **Create-on-column variant** — `create_doc(location:
|
|
133
|
+
* CreateDocInput!) → Document` with `location: { board:
|
|
134
|
+
* CreateDocBoardInput { column_id!, item_id! } }`.
|
|
135
|
+
* - **Rename variant** — `update_doc_name(docId: ID!,
|
|
136
|
+
* name: String!) → JSON` (opaque scalar). Returns wire-side
|
|
137
|
+
* opaque payload; the CLI projects to flat `{ doc_id: <input>,
|
|
138
|
+
* success: true }` envelope at the fetcher boundary per D9.
|
|
139
|
+
* The agent contract is the projected envelope shape, NOT the
|
|
140
|
+
* opaque Monday return value.
|
|
141
|
+
* - **Delete variant** — `delete_doc(docId: ID!) → JSON`
|
|
142
|
+
* (opaque). Same `{ doc_id, success }` projection per D9.
|
|
143
|
+
* Destructive gate per cli-design §3.1 (M35 verb requires
|
|
144
|
+
* `--yes`).
|
|
145
|
+
* - **Duplicate variant** — `duplicate_doc(docId: ID!,
|
|
146
|
+
* duplicateType?: DuplicateType) → JSON` (opaque). Same
|
|
147
|
+
* `{ doc_id, success }` projection per D9. `--with-updates`
|
|
148
|
+
* argv flag flips wire `duplicateType` from
|
|
149
|
+
* `duplicate_doc_with_content` (default) to
|
|
150
|
+
* `duplicate_doc_with_content_and_updates`. **No `--name <n>`
|
|
151
|
+
* rename slot per D8** — Monday's `duplicate_doc` mutation
|
|
152
|
+
* carries no name-override arg on the wire, so the CLI defers
|
|
153
|
+
* the rename-on-duplicate UX (an agent that needs a renamed
|
|
154
|
+
* duplicate pairs the verb with a follow-up `monday doc
|
|
155
|
+
* rename <new-id> --name <n>` call).
|
|
156
|
+
*
|
|
157
|
+
* **`CreateDocInput` is mutually-exclusive on Monday's wire.**
|
|
158
|
+
* Per Finding 6 + round-3 nested-inputs probe, supplying both
|
|
159
|
+
* `board` AND `workspace` slots fails server-side. The CLI's
|
|
160
|
+
* two-verb split (`monday doc create-in-workspace` vs `monday doc
|
|
161
|
+
* create-on-column`) flows the mutual-exclusion into the argv
|
|
162
|
+
* boundary — each verb's input schema declares the slot it
|
|
163
|
+
* supports, and there's no way to set both via the CLI surface.
|
|
164
|
+
* Single verb with `--workspace` / `--board` choosers was the
|
|
165
|
+
* D7 alternative; pre-flight ratified the two-verb shape per the
|
|
166
|
+
* agent-UX principle of "fewer ambiguous flags is clearer than
|
|
167
|
+
* one verb with mutually-exclusive choosers" (mirrors how the
|
|
168
|
+
* v0.4 cli-design ships separate `monday item upload` /
|
|
169
|
+
* `monday update upload` verbs for the same multipart wire path).
|
|
170
|
+
*
|
|
171
|
+
* **camelCase vs snake_case asymmetry across the doc-mutation
|
|
172
|
+
* surface (Finding 7).** Monday's `update_doc_name` /
|
|
173
|
+
* `delete_doc` / `duplicate_doc` mutations use **camelCase** arg
|
|
174
|
+
* names (`docId`, `duplicateType`) on the wire — distinct from
|
|
175
|
+
* the snake_case `doc_id` Monday uses for `Document` field names
|
|
176
|
+
* elsewhere on the schema. The fetcher boundary uses the wire's
|
|
177
|
+
* camelCase variable shape verbatim; the CLI argv stays kebab-case
|
|
178
|
+
* throughout (`--name <n>`, `--with-updates`); the error envelope
|
|
179
|
+
* `details.*` keys stay snake_case per cli-design §6.5 (e.g.
|
|
180
|
+
* `details.doc_id`). The asymmetry is wire-side and stays at the
|
|
181
|
+
* fetcher boundary; agents see camelCase nowhere. Cross-link to
|
|
182
|
+
* `docs/architecture.md` "Wire-vs-CLI semantics documentation
|
|
183
|
+
* conventions" — 4th supporting site for R-NEW-41 (camelCase vs
|
|
184
|
+
* snake_case arg-name asymmetry; R-v0.5-NEW-3 graduation candidate
|
|
185
|
+
* at v0.5-plan §22).
|
|
186
|
+
*
|
|
187
|
+
* **Opaque-JSON return shape (D9 closure).** 3 of 4 M35 mutations
|
|
188
|
+
* return Monday's `JSON` scalar — an untyped wire payload whose
|
|
189
|
+
* exact shape isn't pinned by introspection. The CLI projects to
|
|
190
|
+
* a flat `{ doc_id: string, success: true }` envelope at the
|
|
191
|
+
* fetcher boundary so agents read a uniform shape across rename /
|
|
192
|
+
* delete / duplicate; `success` is pinned literal-`true` because
|
|
193
|
+
* Monday surfaces failure via GraphQL `errors[]` (mapped to typed
|
|
194
|
+
* `ApiError`s upstream), not via a wire-side success flag.
|
|
195
|
+
*
|
|
196
|
+
* Per-fetcher null-payload semantics ARE asymmetric (round-1 P2-1
|
|
197
|
+
* closure):
|
|
198
|
+
*
|
|
199
|
+
* - **`renameDoc` (`update_doc_name`)** — present-but-null
|
|
200
|
+
* payload → success envelope. Monday's probe description
|
|
201
|
+
* carries NO "returns X" prose, so null is plausibly an
|
|
202
|
+
* empty-success indicator.
|
|
203
|
+
* - **`deleteDoc` (`delete_doc`)** — present-but-null payload
|
|
204
|
+
* → `not_found`. Probe description: "Returns success status
|
|
205
|
+
* and the deleted document ID" — null indicates the source
|
|
206
|
+
* doc was bogus or already-deleted.
|
|
207
|
+
* - **`duplicateDoc` (`duplicate_doc`)** — present-but-null
|
|
208
|
+
* payload → `not_found`. Probe description: "Returns the new
|
|
209
|
+
* document's ID on success" — null indicates the source
|
|
210
|
+
* wasn't duplicable.
|
|
211
|
+
*
|
|
212
|
+
* Missing-root-key cases for all three uniformly throw
|
|
213
|
+
* `internal_error` via `assertResponseFieldPresent` (schema
|
|
214
|
+
* drift). For `duplicate_doc`, the projected `doc_id` is the
|
|
215
|
+
* **NEWLY-CREATED doc's id** extracted from the opaque JSON via
|
|
216
|
+
* {@link extractDuplicateDocId} — defensive across multiple
|
|
217
|
+
* plausible wire shapes (bare string / number / record-with-`id`
|
|
218
|
+
* / `doc_id` / `new_doc_id`). Capturing a live duplicate-doc wire
|
|
219
|
+
* response would let a future revision narrow the helper's
|
|
220
|
+
* accepted shapes; today's helper is constructed against the
|
|
221
|
+
* read-only probe + Monday's wire description, not a live wire
|
|
222
|
+
* response.
|
|
223
|
+
*
|
|
224
|
+
* **No new ERROR_CODES at M35.** Existing codes route doc-mutation
|
|
225
|
+
* failures: `not_found` (rename/delete/duplicate against a
|
|
226
|
+
* non-existent or inaccessible doc), `usage_error` (argv-parse
|
|
227
|
+
* rejections — bad DocId, missing `--name`, unknown `--kind`),
|
|
228
|
+
* `validation_failed` (Monday-side rejection, e.g. workspace
|
|
229
|
+
* lacks doc-create permission), `forbidden`/`unauthorized` (token
|
|
230
|
+
* lacks workdoc write scope), `confirmation_required` (destructive
|
|
231
|
+
* gate on `doc delete` missing `--yes`).
|
|
232
|
+
*
|
|
233
|
+
* **Doc mutations are live-only at v0.5-M35.** Pure-mutation
|
|
234
|
+
* surfaces don't cache by definition (cli-design §8); `meta.source:
|
|
235
|
+
* "live"`. Dry-run paths emit `meta.source: "none"` per §6.4
|
|
236
|
+
* mutation-dry-run discipline.
|
|
237
|
+
*
|
|
238
|
+
* **Runtime bodies landed at v0.5-M35 IMPL.** All five fetchers
|
|
239
|
+
* issue a single `client.raw` round-trip with their pinned
|
|
240
|
+
* `operationName`; responses parse through the wrapping schemas
|
|
241
|
+
* via `unwrapOrThrow` + `assertResponseFieldPresent`, then either
|
|
242
|
+
* unwrap the per-Document payload (create variants) or project
|
|
243
|
+
* the opaque JSON return to the flat `{ doc_id, success: true }`
|
|
244
|
+
* envelope (rename / delete / duplicate per D9). The
|
|
245
|
+
* `duplicate_doc` projection extracts the new doc id from the
|
|
246
|
+
* opaque JSON via {@link extractDuplicateDocId} — defensive
|
|
247
|
+
* across common wire shapes (bare string, `{id}`, `{doc_id}`,
|
|
248
|
+
* `{new_doc_id}`); unrecognised shapes surface `internal_error`
|
|
249
|
+
* with a re-probe hint.
|
|
250
|
+
*
|
|
251
|
+
* **R-NEW-76 discipline preserved** across all 5 M35 command
|
|
252
|
+
* action bodies — `parseArgv` fires BEFORE `resolveClient` on
|
|
253
|
+
* every verb so invalid argv surfaces `usage_error` from the
|
|
254
|
+
* parse boundary, ahead of any `config_error` from a missing
|
|
255
|
+
* token. `doc delete` additionally calls `parseGlobalFlags` +
|
|
256
|
+
* `enforceDestructiveGate` BEFORE `resolveClient` (the gate
|
|
257
|
+
* needs the parsed global flags to check `--yes`; M10 round-1
|
|
258
|
+
* P2 invariant); the other four verbs let `resolveClient` parse
|
|
259
|
+
* global flags internally before `loadConfig` runs. M35 verbs
|
|
260
|
+
* do not consume comma-separated brand-list flags
|
|
261
|
+
* (`parseBrandedListArg` is an M34 team-writer / M32 `doc list
|
|
262
|
+
* --workspace` helper); the M35
|
|
263
|
+
* surface doesn't need it.
|
|
264
|
+
*/
|
|
265
|
+
import { z } from 'zod';
|
|
266
|
+
import { ApiError } from '../utils/errors.js';
|
|
267
|
+
import { unwrapOrThrow } from '../utils/parse-boundary.js';
|
|
268
|
+
import { assertResponseFieldPresent } from './response-root.js';
|
|
269
|
+
/**
|
|
270
|
+
* Schema for a required JSON-scalar slot — the key must be present
|
|
271
|
+
* on the parsed object, but the value can be any JSON shape Monday
|
|
272
|
+
* surfaces (object / array / string / number / boolean / null).
|
|
273
|
+
*
|
|
274
|
+
* Bare `z.unknown()` treats a missing key as "present with value
|
|
275
|
+
* `undefined`", so a wire response that omits `Document.settings`
|
|
276
|
+
* or `DocumentBlock.content` would still pass the strict schema —
|
|
277
|
+
* silently weakening the 13-field / 9-field contract. The
|
|
278
|
+
* `.refine` rejects `undefined` explicitly so a missing key
|
|
279
|
+
* surfaces as a typed parse error at the IMPL response-parse
|
|
280
|
+
* boundary (will fold into `internal_error` via `unwrapOrThrow`),
|
|
281
|
+
* matching every other field's "present-but-typed" semantics.
|
|
282
|
+
*
|
|
283
|
+
* Mirrors the M27 `Webhook.config: z.string().nullable()` pin
|
|
284
|
+
* (config is always present, value can be null) but for JSON-
|
|
285
|
+
* shaped slots whose payload shape varies per surface.
|
|
286
|
+
*/
|
|
287
|
+
const requiredJsonValueSchema = z.unknown().refine((v) => v !== undefined, {
|
|
288
|
+
message: 'required JSON value (may be null, but the key must be present)',
|
|
289
|
+
});
|
|
290
|
+
/**
|
|
291
|
+
* Inclusive range for the `--limit` argv slot on `monday doc list`.
|
|
292
|
+
* Default `25` matches Monday's wire-side default at API `2026-01`
|
|
293
|
+
* (per the M32 empirical probe `Query.docs.args.limit.description`:
|
|
294
|
+
* "Number of items to get, the default is 25"); ceiling `100` keeps
|
|
295
|
+
* worst-case response sizes bounded for doc-heavy accounts (a
|
|
296
|
+
* `--limit 500` request would multiply payload across 500 rich-text
|
|
297
|
+
* Document records, easily blowing Monday's complexity budget).
|
|
298
|
+
*/
|
|
299
|
+
export const MIN_DOC_LIST_LIMIT = 1;
|
|
300
|
+
export const MAX_DOC_LIST_LIMIT = 100;
|
|
301
|
+
export const DEFAULT_DOC_LIST_LIMIT = 25;
|
|
302
|
+
/**
|
|
303
|
+
* Monday's `DocsOrderBy` enum vocabulary (empirical probe 2026-05-14,
|
|
304
|
+
* API `2026-01`; 2 values). Pinned at M32 pre-flight as a closed
|
|
305
|
+
* literal-union enum so unknown `--order-by` values reject at parse
|
|
306
|
+
* boundary with `usage_error`.
|
|
307
|
+
*
|
|
308
|
+
* Both values sort `desc` on Monday's wire (most-recent first); no
|
|
309
|
+
* ascending variant is exposed. Adding a third value to Monday's
|
|
310
|
+
* enum is a minor (additive) bump for the CLI — extend this list +
|
|
311
|
+
* the per-command flag help.
|
|
312
|
+
*/
|
|
313
|
+
export const DOCS_ORDER_BY = ['created_at', 'used_at'];
|
|
314
|
+
export const docsOrderBySchema = z.enum(DOCS_ORDER_BY);
|
|
315
|
+
export const DEFAULT_DOCS_ORDER_BY = 'created_at';
|
|
316
|
+
/**
|
|
317
|
+
* Slim projection of Monday's `User` for the `Document.created_by` +
|
|
318
|
+
* `DocumentBlock.created_by` slots. Mirrors the M19 `account_tags`
|
|
319
|
+
* + M31 `Asset.uploaded_by` slim-User cadence: `{id, name}` only,
|
|
320
|
+
* keeping envelope size bounded. Full-User reads route through
|
|
321
|
+
* `monday user get <uid>`.
|
|
322
|
+
*/
|
|
323
|
+
export const docUserSchema = z
|
|
324
|
+
.object({
|
|
325
|
+
id: z.string().min(1),
|
|
326
|
+
name: z.string().min(1),
|
|
327
|
+
})
|
|
328
|
+
.strict();
|
|
329
|
+
/**
|
|
330
|
+
* Slim projection of Monday's `Workspace` for the `Document.workspace`
|
|
331
|
+
* slot. Same `{id, name}` shape as {@link docUserSchema}. Full-
|
|
332
|
+
* Workspace reads route through `monday workspace get <wid>`.
|
|
333
|
+
*/
|
|
334
|
+
export const docWorkspaceSchema = z
|
|
335
|
+
.object({
|
|
336
|
+
id: z.string().min(1),
|
|
337
|
+
name: z.string().min(1),
|
|
338
|
+
})
|
|
339
|
+
.strict();
|
|
340
|
+
/**
|
|
341
|
+
* `doc_kind` literal-union enum (3 values per the M32 probe + the
|
|
342
|
+
* DocKind introspection result). Monday's wire types this field as
|
|
343
|
+
* `BoardKind!` — see the module header's "BoardKind reuse" note for
|
|
344
|
+
* the wire-side type-name aliasing. The CLI surface mirrors the
|
|
345
|
+
* three string values verbatim with no projection drift.
|
|
346
|
+
*/
|
|
347
|
+
export const DOC_KIND_VALUES = ['public', 'private', 'share'];
|
|
348
|
+
export const docKindSchema = z.enum(DOC_KIND_VALUES);
|
|
349
|
+
/**
|
|
350
|
+
* DocumentBlock projection — Monday's 9-field block shape per the
|
|
351
|
+
* M32 probe. `content` is `JSON` on the wire (block payload — schema
|
|
352
|
+
* varies per `type`); the CLI passes it through unmodified as
|
|
353
|
+
* `unknown` so agents introspect the per-block-type payload
|
|
354
|
+
* themselves (Monday's wire is the source of truth for the per-
|
|
355
|
+
* block-type schema).
|
|
356
|
+
*
|
|
357
|
+
* Only surfaces under `doc get` envelopes (the per-doc body-hydrating
|
|
358
|
+
* path); `doc list` envelopes ship Documents WITHOUT `blocks` per
|
|
359
|
+
* the D6 list-row-projection closure.
|
|
360
|
+
*/
|
|
361
|
+
export const documentBlockSchema = z
|
|
362
|
+
.object({
|
|
363
|
+
id: z.string().min(1),
|
|
364
|
+
type: z.string().nullable(),
|
|
365
|
+
content: requiredJsonValueSchema,
|
|
366
|
+
position: z.number().nullable(),
|
|
367
|
+
parent_block_id: z.string().nullable(),
|
|
368
|
+
doc_id: z.string().nullable(),
|
|
369
|
+
created_at: z.string().nullable(),
|
|
370
|
+
created_by: docUserSchema.nullable(),
|
|
371
|
+
updated_at: z.string().nullable(),
|
|
372
|
+
})
|
|
373
|
+
.strict();
|
|
374
|
+
/**
|
|
375
|
+
* Base Document projection (13 fields — all of Monday's 14 minus
|
|
376
|
+
* `blocks`). Used as the list-row shape under `doc list` envelopes;
|
|
377
|
+
* appended with the required `blocks: [DocumentBlock]` slot for
|
|
378
|
+
* `doc get` envelopes via {@link documentWithBlocksSchema} (the
|
|
379
|
+
* extension makes `blocks` mandatory — `doc get` always hydrates
|
|
380
|
+
* the rich-text body per D6).
|
|
381
|
+
*
|
|
382
|
+
* `settings` is `JSON` on the wire (per-doc display/sharing config)
|
|
383
|
+
* — passed through as `unknown` for the same reason
|
|
384
|
+
* `DocumentBlock.content` is. Agents that need a specific settings
|
|
385
|
+
* key destructure client-side.
|
|
386
|
+
*/
|
|
387
|
+
export const documentSchema = z
|
|
388
|
+
.object({
|
|
389
|
+
id: z.string().min(1),
|
|
390
|
+
object_id: z.string().min(1),
|
|
391
|
+
name: z.string().min(1),
|
|
392
|
+
doc_kind: docKindSchema,
|
|
393
|
+
url: z.string().nullable(),
|
|
394
|
+
relative_url: z.string().nullable(),
|
|
395
|
+
workspace_id: z.string().nullable(),
|
|
396
|
+
workspace: docWorkspaceSchema.nullable(),
|
|
397
|
+
doc_folder_id: z.string().nullable(),
|
|
398
|
+
created_at: z.string().nullable(),
|
|
399
|
+
created_by: docUserSchema.nullable(),
|
|
400
|
+
updated_at: z.string().nullable(),
|
|
401
|
+
settings: requiredJsonValueSchema,
|
|
402
|
+
})
|
|
403
|
+
.strict();
|
|
404
|
+
/**
|
|
405
|
+
* `doc get` projection — base Document + the `blocks` slot hydrated.
|
|
406
|
+
* Same shape as {@link documentSchema} with `blocks:
|
|
407
|
+
* [DocumentBlock]` appended (Monday's wire returns the block array
|
|
408
|
+
* directly under `Document.blocks`; the CLI surfaces it verbatim).
|
|
409
|
+
*
|
|
410
|
+
* `blocks` is non-null on the wire when the selection is requested;
|
|
411
|
+
* an empty doc surfaces `blocks: []` rather than `null`. The schema
|
|
412
|
+
* pins non-null for envelope predictability — an unexpected null
|
|
413
|
+
* surfaces `internal_error` at the IMPL parse boundary.
|
|
414
|
+
*/
|
|
415
|
+
export const documentWithBlocksSchema = documentSchema
|
|
416
|
+
.extend({
|
|
417
|
+
blocks: z.array(documentBlockSchema),
|
|
418
|
+
})
|
|
419
|
+
.strict();
|
|
420
|
+
/**
|
|
421
|
+
* Output shape for `monday doc list [--workspace <wid>,...]
|
|
422
|
+
* [--order-by <created_at|used_at>] [--limit <n>] [--page <n>]`.
|
|
423
|
+
* Wrapped record (NOT bare array) because page/limit pagination
|
|
424
|
+
* surfaces pagination context inline rather than via `meta.cursor`
|
|
425
|
+
* (cli-design §6.1 cursor slot is for items_page-style surfaces;
|
|
426
|
+
* the workdocs wire has no cursor).
|
|
427
|
+
*
|
|
428
|
+
* `documents` — Monday's wire-ordered array (server-applied
|
|
429
|
+
* `created_at desc` or `used_at desc` per `--order-by`).
|
|
430
|
+
* `page` / `limit` — echoed inputs confirming what the wire saw.
|
|
431
|
+
* `returned_count` — `documents.length` cached for agent ergonomics.
|
|
432
|
+
* `has_more` — heuristic `returned_count === limit`; Monday's wire
|
|
433
|
+
* doesn't surface a total count, so "exactly `limit` rows returned"
|
|
434
|
+
* is the only signal that a follow-up page exists. Agents
|
|
435
|
+
* pessimistically re-fetch with `page + 1` if `has_more: true`.
|
|
436
|
+
*/
|
|
437
|
+
export const docListOutputSchema = z
|
|
438
|
+
.object({
|
|
439
|
+
documents: z.array(documentSchema),
|
|
440
|
+
page: z.number().int().min(1),
|
|
441
|
+
limit: z
|
|
442
|
+
.number()
|
|
443
|
+
.int()
|
|
444
|
+
.min(MIN_DOC_LIST_LIMIT)
|
|
445
|
+
.max(MAX_DOC_LIST_LIMIT),
|
|
446
|
+
returned_count: z.number().int().min(0),
|
|
447
|
+
has_more: z.boolean(),
|
|
448
|
+
})
|
|
449
|
+
.strict()
|
|
450
|
+
// Pagination-invariant cross-field check (round-2 P2-1 fix +
|
|
451
|
+
// round-3 P2-1 guard). The schema-level field types/ranges don't
|
|
452
|
+
// enforce the documented invariants between `returned_count` /
|
|
453
|
+
// `documents.length` / `limit` / `has_more` — an IMPL bug could
|
|
454
|
+
// emit inconsistent pagination data and still pass output
|
|
455
|
+
// validation, then bleed agent-visible drift into the envelope.
|
|
456
|
+
// Pin the two invariants here so the unwrap-or-throw boundary
|
|
457
|
+
// catches violations at parse time:
|
|
458
|
+
//
|
|
459
|
+
// 1. `returned_count === documents.length` — the count field is
|
|
460
|
+
// the cached array length, not an independent counter.
|
|
461
|
+
// 2. `has_more === (returned_count === limit)` — Monday's wire
|
|
462
|
+
// surface doesn't expose a total-count, so "exactly `limit`
|
|
463
|
+
// rows returned" is the only signal that a follow-up page
|
|
464
|
+
// may exist (D9 closure).
|
|
465
|
+
//
|
|
466
|
+
// **Round-3 P2-1 fix — early-return guard.** Zod's `.superRefine`
|
|
467
|
+
// still runs even when scalar range checks above have produced
|
|
468
|
+
// "dirty" issues. Without this guard, a malformed input like
|
|
469
|
+
// `{ limit: 0, returned_count: 0, has_more: false }` would emit
|
|
470
|
+
// BOTH the legitimate `limit` range-floor violation AND a
|
|
471
|
+
// misleading `has_more` invariant violation (because `has_more
|
|
472
|
+
// === (returned_count === limit)` evaluates against the invalid
|
|
473
|
+
// limit value). Short-circuit when participating scalars are
|
|
474
|
+
// out-of-range so the user sees only the underlying range
|
|
475
|
+
// violation, not a derived inconsistency error stacked on top.
|
|
476
|
+
.superRefine((value, ctx) => {
|
|
477
|
+
if (value.limit < MIN_DOC_LIST_LIMIT ||
|
|
478
|
+
value.limit > MAX_DOC_LIST_LIMIT ||
|
|
479
|
+
value.returned_count < 0 ||
|
|
480
|
+
!Number.isInteger(value.limit) ||
|
|
481
|
+
!Number.isInteger(value.returned_count)) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (value.returned_count !== value.documents.length) {
|
|
485
|
+
ctx.addIssue({
|
|
486
|
+
code: 'custom',
|
|
487
|
+
path: ['returned_count'],
|
|
488
|
+
message: `returned_count (${String(value.returned_count)}) must equal ` +
|
|
489
|
+
`documents.length (${String(value.documents.length)})`,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
const expectedHasMore = value.returned_count === value.limit;
|
|
493
|
+
if (value.has_more !== expectedHasMore) {
|
|
494
|
+
ctx.addIssue({
|
|
495
|
+
code: 'custom',
|
|
496
|
+
path: ['has_more'],
|
|
497
|
+
message: `has_more (${String(value.has_more)}) must equal ` +
|
|
498
|
+
`(returned_count === limit) which is ${String(expectedHasMore)}`,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
/**
|
|
503
|
+
* Output shape for `monday doc get <did>`. Direct unwrap of the
|
|
504
|
+
* single Document (with blocks hydrated) — matches the convention
|
|
505
|
+
* for read-one verbs (`monday board get <bid>` returns `data:
|
|
506
|
+
* <Board>`, `monday user get <uid>` returns `data: <User>`).
|
|
507
|
+
*
|
|
508
|
+
* The Document's own `id` field is the echoed input — no separate
|
|
509
|
+
* `doc_id` slot needed.
|
|
510
|
+
*/
|
|
511
|
+
export const docGetOutputSchema = documentWithBlocksSchema;
|
|
512
|
+
/**
|
|
513
|
+
* GraphQL query document for `Query.docs(...)` listing variant.
|
|
514
|
+
* Operation name pinned literally to `ListDocs` and matches the
|
|
515
|
+
* wire `operationName` payload (R-NEW-37 W2 audit-point — caller-
|
|
516
|
+
* overridable operationName slots were closed at M27 IMPL round-1
|
|
517
|
+
* P2-1; M32 maintains the safely-by-construction shape).
|
|
518
|
+
*
|
|
519
|
+
* Selects every list-row field (no `blocks` selection — list rows
|
|
520
|
+
* project the 13-field base shape per D6). `workspace_ids:` typed
|
|
521
|
+
* as `[ID]` to mirror Monday's wire signature (the inner ID is
|
|
522
|
+
* nullable on Monday's side — an empirical-probe finding that
|
|
523
|
+
* doesn't show up in the contract today but stays preserved for
|
|
524
|
+
* future-proofing).
|
|
525
|
+
*/
|
|
526
|
+
export const LIST_DOCS_QUERY = `
|
|
527
|
+
query ListDocs(
|
|
528
|
+
$workspaceIds: [ID],
|
|
529
|
+
$orderBy: DocsOrderBy,
|
|
530
|
+
$limit: Int,
|
|
531
|
+
$page: Int
|
|
532
|
+
) {
|
|
533
|
+
docs(
|
|
534
|
+
workspace_ids: $workspaceIds,
|
|
535
|
+
order_by: $orderBy,
|
|
536
|
+
limit: $limit,
|
|
537
|
+
page: $page
|
|
538
|
+
) {
|
|
539
|
+
id
|
|
540
|
+
object_id
|
|
541
|
+
name
|
|
542
|
+
doc_kind
|
|
543
|
+
url
|
|
544
|
+
relative_url
|
|
545
|
+
workspace_id
|
|
546
|
+
workspace { id name }
|
|
547
|
+
doc_folder_id
|
|
548
|
+
created_at
|
|
549
|
+
created_by { id name }
|
|
550
|
+
updated_at
|
|
551
|
+
settings
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
`;
|
|
555
|
+
/**
|
|
556
|
+
* GraphQL query document for `Query.docs(ids:)` single-id read
|
|
557
|
+
* variant. Operation name pinned to `GetDoc` (R-NEW-37 W2). Selects
|
|
558
|
+
* all base Document fields plus the `blocks` selection (the per-doc
|
|
559
|
+
* body-hydrating leg).
|
|
560
|
+
*
|
|
561
|
+
* Single-id wire shape — Monday returns `[Document]` (an array even
|
|
562
|
+
* for one id); the fetcher extracts index 0. An empty array
|
|
563
|
+
* (Monday's response for "doc not found" or "doc not visible to
|
|
564
|
+
* token") surfaces `not_found` with `details.doc_id`.
|
|
565
|
+
*/
|
|
566
|
+
export const GET_DOC_QUERY = `
|
|
567
|
+
query GetDoc($ids: [ID!]!) {
|
|
568
|
+
docs(ids: $ids) {
|
|
569
|
+
id
|
|
570
|
+
object_id
|
|
571
|
+
name
|
|
572
|
+
doc_kind
|
|
573
|
+
url
|
|
574
|
+
relative_url
|
|
575
|
+
workspace_id
|
|
576
|
+
workspace { id name }
|
|
577
|
+
doc_folder_id
|
|
578
|
+
created_at
|
|
579
|
+
created_by { id name }
|
|
580
|
+
updated_at
|
|
581
|
+
settings
|
|
582
|
+
blocks {
|
|
583
|
+
id
|
|
584
|
+
type
|
|
585
|
+
content
|
|
586
|
+
position
|
|
587
|
+
parent_block_id
|
|
588
|
+
doc_id
|
|
589
|
+
created_at
|
|
590
|
+
created_by { id name }
|
|
591
|
+
updated_at
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
`;
|
|
596
|
+
/**
|
|
597
|
+
* Wrapping response schema for the `ListDocs` operation. Monday's
|
|
598
|
+
* documented wire shape is `[Document]` (an array, possibly empty)
|
|
599
|
+
* — never null. The wrapper accepts `null` defensively so a wire-
|
|
600
|
+
* shape regression parses cleanly and is rewrapped by the fetcher
|
|
601
|
+
* as `internal_error` with a drift hint (rather than faulting the
|
|
602
|
+
* parse with a confusing zod issue path). Both fetchers reject a
|
|
603
|
+
* null root post-parse; null is NEVER a `not_found` rewrap target
|
|
604
|
+
* — that's the empty-array D8 case in `getDocument`.
|
|
605
|
+
*
|
|
606
|
+
* `.loose()` mirrors the M27 `listWebhooksResponseSchema` cadence —
|
|
607
|
+
* Monday occasionally returns side-band debug keys (`extensions`,
|
|
608
|
+
* `account_id`) alongside the documented data root; the loose
|
|
609
|
+
* mode lets them pass without faulting the parse.
|
|
610
|
+
*/
|
|
611
|
+
const listDocsResponseSchema = z
|
|
612
|
+
.object({
|
|
613
|
+
docs: z.array(documentSchema).nullable(),
|
|
614
|
+
})
|
|
615
|
+
.loose();
|
|
616
|
+
/**
|
|
617
|
+
* Wrapping response schema for the `GetDoc` operation. Same shape
|
|
618
|
+
* as the list variant but with `blocks` hydrated on every entry.
|
|
619
|
+
*/
|
|
620
|
+
const getDocResponseSchema = z
|
|
621
|
+
.object({
|
|
622
|
+
docs: z.array(documentWithBlocksSchema).nullable(),
|
|
623
|
+
})
|
|
624
|
+
.loose();
|
|
625
|
+
/**
|
|
626
|
+
* Fetches the workdocs visible to the token via a single
|
|
627
|
+
* `Query.docs(...)` round-trip with `operationName: 'ListDocs'`
|
|
628
|
+
* (R-NEW-37 W2). Source is always `'live'` per cli-design §8 cache
|
|
629
|
+
* scope; workdocs aren't cached at v0.4 per D7.
|
|
630
|
+
*
|
|
631
|
+
* Variables map to Monday's wire `Query.docs(...)` args:
|
|
632
|
+
* `workspaceIds` → `workspace_ids: [ID]`; `orderBy` →
|
|
633
|
+
* `order_by: DocsOrderBy`; `limit` → `limit: Int`; `page` →
|
|
634
|
+
* `page: Int`. Omitted inputs drop the corresponding `$variable`
|
|
635
|
+
* so Monday's per-arg server-side default applies (rather than
|
|
636
|
+
* threading an explicit `null` that the wire treats as "field
|
|
637
|
+
* present").
|
|
638
|
+
*
|
|
639
|
+
* Echoed `page` / `limit` carry Monday's defaults when the caller
|
|
640
|
+
* omits them ({@link DEFAULT_DOC_LIST_LIMIT} for limit, `1` for
|
|
641
|
+
* page) so the envelope's pagination-invariant `.superRefine`
|
|
642
|
+
* sees consistent values regardless of which inputs the verb
|
|
643
|
+
* received.
|
|
644
|
+
*
|
|
645
|
+
* A null `docs` root surfaces `internal_error` (Monday's documented
|
|
646
|
+
* shape is `[Document]` even for empty accounts — the array, never
|
|
647
|
+
* null at this layer). Schema drift in the per-doc shape rewraps to
|
|
648
|
+
* `internal_error` with `details.issues` via `unwrapOrThrow`.
|
|
649
|
+
*/
|
|
650
|
+
export const listDocuments = async (inputs) => {
|
|
651
|
+
const variables = {};
|
|
652
|
+
if (inputs.workspaceIds !== undefined) {
|
|
653
|
+
variables.workspaceIds = inputs.workspaceIds;
|
|
654
|
+
}
|
|
655
|
+
if (inputs.orderBy !== undefined) {
|
|
656
|
+
variables.orderBy = inputs.orderBy;
|
|
657
|
+
}
|
|
658
|
+
if (inputs.limit !== undefined) {
|
|
659
|
+
variables.limit = inputs.limit;
|
|
660
|
+
}
|
|
661
|
+
if (inputs.page !== undefined) {
|
|
662
|
+
variables.page = inputs.page;
|
|
663
|
+
}
|
|
664
|
+
const response = await inputs.client.raw(LIST_DOCS_QUERY, variables, { operationName: 'ListDocs' });
|
|
665
|
+
const parsed = unwrapOrThrow(listDocsResponseSchema.safeParse(response.data), {
|
|
666
|
+
context: 'Monday `Query.docs(ListDocs)` response',
|
|
667
|
+
hint: 'Monday may have amended the `Document` selection — re-probe and amend `src/api/documents.ts` if so',
|
|
668
|
+
});
|
|
669
|
+
if (parsed.docs === null) {
|
|
670
|
+
throw new ApiError('internal_error', 'Monday returned a null `docs` payload from ListDocs', {
|
|
671
|
+
details: {
|
|
672
|
+
hint: 'Monday\'s documented shape is `[Document]` (an array, possibly empty) — ' +
|
|
673
|
+
'a null root indicates a wire change that needs re-probing',
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
documents: parsed.docs,
|
|
679
|
+
page: inputs.page ?? 1,
|
|
680
|
+
limit: inputs.limit ?? DEFAULT_DOC_LIST_LIMIT,
|
|
681
|
+
source: 'live',
|
|
682
|
+
cacheAgeSeconds: null,
|
|
683
|
+
complexity: response.complexity,
|
|
684
|
+
};
|
|
685
|
+
};
|
|
686
|
+
/**
|
|
687
|
+
* Fetches a single workdoc by ID via a single `Query.docs(ids:)`
|
|
688
|
+
* round-trip with `operationName: 'GetDoc'` (R-NEW-37 W2). Returns
|
|
689
|
+
* the Document with `blocks` hydrated. Source is always `'live'`
|
|
690
|
+
* per cli-design §8 cache scope.
|
|
691
|
+
*
|
|
692
|
+
* Empty wire response (Monday's shape for "doc doesn't exist" OR
|
|
693
|
+
* "doc not visible to token") surfaces `not_found` with
|
|
694
|
+
* `details.doc_id` — no `forbidden` rewrap, because Monday's wire
|
|
695
|
+
* collapses the two cases into one shape and the CLI can't
|
|
696
|
+
* distinguish them per D8.
|
|
697
|
+
*
|
|
698
|
+
* A multi-element wire response (which Monday's `docs(ids:)`
|
|
699
|
+
* shouldn't return for a single-id query) surfaces `internal_error`
|
|
700
|
+
* as a defensive guard — the CLI assumes one doc per id and a
|
|
701
|
+
* count mismatch indicates a wire-shape regression worth surfacing
|
|
702
|
+
* loudly rather than silently dropping entries.
|
|
703
|
+
*/
|
|
704
|
+
export const getDocument = async (inputs) => {
|
|
705
|
+
const response = await inputs.client.raw(GET_DOC_QUERY, { ids: [inputs.docId] }, { operationName: 'GetDoc' });
|
|
706
|
+
const parsed = unwrapOrThrow(getDocResponseSchema.safeParse(response.data), {
|
|
707
|
+
context: 'Monday `Query.docs(GetDoc)` response',
|
|
708
|
+
details: { doc_id: inputs.docId },
|
|
709
|
+
hint: 'Monday may have amended the `Document` / `DocumentBlock` selection — re-probe and amend `src/api/documents.ts` if so',
|
|
710
|
+
});
|
|
711
|
+
if (parsed.docs === null) {
|
|
712
|
+
throw new ApiError('internal_error', `Monday returned a null \`docs\` payload from GetDoc(${inputs.docId})`, {
|
|
713
|
+
details: {
|
|
714
|
+
doc_id: inputs.docId,
|
|
715
|
+
hint: 'Monday\'s documented shape is `[Document]` (an array, possibly empty) — ' +
|
|
716
|
+
'a null root indicates a wire change that needs re-probing',
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (parsed.docs.length === 0) {
|
|
721
|
+
throw new ApiError('not_found', `workdoc ${inputs.docId} not found (does not exist or not visible to token)`, { details: { doc_id: inputs.docId } });
|
|
722
|
+
}
|
|
723
|
+
if (parsed.docs.length > 1) {
|
|
724
|
+
throw new ApiError('internal_error', `Monday returned ${String(parsed.docs.length)} docs for a single-id GetDoc query`, {
|
|
725
|
+
details: {
|
|
726
|
+
doc_id: inputs.docId,
|
|
727
|
+
hint: 'wire shape regression — re-probe `Query.docs(ids:)`',
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
const [document] = parsed.docs;
|
|
732
|
+
/* c8 ignore next 6 */
|
|
733
|
+
if (document === undefined) {
|
|
734
|
+
throw new ApiError('internal_error', `Monday returned a sparse docs array for GetDoc(${inputs.docId})`, { details: { doc_id: inputs.docId } });
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
document,
|
|
738
|
+
source: 'live',
|
|
739
|
+
cacheAgeSeconds: null,
|
|
740
|
+
complexity: response.complexity,
|
|
741
|
+
};
|
|
742
|
+
};
|
|
743
|
+
// ===========================================================================
|
|
744
|
+
// v0.5-M35 doc-level CRUD mutation surface
|
|
745
|
+
// ===========================================================================
|
|
746
|
+
// All five fetchers below landed runtime bodies at v0.5-M35 IMPL. Each
|
|
747
|
+
// issues a single `client.raw` round-trip with its pinned `operationName`,
|
|
748
|
+
// parses via the wrapping response schema + `assertResponseFieldPresent`,
|
|
749
|
+
// and either unwraps the Document payload (create variants) or projects
|
|
750
|
+
// the opaque JSON return into the flat `{ doc_id, success: true }`
|
|
751
|
+
// envelope per D9 (rename / delete / duplicate).
|
|
752
|
+
/**
|
|
753
|
+
* Monday's `DuplicateType` enum vocabulary (empirical probe 2026-05-15,
|
|
754
|
+
* API `2026-01`; 2 values). Pinned at M35 pre-flight as a closed
|
|
755
|
+
* literal-union so unknown values reject at the parse boundary with
|
|
756
|
+
* `usage_error`.
|
|
757
|
+
*
|
|
758
|
+
* - `duplicate_doc_with_content` — clone the doc body only; comments
|
|
759
|
+
* and update history are NOT copied. Wire-side default when
|
|
760
|
+
* `duplicateType` is omitted.
|
|
761
|
+
* - `duplicate_doc_with_content_and_updates` — clone the doc body
|
|
762
|
+
* AND every comment / update thread attached to the source doc.
|
|
763
|
+
* Picks up Monday's "full backup" duplicate semantics.
|
|
764
|
+
*
|
|
765
|
+
* Maps to the CLI's boolean `--with-updates` flag at the M35
|
|
766
|
+
* `monday doc duplicate` argv boundary: absent → wire default
|
|
767
|
+
* (`duplicate_doc_with_content`, content-only); present → wire
|
|
768
|
+
* `duplicate_doc_with_content_and_updates`. The two-value enum
|
|
769
|
+
* stays internal to the fetcher — agents see a boolean opt-in, not
|
|
770
|
+
* the wire enum name.
|
|
771
|
+
*
|
|
772
|
+
* Adding a third value to Monday's enum is a minor (additive) bump
|
|
773
|
+
* for the CLI — extend this list + the fetcher mapping; the
|
|
774
|
+
* boolean argv stays.
|
|
775
|
+
*/
|
|
776
|
+
export const DUPLICATE_TYPE_VALUES = [
|
|
777
|
+
'duplicate_doc_with_content',
|
|
778
|
+
'duplicate_doc_with_content_and_updates',
|
|
779
|
+
];
|
|
780
|
+
export const duplicateTypeSchema = z.enum(DUPLICATE_TYPE_VALUES);
|
|
781
|
+
/**
|
|
782
|
+
* Projected envelope shape for the three opaque-JSON mutation
|
|
783
|
+
* results (`update_doc_name` / `delete_doc` / `duplicate_doc`).
|
|
784
|
+
* Per D9 closure, the CLI surfaces a flat `{ doc_id: string,
|
|
785
|
+
* success: true }` envelope so agents read a uniform shape across
|
|
786
|
+
* mutations — Monday's wire returns an opaque `JSON` scalar whose
|
|
787
|
+
* exact shape isn't pinned by introspection (the v0.5 doc-CRUD
|
|
788
|
+
* probe was read-only — no live mutation response was captured).
|
|
789
|
+
* The projection insulates agents from any wire-side shape drift;
|
|
790
|
+
* the runtime accepts plausible-defensive shapes today, and a
|
|
791
|
+
* future live wire response would narrow what the helper accepts.
|
|
792
|
+
*
|
|
793
|
+
* `success` is pinned to literal `true` because Monday surfaces
|
|
794
|
+
* failure via GraphQL `errors[]` (mapped to typed `ApiError`s at
|
|
795
|
+
* the transport layer); a JSON return that reaches this projection
|
|
796
|
+
* is by construction the success path. If Monday ever flips to a
|
|
797
|
+
* `success/error` result OBJECT shape for these mutations (mirroring
|
|
798
|
+
* `import_doc_from_html` / `add_content_to_doc_from_markdown` per
|
|
799
|
+
* Finding 6), the projection widens to a discriminated union — for
|
|
800
|
+
* today the literal-`true` form keeps the shape pinned.
|
|
801
|
+
*
|
|
802
|
+
* `doc_id` semantics differ per mutation:
|
|
803
|
+
*
|
|
804
|
+
* - `rename` / `delete` — echoes the input id (the operation
|
|
805
|
+
* targets that specific doc; success means it was renamed /
|
|
806
|
+
* deleted).
|
|
807
|
+
* - `duplicate` — emits the **NEWLY-CREATED** doc's id (Monday's
|
|
808
|
+
* `duplicate_doc` description: "Returns the new document's ID
|
|
809
|
+
* on success"; the fetcher extracts the new id from the
|
|
810
|
+
* opaque JSON via {@link extractDuplicateDocId}, which is
|
|
811
|
+
* defensive across plausible wire shapes today). The original
|
|
812
|
+
* source-doc id stays available via the argv positional.
|
|
813
|
+
*/
|
|
814
|
+
export const docMutationResultSchema = z
|
|
815
|
+
.object({
|
|
816
|
+
doc_id: z.string().min(1),
|
|
817
|
+
success: z.literal(true),
|
|
818
|
+
})
|
|
819
|
+
.strict();
|
|
820
|
+
/**
|
|
821
|
+
* Output shape for `monday doc create-in-workspace --workspace
|
|
822
|
+
* <wid> --name <n> [--folder <fid>] [--kind public|private|share]`.
|
|
823
|
+
* Direct unwrap of the created Document — `data: <Document>` per
|
|
824
|
+
* cli-design §6.1 single-record convention. The envelope OMITS
|
|
825
|
+
* the `blocks` slot entirely (the base {@link documentSchema} M32
|
|
826
|
+
* pin is the 13-field projection without `blocks`).
|
|
827
|
+
*
|
|
828
|
+
* **Rationale for the omit.** Monday's wire returns
|
|
829
|
+
* `blocks: null` on a fresh `create_doc` because a freshly-
|
|
830
|
+
* created doc has no rich-text body yet (Monday hasn't
|
|
831
|
+
* materialised it). Surfacing a constant-null `blocks` slot on
|
|
832
|
+
* every create envelope would add agent-noise — every caller
|
|
833
|
+
* has to ignore it. Agents that need block hydration (rare on a
|
|
834
|
+
* fresh create — usually the next step is content authoring)
|
|
835
|
+
* call `monday doc get <new-doc-id>` per the M32 read cadence.
|
|
836
|
+
* The omit is a contract decision, not a schema-flexibility
|
|
837
|
+
* artifact.
|
|
838
|
+
*/
|
|
839
|
+
export const docCreateInWorkspaceOutputSchema = documentSchema;
|
|
840
|
+
/**
|
|
841
|
+
* Output shape for `monday doc create-on-column --item <iid>
|
|
842
|
+
* --column <cid>`. Same shape as
|
|
843
|
+
* {@link docCreateInWorkspaceOutputSchema} — Monday's wire returns
|
|
844
|
+
* the full Document regardless of placement; the two-verb split
|
|
845
|
+
* (per D7) lives at the argv layer, not the response shape.
|
|
846
|
+
*/
|
|
847
|
+
export const docCreateOnColumnOutputSchema = documentSchema;
|
|
848
|
+
/**
|
|
849
|
+
* Output shape for `monday doc rename <doc-id> --name <n>`.
|
|
850
|
+
* Projected from Monday's opaque `JSON` scalar return per D9 — the
|
|
851
|
+
* fetcher emits `{ doc_id: <echoed>, success: true }`. Agents key
|
|
852
|
+
* off `ok: true` for retry/idempotency reasoning; the literal
|
|
853
|
+
* `success: true` slot exists for envelope-snapshot stability and
|
|
854
|
+
* future-proofing (if Monday ever surfaces a wire-side success
|
|
855
|
+
* flag, the schema widens to read the wire value rather than
|
|
856
|
+
* always emitting `true`).
|
|
857
|
+
*/
|
|
858
|
+
export const docRenameOutputSchema = docMutationResultSchema;
|
|
859
|
+
/**
|
|
860
|
+
* Output shape for `monday doc delete <doc-id> --yes`. Same
|
|
861
|
+
* `{ doc_id: <echoed>, success: true }` projection as rename per
|
|
862
|
+
* D9. The destructive-gate envelope sits at the action body —
|
|
863
|
+
* `confirmation_required` fires BEFORE this output schema is
|
|
864
|
+
* referenced (cli-design §3.1 #6).
|
|
865
|
+
*/
|
|
866
|
+
export const docDeleteOutputSchema = docMutationResultSchema;
|
|
867
|
+
/**
|
|
868
|
+
* Output shape for `monday doc duplicate <doc-id> [--with-updates]`.
|
|
869
|
+
* Same flat `{ doc_id, success: true }` projection per D9, BUT the
|
|
870
|
+
* `doc_id` slot carries the **newly-created duplicate's id** — NOT
|
|
871
|
+
* the source-doc id. The verb's positional argv is the source-doc
|
|
872
|
+
* id; the wire returns the new id in its JSON payload, which
|
|
873
|
+
* {@link extractDuplicateDocId} pulls out (defensive across
|
|
874
|
+
* plausible shapes today; a future live wire response would
|
|
875
|
+
* narrow what the helper accepts).
|
|
876
|
+
*
|
|
877
|
+
* **No `--name <n>` slot per D8** — Monday's `duplicate_doc`
|
|
878
|
+
* mutation carries no rename-on-duplicate arg. Agents needing a
|
|
879
|
+
* renamed duplicate pair with a follow-up `monday doc rename
|
|
880
|
+
* <new-id> --name <n>` call.
|
|
881
|
+
*/
|
|
882
|
+
export const docDuplicateOutputSchema = docMutationResultSchema;
|
|
883
|
+
/**
|
|
884
|
+
* GraphQL mutation document for `create_doc(location: {workspace:
|
|
885
|
+
* ...})`. Operation name pinned to `CreateDocInWorkspace`
|
|
886
|
+
* (R-NEW-37 W2 audit-point — operationNames NOT caller-overridable).
|
|
887
|
+
* Selects every base-Document field per the M32 cadence; `blocks`
|
|
888
|
+
* deliberately omitted (Monday's wire returns `blocks: null` on a
|
|
889
|
+
* fresh create — see the module header's create-variant note).
|
|
890
|
+
*
|
|
891
|
+
* `$input: CreateDocInput!` carries the mutually-exclusive wire
|
|
892
|
+
* shape; the verb's action body composes
|
|
893
|
+
* `{ workspace: { workspace_id, name, folder_id?, kind? } }` from
|
|
894
|
+
* the parsed argv. The `board` slot stays unset at the wire — the
|
|
895
|
+
* two-verb CLI split (per D7) makes the mutual-exclusion safely-
|
|
896
|
+
* by-construction.
|
|
897
|
+
*/
|
|
898
|
+
export const CREATE_DOC_IN_WORKSPACE_MUTATION = `
|
|
899
|
+
mutation CreateDocInWorkspace($input: CreateDocInput!) {
|
|
900
|
+
create_doc(location: $input) {
|
|
901
|
+
id
|
|
902
|
+
object_id
|
|
903
|
+
name
|
|
904
|
+
doc_kind
|
|
905
|
+
url
|
|
906
|
+
relative_url
|
|
907
|
+
workspace_id
|
|
908
|
+
workspace { id name }
|
|
909
|
+
doc_folder_id
|
|
910
|
+
created_at
|
|
911
|
+
created_by { id name }
|
|
912
|
+
updated_at
|
|
913
|
+
settings
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
`;
|
|
917
|
+
/**
|
|
918
|
+
* GraphQL mutation document for `create_doc(location: {board: ...})`.
|
|
919
|
+
* Operation name pinned to `CreateDocOnColumn` (R-NEW-37 W2). Same
|
|
920
|
+
* Document selection as {@link CREATE_DOC_IN_WORKSPACE_MUTATION};
|
|
921
|
+
* `$input: CreateDocInput!` is composed as `{ board: { column_id,
|
|
922
|
+
* item_id } }` at the action body. Distinct named operation per
|
|
923
|
+
* verb so the wire-side `operationName` payload mirrors the CLI
|
|
924
|
+
* verb name verbatim (agents tracing wire traffic can identify the
|
|
925
|
+
* verb without parsing the doc body).
|
|
926
|
+
*/
|
|
927
|
+
export const CREATE_DOC_ON_COLUMN_MUTATION = `
|
|
928
|
+
mutation CreateDocOnColumn($input: CreateDocInput!) {
|
|
929
|
+
create_doc(location: $input) {
|
|
930
|
+
id
|
|
931
|
+
object_id
|
|
932
|
+
name
|
|
933
|
+
doc_kind
|
|
934
|
+
url
|
|
935
|
+
relative_url
|
|
936
|
+
workspace_id
|
|
937
|
+
workspace { id name }
|
|
938
|
+
doc_folder_id
|
|
939
|
+
created_at
|
|
940
|
+
created_by { id name }
|
|
941
|
+
updated_at
|
|
942
|
+
settings
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
`;
|
|
946
|
+
/**
|
|
947
|
+
* GraphQL mutation document for `update_doc_name(docId, name) →
|
|
948
|
+
* JSON`. Operation name pinned to `UpdateDocName` (R-NEW-37 W2).
|
|
949
|
+
* The wire return type is the opaque `JSON` scalar — no selection
|
|
950
|
+
* set per GraphQL grammar (scalars are leaves). The CLI projects
|
|
951
|
+
* the opaque value to the flat `{ doc_id, success }` envelope at
|
|
952
|
+
* the fetcher boundary per D9.
|
|
953
|
+
*
|
|
954
|
+
* `docId` + `name` are camelCase on Monday's wire per Finding 7;
|
|
955
|
+
* the variable names mirror the wire shape verbatim. CLI argv is
|
|
956
|
+
* `<doc-id>` positional + `--name <n>` flag (kebab-case throughout).
|
|
957
|
+
*/
|
|
958
|
+
export const UPDATE_DOC_NAME_MUTATION = `
|
|
959
|
+
mutation UpdateDocName($docId: ID!, $name: String!) {
|
|
960
|
+
update_doc_name(docId: $docId, name: $name)
|
|
961
|
+
}
|
|
962
|
+
`;
|
|
963
|
+
/**
|
|
964
|
+
* GraphQL mutation document for `delete_doc(docId) → JSON`.
|
|
965
|
+
* Operation name pinned to `DeleteDoc` (R-NEW-37 W2). Wire return
|
|
966
|
+
* is opaque JSON — same projection cadence as
|
|
967
|
+
* {@link UPDATE_DOC_NAME_MUTATION}.
|
|
968
|
+
*
|
|
969
|
+
* **Destructive-gate ordering.** The verb's action body MUST call
|
|
970
|
+
* `enforceDestructiveGate` BEFORE this fetcher per the M10 round-1
|
|
971
|
+
* P2 invariant. A missing `--yes` surfaces as
|
|
972
|
+
* `confirmation_required` from the action layer, never masked by
|
|
973
|
+
* `config_error` when no token is configured.
|
|
974
|
+
*/
|
|
975
|
+
export const DELETE_DOC_MUTATION = `
|
|
976
|
+
mutation DeleteDoc($docId: ID!) {
|
|
977
|
+
delete_doc(docId: $docId)
|
|
978
|
+
}
|
|
979
|
+
`;
|
|
980
|
+
/**
|
|
981
|
+
* GraphQL mutation document for `duplicate_doc(docId,
|
|
982
|
+
* duplicateType?) → JSON`. Operation name pinned to `DuplicateDoc`
|
|
983
|
+
* (R-NEW-37 W2). `duplicateType` is the optional 2-value enum
|
|
984
|
+
* (see {@link DUPLICATE_TYPE_VALUES}); when omitted at the variable
|
|
985
|
+
* boundary, Monday's wire-side default (`duplicate_doc_with_content`,
|
|
986
|
+
* content-only) applies.
|
|
987
|
+
*
|
|
988
|
+
* The verb's `--with-updates` boolean argv maps to:
|
|
989
|
+
* absent → omit the variable entirely (wire default applies);
|
|
990
|
+
* present → `duplicateType: 'duplicate_doc_with_content_and_updates'`.
|
|
991
|
+
* The omit-vs-null discipline mirrors M34 `team-create`'s
|
|
992
|
+
* `is_guest_team` handling — Monday treats a `null` variable as
|
|
993
|
+
* "field present with null value" rather than "field omitted".
|
|
994
|
+
*
|
|
995
|
+
* Wire return is opaque JSON — same projection cadence as the
|
|
996
|
+
* rename + delete mutations.
|
|
997
|
+
*/
|
|
998
|
+
export const DUPLICATE_DOC_MUTATION = `
|
|
999
|
+
mutation DuplicateDoc($docId: ID!, $duplicateType: DuplicateType) {
|
|
1000
|
+
duplicate_doc(docId: $docId, duplicateType: $duplicateType)
|
|
1001
|
+
}
|
|
1002
|
+
`;
|
|
1003
|
+
/**
|
|
1004
|
+
* Wrapping response schema for the `CreateDocInWorkspace` /
|
|
1005
|
+
* `CreateDocOnColumn` mutations. Monday's wire shape returns the
|
|
1006
|
+
* full Document on success; a `null` payload surfaces
|
|
1007
|
+
* `internal_error` (a successful `create_doc` must return the
|
|
1008
|
+
* created Document per Monday's documented contract — null
|
|
1009
|
+
* indicates a wire-shape regression worth surfacing loudly rather
|
|
1010
|
+
* than silently dropping the response).
|
|
1011
|
+
*
|
|
1012
|
+
* Shared between both create-variants because the wire returns
|
|
1013
|
+
* the same Document shape regardless of placement; the schema's
|
|
1014
|
+
* `.loose()` mode mirrors M27 / M32 / M34 — Monday occasionally
|
|
1015
|
+
* surfaces side-band debug keys (`extensions`, `account_id`)
|
|
1016
|
+
* alongside the documented data root, and the loose mode lets
|
|
1017
|
+
* them pass without faulting the parse.
|
|
1018
|
+
*
|
|
1019
|
+
* The `create_doc` slot widens to `unknown` so the wrapping parse
|
|
1020
|
+
* tolerates Monday's side-band keys and any rare `null` payload;
|
|
1021
|
+
* the post-parse layer pins the value against {@link documentSchema}
|
|
1022
|
+
* via `unwrapOrThrow` so per-field drift surfaces with structured
|
|
1023
|
+
* `details.issues` (mirrors M34 createTeam's two-stage parse).
|
|
1024
|
+
*/
|
|
1025
|
+
const createDocResponseSchema = z
|
|
1026
|
+
.object({
|
|
1027
|
+
create_doc: z.unknown(),
|
|
1028
|
+
})
|
|
1029
|
+
.loose();
|
|
1030
|
+
/**
|
|
1031
|
+
* Wrapping response schema for the `UpdateDocName` mutation. The
|
|
1032
|
+
* `update_doc_name` root carries Monday's opaque `JSON` scalar —
|
|
1033
|
+
* `z.unknown()` accepts any wire shape (null / record / scalar)
|
|
1034
|
+
* and the projection layer extracts what it needs. Missing key
|
|
1035
|
+
* surfaces `internal_error` via `assertResponseFieldPresent`.
|
|
1036
|
+
*/
|
|
1037
|
+
const updateDocNameResponseSchema = z
|
|
1038
|
+
.object({
|
|
1039
|
+
update_doc_name: z.unknown(),
|
|
1040
|
+
})
|
|
1041
|
+
.loose();
|
|
1042
|
+
/**
|
|
1043
|
+
* Wrapping response schema for the `DeleteDoc` mutation. Same
|
|
1044
|
+
* shape as {@link updateDocNameResponseSchema} — opaque JSON
|
|
1045
|
+
* scalar at the root; projection layer handles the extraction.
|
|
1046
|
+
* A `null` value surfaces `not_found` (mirrors M14 workspace-
|
|
1047
|
+
* delete + M34 team-delete cadence — id bogus / already deleted
|
|
1048
|
+
* by a concurrent caller).
|
|
1049
|
+
*/
|
|
1050
|
+
const deleteDocResponseSchema = z
|
|
1051
|
+
.object({
|
|
1052
|
+
delete_doc: z.unknown(),
|
|
1053
|
+
})
|
|
1054
|
+
.loose();
|
|
1055
|
+
/**
|
|
1056
|
+
* Wrapping response schema for the `DuplicateDoc` mutation. Same
|
|
1057
|
+
* opaque-JSON shape as the rename/delete variants. Monday's wire
|
|
1058
|
+
* carries the newly-created doc id in this opaque JSON payload
|
|
1059
|
+
* (probe description: "Returns the new document's ID on
|
|
1060
|
+
* success"); the exact shape isn't pinned by introspection and
|
|
1061
|
+
* the v0.5 doc-CRUD probe was read-only. The fetcher extracts
|
|
1062
|
+
* the new id via {@link extractDuplicateDocId}, which accepts
|
|
1063
|
+
* multiple plausible shapes defensively (bare string / number /
|
|
1064
|
+
* record-with-`id` / `doc_id` / `new_doc_id`). A future live
|
|
1065
|
+
* wire response would let a follow-up commit narrow what the
|
|
1066
|
+
* helper accepts to the exact wire shape.
|
|
1067
|
+
*/
|
|
1068
|
+
const duplicateDocResponseSchema = z
|
|
1069
|
+
.object({
|
|
1070
|
+
duplicate_doc: z.unknown(),
|
|
1071
|
+
})
|
|
1072
|
+
.loose();
|
|
1073
|
+
/**
|
|
1074
|
+
* Creates a workspace-scoped workdoc via `create_doc(location:
|
|
1075
|
+
* { workspace: {...} })` with `operationName:
|
|
1076
|
+
* 'CreateDocInWorkspace'` (R-NEW-37 W2). Returns the created
|
|
1077
|
+
* Document with `id` populated post-create.
|
|
1078
|
+
*
|
|
1079
|
+
* The fetcher composes `location: { workspace: { workspace_id,
|
|
1080
|
+
* name, folder_id?, kind? } }` from the inputs; `folderId` /
|
|
1081
|
+
* `kind` are omitted entirely from the wire payload when unset
|
|
1082
|
+
* (Monday's per-arg server-side defaults apply — null would be
|
|
1083
|
+
* treated as "field present" rather than "field omitted",
|
|
1084
|
+
* mirroring M34 `createTeam`'s discipline).
|
|
1085
|
+
*
|
|
1086
|
+
* A missing-key wire response surfaces `internal_error` via
|
|
1087
|
+
* `assertResponseFieldPresent`; a null payload surfaces
|
|
1088
|
+
* `internal_error` too — a successful `create_doc` mutation must
|
|
1089
|
+
* return the created Document per Monday's documented contract.
|
|
1090
|
+
*/
|
|
1091
|
+
export const createDocInWorkspace = async (inputs) => {
|
|
1092
|
+
const workspaceInput = {
|
|
1093
|
+
workspace_id: inputs.workspaceId,
|
|
1094
|
+
name: inputs.name,
|
|
1095
|
+
};
|
|
1096
|
+
if (inputs.folderId !== undefined) {
|
|
1097
|
+
workspaceInput.folder_id = inputs.folderId;
|
|
1098
|
+
}
|
|
1099
|
+
if (inputs.kind !== undefined) {
|
|
1100
|
+
workspaceInput.kind = inputs.kind;
|
|
1101
|
+
}
|
|
1102
|
+
const response = await inputs.client.raw(CREATE_DOC_IN_WORKSPACE_MUTATION, { input: { workspace: workspaceInput } }, { operationName: 'CreateDocInWorkspace' });
|
|
1103
|
+
const data = unwrapOrThrow(createDocResponseSchema.safeParse(response.data), {
|
|
1104
|
+
context: 'Monday returned a malformed CreateDocInWorkspace response',
|
|
1105
|
+
details: { workspace_id: inputs.workspaceId, name: inputs.name },
|
|
1106
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1107
|
+
'the response shape and update `createDocResponseSchema` if ' +
|
|
1108
|
+
'Monday\'s contract has changed.',
|
|
1109
|
+
});
|
|
1110
|
+
assertResponseFieldPresent({
|
|
1111
|
+
data,
|
|
1112
|
+
key: 'create_doc',
|
|
1113
|
+
operationLabel: 'CreateDocInWorkspace',
|
|
1114
|
+
details: { workspace_id: inputs.workspaceId, name: inputs.name },
|
|
1115
|
+
nullHandling: 'caller_handles',
|
|
1116
|
+
});
|
|
1117
|
+
const rawDoc = data.create_doc;
|
|
1118
|
+
if (rawDoc === null || rawDoc === undefined) {
|
|
1119
|
+
throw new ApiError('internal_error', `Monday returned no document payload from create_doc(location: {workspace: ${inputs.workspaceId}}, name: ${JSON.stringify(inputs.name)}).`, { details: { workspace_id: inputs.workspaceId, name: inputs.name } });
|
|
1120
|
+
}
|
|
1121
|
+
const document = unwrapOrThrow(documentSchema.safeParse(rawDoc), {
|
|
1122
|
+
context: `Monday returned a malformed Document payload from create_doc (workspace ${inputs.workspaceId})`,
|
|
1123
|
+
details: { workspace_id: inputs.workspaceId, name: inputs.name },
|
|
1124
|
+
});
|
|
1125
|
+
return {
|
|
1126
|
+
document,
|
|
1127
|
+
source: 'live',
|
|
1128
|
+
cacheAgeSeconds: null,
|
|
1129
|
+
complexity: response.complexity,
|
|
1130
|
+
};
|
|
1131
|
+
};
|
|
1132
|
+
/**
|
|
1133
|
+
* Creates an item-scoped workdoc via `create_doc(location:
|
|
1134
|
+
* { board: {...} })` with `operationName: 'CreateDocOnColumn'`
|
|
1135
|
+
* (R-NEW-37 W2). Returns the created Document; the doc is
|
|
1136
|
+
* embedded into the named column of the named item.
|
|
1137
|
+
*
|
|
1138
|
+
* The fetcher composes `location: { board: { column_id, item_id
|
|
1139
|
+
* } }` from the inputs. Both slots are required on Monday's wire
|
|
1140
|
+
* (`CreateDocBoardInput.column_id!` + `CreateDocBoardInput.
|
|
1141
|
+
* item_id!`); the CLI verb's `--item <iid>` + `--column <cid>`
|
|
1142
|
+
* flags are both required at the parse boundary.
|
|
1143
|
+
*
|
|
1144
|
+
* Failure modes mirror the workspace variant: missing-key
|
|
1145
|
+
* response → `internal_error`; null payload → `internal_error`;
|
|
1146
|
+
* column not configured for docs → `validation_failed` (Monday-
|
|
1147
|
+
* side rejection — the CLI doesn't pre-check column-type
|
|
1148
|
+
* compatibility, mirroring M8's `change_column_value` cadence).
|
|
1149
|
+
*/
|
|
1150
|
+
export const createDocOnColumn = async (inputs) => {
|
|
1151
|
+
const response = await inputs.client.raw(CREATE_DOC_ON_COLUMN_MUTATION, {
|
|
1152
|
+
input: {
|
|
1153
|
+
board: { item_id: inputs.itemId, column_id: inputs.columnId },
|
|
1154
|
+
},
|
|
1155
|
+
}, { operationName: 'CreateDocOnColumn' });
|
|
1156
|
+
const data = unwrapOrThrow(createDocResponseSchema.safeParse(response.data), {
|
|
1157
|
+
context: 'Monday returned a malformed CreateDocOnColumn response',
|
|
1158
|
+
details: { item_id: inputs.itemId, column_id: inputs.columnId },
|
|
1159
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1160
|
+
'the response shape and update `createDocResponseSchema` if ' +
|
|
1161
|
+
'Monday\'s contract has changed.',
|
|
1162
|
+
});
|
|
1163
|
+
assertResponseFieldPresent({
|
|
1164
|
+
data,
|
|
1165
|
+
key: 'create_doc',
|
|
1166
|
+
operationLabel: 'CreateDocOnColumn',
|
|
1167
|
+
details: { item_id: inputs.itemId, column_id: inputs.columnId },
|
|
1168
|
+
nullHandling: 'caller_handles',
|
|
1169
|
+
});
|
|
1170
|
+
const rawDoc = data.create_doc;
|
|
1171
|
+
if (rawDoc === null || rawDoc === undefined) {
|
|
1172
|
+
throw new ApiError('internal_error', `Monday returned no document payload from create_doc(location: {board: {item_id: ${inputs.itemId}, column_id: ${inputs.columnId}}}).`, { details: { item_id: inputs.itemId, column_id: inputs.columnId } });
|
|
1173
|
+
}
|
|
1174
|
+
const document = unwrapOrThrow(documentSchema.safeParse(rawDoc), {
|
|
1175
|
+
context: `Monday returned a malformed Document payload from create_doc (item ${inputs.itemId}, column ${inputs.columnId})`,
|
|
1176
|
+
details: { item_id: inputs.itemId, column_id: inputs.columnId },
|
|
1177
|
+
});
|
|
1178
|
+
return {
|
|
1179
|
+
document,
|
|
1180
|
+
source: 'live',
|
|
1181
|
+
cacheAgeSeconds: null,
|
|
1182
|
+
complexity: response.complexity,
|
|
1183
|
+
};
|
|
1184
|
+
};
|
|
1185
|
+
/**
|
|
1186
|
+
* Renames a workdoc via `update_doc_name(docId, name)` with
|
|
1187
|
+
* `operationName: 'UpdateDocName'` (R-NEW-37 W2). Projects
|
|
1188
|
+
* Monday's opaque JSON return into the flat
|
|
1189
|
+
* `{ doc_id: <echoed>, success: true }` envelope per D9.
|
|
1190
|
+
*
|
|
1191
|
+
* Failure modes:
|
|
1192
|
+
*
|
|
1193
|
+
* - **Missing `update_doc_name` key** → `internal_error` via
|
|
1194
|
+
* `assertResponseFieldPresent` (schema drift — Monday's
|
|
1195
|
+
* response shape regressed).
|
|
1196
|
+
* - **Present-but-null payload** → projected as success (the
|
|
1197
|
+
* `{doc_id, success: true}` envelope). Monday's probe
|
|
1198
|
+
* description for `update_doc_name` carries NO "returns X"
|
|
1199
|
+
* prose (unlike `delete_doc` which promises "success status
|
|
1200
|
+
* and the deleted document ID"), so a null wire payload is a
|
|
1201
|
+
* plausible empty-success indicator. If Monday returns a
|
|
1202
|
+
* typed error for non-existent doc IDs, that bubbles via
|
|
1203
|
+
* GraphQL `errors[]` (mapped to typed `ApiError`s upstream) —
|
|
1204
|
+
* a present-but-null JSON return reaching this projection is
|
|
1205
|
+
* by construction the success path. Distinct from
|
|
1206
|
+
* `deleteDoc` + `duplicateDoc` cadence which DO treat null
|
|
1207
|
+
* as `not_found` because their probe descriptions explicitly
|
|
1208
|
+
* promise a non-null return payload on success.
|
|
1209
|
+
* - **Typed Monday errors** (forbidden / not_found from a wire-
|
|
1210
|
+
* side `errors[]`) → bubble through the transport's error-
|
|
1211
|
+
* mapping layer.
|
|
1212
|
+
*/
|
|
1213
|
+
export const renameDoc = async (inputs) => {
|
|
1214
|
+
const response = await inputs.client.raw(UPDATE_DOC_NAME_MUTATION, { docId: inputs.docId, name: inputs.name }, { operationName: 'UpdateDocName' });
|
|
1215
|
+
const data = unwrapOrThrow(updateDocNameResponseSchema.safeParse(response.data), {
|
|
1216
|
+
context: 'Monday returned a malformed UpdateDocName response',
|
|
1217
|
+
details: { doc_id: inputs.docId },
|
|
1218
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1219
|
+
'the response shape and update `updateDocNameResponseSchema` ' +
|
|
1220
|
+
'if Monday\'s contract has changed.',
|
|
1221
|
+
});
|
|
1222
|
+
assertResponseFieldPresent({
|
|
1223
|
+
data,
|
|
1224
|
+
key: 'update_doc_name',
|
|
1225
|
+
operationLabel: 'UpdateDocName',
|
|
1226
|
+
details: { doc_id: inputs.docId },
|
|
1227
|
+
nullHandling: 'caller_handles',
|
|
1228
|
+
});
|
|
1229
|
+
// Project Monday's opaque JSON return per D9 — agents see a
|
|
1230
|
+
// uniform `{ doc_id, success: true }` envelope regardless of
|
|
1231
|
+
// what's inside `data.update_doc_name`. Unlike `delete_doc` +
|
|
1232
|
+
// `duplicate_doc`, a present `null` is NOT remapped to
|
|
1233
|
+
// `not_found` (round-1 P2-1 closure — Monday's
|
|
1234
|
+
// `update_doc_name` probe description makes no return-shape
|
|
1235
|
+
// promise, so null is a plausible empty-success indicator).
|
|
1236
|
+
return {
|
|
1237
|
+
result: { doc_id: inputs.docId, success: true },
|
|
1238
|
+
source: 'live',
|
|
1239
|
+
cacheAgeSeconds: null,
|
|
1240
|
+
complexity: response.complexity,
|
|
1241
|
+
};
|
|
1242
|
+
};
|
|
1243
|
+
/**
|
|
1244
|
+
* Deletes a workdoc by ID via `delete_doc(docId)` with
|
|
1245
|
+
* `operationName: 'DeleteDoc'` (R-NEW-37 W2). Projects Monday's
|
|
1246
|
+
* opaque JSON return into the flat `{ doc_id: <echoed>,
|
|
1247
|
+
* success: true }` envelope per D9.
|
|
1248
|
+
*
|
|
1249
|
+
* A null `delete_doc` payload surfaces `not_found` — same
|
|
1250
|
+
* cadence as M34 team-delete + M14 workspace-delete. A missing
|
|
1251
|
+
* key surfaces `internal_error`.
|
|
1252
|
+
*
|
|
1253
|
+
* **Destructive-gate ordering.** The verb's action body MUST
|
|
1254
|
+
* call `enforceDestructiveGate` BEFORE this fetcher per the
|
|
1255
|
+
* M10 round-1 P2 invariant. A missing `--yes` surfaces as
|
|
1256
|
+
* `confirmation_required` from the action layer, never masked
|
|
1257
|
+
* by `config_error` when no token is configured.
|
|
1258
|
+
*/
|
|
1259
|
+
export const deleteDoc = async (inputs) => {
|
|
1260
|
+
const response = await inputs.client.raw(DELETE_DOC_MUTATION, { docId: inputs.docId }, { operationName: 'DeleteDoc' });
|
|
1261
|
+
const data = unwrapOrThrow(deleteDocResponseSchema.safeParse(response.data), {
|
|
1262
|
+
context: 'Monday returned a malformed DeleteDoc response',
|
|
1263
|
+
details: { doc_id: inputs.docId },
|
|
1264
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1265
|
+
'the response shape and update `deleteDocResponseSchema` if ' +
|
|
1266
|
+
'Monday\'s contract has changed.',
|
|
1267
|
+
});
|
|
1268
|
+
assertResponseFieldPresent({
|
|
1269
|
+
data,
|
|
1270
|
+
key: 'delete_doc',
|
|
1271
|
+
operationLabel: 'DeleteDoc',
|
|
1272
|
+
details: { doc_id: inputs.docId },
|
|
1273
|
+
nullHandling: 'caller_handles',
|
|
1274
|
+
});
|
|
1275
|
+
const rawPayload = data.delete_doc;
|
|
1276
|
+
if (rawPayload === null || rawPayload === undefined) {
|
|
1277
|
+
throw new ApiError('not_found', `Monday returned no payload from delete_doc for doc ${inputs.docId}`, { details: { doc_id: inputs.docId } });
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
result: { doc_id: inputs.docId, success: true },
|
|
1281
|
+
source: 'live',
|
|
1282
|
+
cacheAgeSeconds: null,
|
|
1283
|
+
complexity: response.complexity,
|
|
1284
|
+
};
|
|
1285
|
+
};
|
|
1286
|
+
/**
|
|
1287
|
+
* Best-effort extractor for the new doc's id from
|
|
1288
|
+
* `duplicate_doc`'s opaque JSON return. Monday's wire description
|
|
1289
|
+
* says "Returns the new document's ID on success" but doesn't
|
|
1290
|
+
* pin the wire shape; the schema introspection types the return
|
|
1291
|
+
* as the opaque `JSON` scalar.
|
|
1292
|
+
*
|
|
1293
|
+
* Tries common shapes:
|
|
1294
|
+
*
|
|
1295
|
+
* - Bare string → that's the new id.
|
|
1296
|
+
* - Numeric scalar → stringify (Monday's wire mixes ID + Int
|
|
1297
|
+
* types; the CLI's brand stays string-shaped).
|
|
1298
|
+
* - Record with `id` / `doc_id` / `new_doc_id` field carrying a
|
|
1299
|
+
* non-empty string or number → extract.
|
|
1300
|
+
*
|
|
1301
|
+
* Anything else surfaces `internal_error` with a hint pointing
|
|
1302
|
+
* at re-probing `duplicate_doc` live so a future commit can
|
|
1303
|
+
* narrow the helper to Monday's actual wire shape.
|
|
1304
|
+
*/
|
|
1305
|
+
const extractDuplicateDocId = (raw, sourceId) => {
|
|
1306
|
+
if (typeof raw === 'string' && raw.length > 0)
|
|
1307
|
+
return raw;
|
|
1308
|
+
if (typeof raw === 'number' && Number.isFinite(raw))
|
|
1309
|
+
return String(raw);
|
|
1310
|
+
if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {
|
|
1311
|
+
const rec = raw;
|
|
1312
|
+
for (const key of ['id', 'doc_id', 'new_doc_id']) {
|
|
1313
|
+
const value = rec[key];
|
|
1314
|
+
if (typeof value === 'string' && value.length > 0)
|
|
1315
|
+
return value;
|
|
1316
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1317
|
+
return String(value);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
throw new ApiError('internal_error', `duplicate_doc returned an opaque JSON payload the CLI could not extract a new doc id from (source ${sourceId})`, {
|
|
1322
|
+
details: {
|
|
1323
|
+
doc_id: sourceId,
|
|
1324
|
+
hint: 'Monday\'s `duplicate_doc` wire shape changed — re-probe via ' +
|
|
1325
|
+
'`scripts/probe/` and extend `extractDuplicateDocId` to cover ' +
|
|
1326
|
+
'the new shape.',
|
|
1327
|
+
},
|
|
1328
|
+
});
|
|
1329
|
+
};
|
|
1330
|
+
/**
|
|
1331
|
+
* Duplicates a workdoc by ID via `duplicate_doc(docId,
|
|
1332
|
+
* duplicateType?)` with `operationName: 'DuplicateDoc'`
|
|
1333
|
+
* (R-NEW-37 W2). Projects Monday's opaque JSON return into the
|
|
1334
|
+
* flat `{ doc_id: <NEW>, success: true }` envelope per D9 — the
|
|
1335
|
+
* `doc_id` slot carries the **newly-created duplicate's id**,
|
|
1336
|
+
* NOT the input source-doc id (the source id stays accessible
|
|
1337
|
+
* via the verb's positional argv).
|
|
1338
|
+
*
|
|
1339
|
+
* The fetcher omits the `duplicateType` variable entirely when
|
|
1340
|
+
* `inputs.duplicateType` is unset so Monday's wire-side default
|
|
1341
|
+
* applies (the M34 `team-create` omit-vs-null discipline). A
|
|
1342
|
+
* null `duplicate_doc` payload surfaces `not_found` (source
|
|
1343
|
+
* doc id bogus or inaccessible); missing key surfaces
|
|
1344
|
+
* `internal_error`. The new-id extraction tolerates several
|
|
1345
|
+
* common shapes Monday's opaque JSON might carry (bare string,
|
|
1346
|
+
* `{id}`, `{doc_id}`, `{new_doc_id}`) — anything else surfaces
|
|
1347
|
+
* `internal_error` with a re-probe hint.
|
|
1348
|
+
*/
|
|
1349
|
+
export const duplicateDoc = async (inputs) => {
|
|
1350
|
+
const variables = { docId: inputs.docId };
|
|
1351
|
+
if (inputs.duplicateType !== undefined) {
|
|
1352
|
+
variables.duplicateType = inputs.duplicateType;
|
|
1353
|
+
}
|
|
1354
|
+
const response = await inputs.client.raw(DUPLICATE_DOC_MUTATION, variables, { operationName: 'DuplicateDoc' });
|
|
1355
|
+
const data = unwrapOrThrow(duplicateDocResponseSchema.safeParse(response.data), {
|
|
1356
|
+
context: 'Monday returned a malformed DuplicateDoc response',
|
|
1357
|
+
details: { doc_id: inputs.docId },
|
|
1358
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1359
|
+
'the response shape and update `duplicateDocResponseSchema` ' +
|
|
1360
|
+
'if Monday\'s contract has changed.',
|
|
1361
|
+
});
|
|
1362
|
+
assertResponseFieldPresent({
|
|
1363
|
+
data,
|
|
1364
|
+
key: 'duplicate_doc',
|
|
1365
|
+
operationLabel: 'DuplicateDoc',
|
|
1366
|
+
details: { doc_id: inputs.docId },
|
|
1367
|
+
nullHandling: 'caller_handles',
|
|
1368
|
+
});
|
|
1369
|
+
const rawPayload = data.duplicate_doc;
|
|
1370
|
+
if (rawPayload === null || rawPayload === undefined) {
|
|
1371
|
+
throw new ApiError('not_found', `Monday returned no payload from duplicate_doc for source doc ${inputs.docId}`, { details: { doc_id: inputs.docId } });
|
|
1372
|
+
}
|
|
1373
|
+
const newDocId = extractDuplicateDocId(rawPayload, inputs.docId);
|
|
1374
|
+
return {
|
|
1375
|
+
result: { doc_id: newDocId, success: true },
|
|
1376
|
+
source: 'live',
|
|
1377
|
+
cacheAgeSeconds: null,
|
|
1378
|
+
complexity: response.complexity,
|
|
1379
|
+
};
|
|
1380
|
+
};
|
|
1381
|
+
// ===========================================================================
|
|
1382
|
+
// v0.5-M36 doc-block CRUD mutation surface
|
|
1383
|
+
// ===========================================================================
|
|
1384
|
+
// Three fetchers land the per-block CRUD wire surface against Monday's
|
|
1385
|
+
// `create_doc_block` / `update_doc_block` / `delete_doc_block` mutations.
|
|
1386
|
+
// Each issues a single `client.raw` round-trip with a literal-pinned
|
|
1387
|
+
// `operationName` (R-NEW-37 W2 safely-by-construction), parses the
|
|
1388
|
+
// wrapping response shape via a loose `z.object({...}).loose()` schema,
|
|
1389
|
+
// asserts the field presence via `assertResponseFieldPresent`, applies
|
|
1390
|
+
// the per-fetcher null-payload contract (per R-v0.5-NEW-11 — see
|
|
1391
|
+
// per-fetcher JSDoc), and unwraps the inner OBJECT via the shared
|
|
1392
|
+
// {@link documentBlockSchema} (create + update) or the new
|
|
1393
|
+
// {@link documentBlockIdOnlySchema} (delete). Each command action body
|
|
1394
|
+
// runs `parseArgv` (+ `parseJsonArg` for `--content` consumers +
|
|
1395
|
+
// `enforceDestructiveGate` on `block-delete --yes`) BEFORE
|
|
1396
|
+
// `resolveClient` so invalid argv surfaces `usage_error` from the parse
|
|
1397
|
+
// boundary (R-NEW-76 graduated discipline).
|
|
1398
|
+
//
|
|
1399
|
+
// Wire summary (empirical probe at `scripts/probe/v0.5-doc-mutations.ts` +
|
|
1400
|
+
// `v0.5-inputs-and-results.ts`, 2026-05-15, API `2026-01`):
|
|
1401
|
+
//
|
|
1402
|
+
// - `create_doc_block(doc_id: ID!, type: DocBlockContentType!,
|
|
1403
|
+
// content: JSON!, after_block_id: String, parent_block_id: String) →
|
|
1404
|
+
// DocumentBlock` (NON_NULL/OBJECT). Operation name `CreateDocBlock`
|
|
1405
|
+
// pinned literally per R-NEW-37 W2.
|
|
1406
|
+
// - `update_doc_block(block_id: String!, content: JSON!) →
|
|
1407
|
+
// DocumentBlock` (NON_NULL/OBJECT). Operation name `UpdateDocBlock`.
|
|
1408
|
+
// - `delete_doc_block(block_id: String!) → DocumentBlockIdOnly`
|
|
1409
|
+
// (NON_NULL/OBJECT with a single `id: String!` field). Operation
|
|
1410
|
+
// name `DeleteDocBlock`.
|
|
1411
|
+
//
|
|
1412
|
+
// **Snake_case wire arg names (Finding 7).** M36's three wire mutations
|
|
1413
|
+
// use SNAKE_CASE arg names (`doc_id`, `block_id`, `after_block_id`,
|
|
1414
|
+
// `parent_block_id`) — back to Monday's standard cadence after the
|
|
1415
|
+
// M35 camelCase asymmetry (`docId` / `duplicateType` on `update_doc_name`
|
|
1416
|
+
// / `delete_doc` / `duplicate_doc`). The GraphQL operation documents
|
|
1417
|
+
// below use camelCase variable names + snake_case wire arg names
|
|
1418
|
+
// (`doc_id: $docId`), matching the `Query.docs(workspace_ids:
|
|
1419
|
+
// $workspaceIds)` M32 cadence. Not a new R-NEW-41 supporting site —
|
|
1420
|
+
// M36 is the symmetric path; M35 was the asymmetric one.
|
|
1421
|
+
//
|
|
1422
|
+
// **OBJECT-return cadence (distinct from M35 opaque-JSON).** Unlike
|
|
1423
|
+
// M35's `update_doc_name` / `delete_doc` / `duplicate_doc` (3 of 4
|
|
1424
|
+
// returned the opaque `JSON` scalar driving the {@link extractDuplicateDocId}
|
|
1425
|
+
// helper + the `{doc_id, success: true}` projection per D9), all three
|
|
1426
|
+
// M36 wire mutations return typed OBJECT shapes. `create_doc_block` +
|
|
1427
|
+
// `update_doc_block` both return the full `DocumentBlock` (9 fields per
|
|
1428
|
+
// the M32 probe — id / type / content / position / parent_block_id /
|
|
1429
|
+
// doc_id / created_at / created_by / updated_at); the fetchers parse via
|
|
1430
|
+
// {@link documentBlockSchema} (reused from M32's `doc get` blocks-
|
|
1431
|
+
// hydration leg) and unwrap. `delete_doc_block` returns `DocumentBlockIdOnly`
|
|
1432
|
+
// — a single `{id: String!}` field — and the fetcher parses via the
|
|
1433
|
+
// new {@link documentBlockIdOnlySchema}. No opaque-JSON projection
|
|
1434
|
+
// helper needed; the OBJECT-return shape is pinned by schema.
|
|
1435
|
+
//
|
|
1436
|
+
// **Per-block `content` shape (D11 closure).** Wire `content: JSON!` —
|
|
1437
|
+
// an opaque per-`DocBlockContentType` shape varying across the 16 enum
|
|
1438
|
+
// values. The CLI accepts user-supplied `--content <json>` strings
|
|
1439
|
+
// unmodified (parsed once at the argv boundary via `parseJsonArg`;
|
|
1440
|
+
// R-NEW-42's 4th + 5th consumers) and passes the resulting JS value to
|
|
1441
|
+
// Monday's wire `JSON` scalar verbatim. Per-block-type content payload
|
|
1442
|
+
// shapes (one per `DocBlockContentType` value) are documented at
|
|
1443
|
+
// `docs/output-shapes.md` "Per-block content shapes" reference table —
|
|
1444
|
+
// cassette-sourced rows for the variants M36 IMPL exercises live, plus
|
|
1445
|
+
// TBD / inferred rows pending follow-up cassettes. Monday's wire stays
|
|
1446
|
+
// the source of truth, and agents that pass a shape-incompatible
|
|
1447
|
+
// `--content` for the chosen `--type` surface `validation_failed` from
|
|
1448
|
+
// Monday at the live path.
|
|
1449
|
+
//
|
|
1450
|
+
// **R-NEW-76 discipline preserved across all three M36 action bodies.**
|
|
1451
|
+
// `parseArgv` (+ `parseJsonArg` for `--content` consumers, +
|
|
1452
|
+
// `enforceDestructiveGate` on `block-delete --yes`) fires BEFORE
|
|
1453
|
+
// `resolveClient` so invalid argv surfaces `usage_error` from the parse
|
|
1454
|
+
// boundary, NOT `internal_error` from a later wire-call leg.
|
|
1455
|
+
/**
|
|
1456
|
+
* Monday's `DocBlockContentType` enum vocabulary (empirical probe
|
|
1457
|
+
* 2026-05-15, API `2026-01`; 16 closed values per Finding 8). Pinned
|
|
1458
|
+
* at M36 pre-flight as a closed literal-union enum so unknown
|
|
1459
|
+
* `--type` values reject at the argv-parse boundary with
|
|
1460
|
+
* `usage_error.details.issues[]` (D10 closure).
|
|
1461
|
+
*
|
|
1462
|
+
* Adding a 17th value to Monday's enum is a minor (additive) bump
|
|
1463
|
+
* for the CLI — extend this list + the per-command flag help; the
|
|
1464
|
+
* per-block content schema documentation in `docs/output-shapes.md`
|
|
1465
|
+
* "Per-block content shapes" reference table gains a new row for
|
|
1466
|
+
* the new variant once a live cassette pins its shape (extending
|
|
1467
|
+
* the table is additive at any future v0.5.x patch).
|
|
1468
|
+
*/
|
|
1469
|
+
export const DOC_BLOCK_CONTENT_TYPE_VALUES = [
|
|
1470
|
+
'bulleted_list',
|
|
1471
|
+
'check_list',
|
|
1472
|
+
'code',
|
|
1473
|
+
'divider',
|
|
1474
|
+
'image',
|
|
1475
|
+
'large_title',
|
|
1476
|
+
'layout',
|
|
1477
|
+
'medium_title',
|
|
1478
|
+
'normal_text',
|
|
1479
|
+
'notice_box',
|
|
1480
|
+
'numbered_list',
|
|
1481
|
+
'page_break',
|
|
1482
|
+
'quote',
|
|
1483
|
+
'small_title',
|
|
1484
|
+
'table',
|
|
1485
|
+
'video',
|
|
1486
|
+
];
|
|
1487
|
+
export const docBlockContentTypeSchema = z.enum(DOC_BLOCK_CONTENT_TYPE_VALUES);
|
|
1488
|
+
/**
|
|
1489
|
+
* `DocumentBlockIdOnly` projection — Monday's `delete_doc_block`
|
|
1490
|
+
* mutation return shape per the v0.5 empirical probe. Single-field
|
|
1491
|
+
* OBJECT carrying only the deleted block's id; the wire description
|
|
1492
|
+
* is "A monday.com doc block" but introspection pins exactly one
|
|
1493
|
+
* field. Mirrors `DocumentBlock.id`'s `String!` shape (non-empty,
|
|
1494
|
+
* opaque).
|
|
1495
|
+
*/
|
|
1496
|
+
export const documentBlockIdOnlySchema = z
|
|
1497
|
+
.object({
|
|
1498
|
+
id: z.string().min(1),
|
|
1499
|
+
})
|
|
1500
|
+
.strict();
|
|
1501
|
+
/**
|
|
1502
|
+
* Output shape for `monday doc block-create <doc-id> --type <t>
|
|
1503
|
+
* --content <json> [--after <bid>] [--parent <bid>]`. Direct unwrap
|
|
1504
|
+
* of the created DocumentBlock — `data: <DocumentBlock>` per cli-
|
|
1505
|
+
* design §6.1 single-record convention. The 9-field `DocumentBlock`
|
|
1506
|
+
* schema reused from {@link documentBlockSchema} (M32's `doc get`
|
|
1507
|
+
* blocks-hydration leg).
|
|
1508
|
+
*/
|
|
1509
|
+
export const docBlockCreateOutputSchema = documentBlockSchema;
|
|
1510
|
+
/**
|
|
1511
|
+
* Output shape for `monday doc block-update <block-id> --content
|
|
1512
|
+
* <json>`. Same 9-field `DocumentBlock` shape as block-create —
|
|
1513
|
+
* Monday's wire returns the updated block with content / type /
|
|
1514
|
+
* position / parent_block_id all reflecting post-update state.
|
|
1515
|
+
*/
|
|
1516
|
+
export const docBlockUpdateOutputSchema = documentBlockSchema;
|
|
1517
|
+
/**
|
|
1518
|
+
* Output shape for `monday doc block-delete <block-id> --yes`.
|
|
1519
|
+
* Single-field `{id: <echoed-block-id>}` projection from Monday's
|
|
1520
|
+
* `DocumentBlockIdOnly` wire return — agents key off the echoed id
|
|
1521
|
+
* for retry/idempotency reasoning. Envelope is intentionally
|
|
1522
|
+
* narrower than the create/update variants because Monday's
|
|
1523
|
+
* `delete_doc_block` wire only returns the id (NOT the full
|
|
1524
|
+
* pre-delete block); the agent contract doesn't gain from
|
|
1525
|
+
* speculatively rehydrating the block on the way out.
|
|
1526
|
+
*/
|
|
1527
|
+
export const docBlockDeleteOutputSchema = documentBlockIdOnlySchema;
|
|
1528
|
+
/**
|
|
1529
|
+
* GraphQL mutation document for `create_doc_block(doc_id, type,
|
|
1530
|
+
* content, after_block_id?, parent_block_id?) → DocumentBlock`.
|
|
1531
|
+
* Operation name pinned literally to `CreateDocBlock` (R-NEW-37
|
|
1532
|
+
* W2 audit-point — operationNames NOT caller-overridable). Wire
|
|
1533
|
+
* args are snake_case (Finding 7); variable names are camelCase
|
|
1534
|
+
* (TS convention).
|
|
1535
|
+
*
|
|
1536
|
+
* Selects every `DocumentBlock` field per the M32 probe. The 9-
|
|
1537
|
+
* field selection matches the `doc get` blocks-hydration leg so a
|
|
1538
|
+
* future create-then-get pipeline can compare envelopes byte-for-
|
|
1539
|
+
* byte.
|
|
1540
|
+
*/
|
|
1541
|
+
export const CREATE_DOC_BLOCK_MUTATION = `
|
|
1542
|
+
mutation CreateDocBlock(
|
|
1543
|
+
$docId: ID!,
|
|
1544
|
+
$type: DocBlockContentType!,
|
|
1545
|
+
$content: JSON!,
|
|
1546
|
+
$afterBlockId: String,
|
|
1547
|
+
$parentBlockId: String
|
|
1548
|
+
) {
|
|
1549
|
+
create_doc_block(
|
|
1550
|
+
doc_id: $docId,
|
|
1551
|
+
type: $type,
|
|
1552
|
+
content: $content,
|
|
1553
|
+
after_block_id: $afterBlockId,
|
|
1554
|
+
parent_block_id: $parentBlockId
|
|
1555
|
+
) {
|
|
1556
|
+
id
|
|
1557
|
+
type
|
|
1558
|
+
content
|
|
1559
|
+
position
|
|
1560
|
+
parent_block_id
|
|
1561
|
+
doc_id
|
|
1562
|
+
created_at
|
|
1563
|
+
created_by { id name }
|
|
1564
|
+
updated_at
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
`;
|
|
1568
|
+
/**
|
|
1569
|
+
* GraphQL mutation document for `update_doc_block(block_id,
|
|
1570
|
+
* content) → DocumentBlock`. Operation name pinned to
|
|
1571
|
+
* `UpdateDocBlock` (R-NEW-37 W2). Same 9-field `DocumentBlock`
|
|
1572
|
+
* selection as the create variant — Monday's wire returns the
|
|
1573
|
+
* full post-update block, not just a delta.
|
|
1574
|
+
*
|
|
1575
|
+
* No `--type` slot on `update_doc_block` (Monday's wire signature
|
|
1576
|
+
* has no `type` arg); agents that need to switch a block's content
|
|
1577
|
+
* type must `block-delete` + `block-create` (lossy: new id, new
|
|
1578
|
+
* position). The CLI surface mirrors the wire constraint exactly
|
|
1579
|
+
* — no client-side "change type" shim that papers over the
|
|
1580
|
+
* destructive recreate.
|
|
1581
|
+
*/
|
|
1582
|
+
export const UPDATE_DOC_BLOCK_MUTATION = `
|
|
1583
|
+
mutation UpdateDocBlock($blockId: String!, $content: JSON!) {
|
|
1584
|
+
update_doc_block(block_id: $blockId, content: $content) {
|
|
1585
|
+
id
|
|
1586
|
+
type
|
|
1587
|
+
content
|
|
1588
|
+
position
|
|
1589
|
+
parent_block_id
|
|
1590
|
+
doc_id
|
|
1591
|
+
created_at
|
|
1592
|
+
created_by { id name }
|
|
1593
|
+
updated_at
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
`;
|
|
1597
|
+
/**
|
|
1598
|
+
* GraphQL mutation document for `delete_doc_block(block_id) →
|
|
1599
|
+
* DocumentBlockIdOnly`. Operation name pinned to `DeleteDocBlock`
|
|
1600
|
+
* (R-NEW-37 W2). Single-field selection — Monday's wire return is
|
|
1601
|
+
* `DocumentBlockIdOnly` (`{id: String!}`).
|
|
1602
|
+
*
|
|
1603
|
+
* **Destructive-gate ordering.** The verb's action body MUST call
|
|
1604
|
+
* `enforceDestructiveGate` BEFORE this fetcher per the M10 round-1
|
|
1605
|
+
* P2 invariant. A missing `--yes` surfaces as
|
|
1606
|
+
* `confirmation_required` from the action layer, never masked by
|
|
1607
|
+
* `config_error` when no token is configured.
|
|
1608
|
+
*/
|
|
1609
|
+
export const DELETE_DOC_BLOCK_MUTATION = `
|
|
1610
|
+
mutation DeleteDocBlock($blockId: String!) {
|
|
1611
|
+
delete_doc_block(block_id: $blockId) {
|
|
1612
|
+
id
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
`;
|
|
1616
|
+
/**
|
|
1617
|
+
* Wrapping response schema for the `CreateDocBlock` mutation. The
|
|
1618
|
+
* `create_doc_block` slot widens to `unknown` so the wrapping
|
|
1619
|
+
* parse tolerates Monday's side-band debug keys (the `.loose()`
|
|
1620
|
+
* mode mirrors M27 / M32 / M34 / M35); the post-parse layer pins
|
|
1621
|
+
* the value against {@link documentBlockSchema} via
|
|
1622
|
+
* `unwrapOrThrow` so per-field drift surfaces with structured
|
|
1623
|
+
* `details.issues` (two-stage parse, mirrors M34 / M35 cadence).
|
|
1624
|
+
* Null payload semantics handled at the fetcher boundary (IMPL).
|
|
1625
|
+
*/
|
|
1626
|
+
const createDocBlockResponseSchema = z
|
|
1627
|
+
.object({
|
|
1628
|
+
create_doc_block: z.unknown(),
|
|
1629
|
+
})
|
|
1630
|
+
.loose();
|
|
1631
|
+
/**
|
|
1632
|
+
* Wrapping response schema for the `UpdateDocBlock` mutation. Same
|
|
1633
|
+
* shape as {@link createDocBlockResponseSchema} — Monday's wire
|
|
1634
|
+
* returns the updated `DocumentBlock` under the
|
|
1635
|
+
* `update_doc_block` root.
|
|
1636
|
+
*/
|
|
1637
|
+
const updateDocBlockResponseSchema = z
|
|
1638
|
+
.object({
|
|
1639
|
+
update_doc_block: z.unknown(),
|
|
1640
|
+
})
|
|
1641
|
+
.loose();
|
|
1642
|
+
/**
|
|
1643
|
+
* Wrapping response schema for the `DeleteDocBlock` mutation. The
|
|
1644
|
+
* `delete_doc_block` slot returns `DocumentBlockIdOnly` (`{id:
|
|
1645
|
+
* String!}`); the post-parse layer pins via
|
|
1646
|
+
* {@link documentBlockIdOnlySchema}. Null payload semantics
|
|
1647
|
+
* (treat as `not_found` per the standard delete cadence mirroring
|
|
1648
|
+
* M14 workspace-delete + M34 team-delete + M35 doc-delete) handled
|
|
1649
|
+
* at the fetcher boundary (IMPL).
|
|
1650
|
+
*/
|
|
1651
|
+
const deleteDocBlockResponseSchema = z
|
|
1652
|
+
.object({
|
|
1653
|
+
delete_doc_block: z.unknown(),
|
|
1654
|
+
})
|
|
1655
|
+
.loose();
|
|
1656
|
+
/**
|
|
1657
|
+
* Creates a per-doc rich-text block via `create_doc_block(doc_id,
|
|
1658
|
+
* type, content, after_block_id?, parent_block_id?)` with
|
|
1659
|
+
* `operationName: 'CreateDocBlock'` (R-NEW-37 W2). Two-stage parse
|
|
1660
|
+
* mirrors M34 / M35 cadence: the loose wrapping schema tolerates
|
|
1661
|
+
* Monday's side-band debug keys, then the inner OBJECT pins via
|
|
1662
|
+
* {@link documentBlockSchema} so per-field drift surfaces with
|
|
1663
|
+
* structured `details.issues`.
|
|
1664
|
+
*
|
|
1665
|
+
* Failure modes:
|
|
1666
|
+
*
|
|
1667
|
+
* - **Missing `create_doc_block` key** → `internal_error` via
|
|
1668
|
+
* `assertResponseFieldPresent` (Monday's response shape
|
|
1669
|
+
* regressed).
|
|
1670
|
+
* - **Present-but-null payload** → `internal_error` (a successful
|
|
1671
|
+
* `create_doc_block` mutation must return the created block per
|
|
1672
|
+
* Monday's documented contract — null indicates a wire-shape
|
|
1673
|
+
* regression worth surfacing loudly; distinct from
|
|
1674
|
+
* {@link updateDocBlock} / {@link deleteDocBlock} which treat
|
|
1675
|
+
* null as `not_found` per their probe-description-derived
|
|
1676
|
+
* contracts at R-v0.5-NEW-11).
|
|
1677
|
+
* - **Inner OBJECT drift** → `internal_error` via `unwrapOrThrow`
|
|
1678
|
+
* on {@link documentBlockSchema} (per-field shape regression).
|
|
1679
|
+
*
|
|
1680
|
+
* Omits `afterBlockId` / `parentBlockId` variables when unset so
|
|
1681
|
+
* Monday's wire-side defaults apply (the M34 / M35 omit-vs-null
|
|
1682
|
+
* discipline — undefined-keyed JSON serialises to a missing key,
|
|
1683
|
+
* NOT `null`).
|
|
1684
|
+
*/
|
|
1685
|
+
export const createDocBlock = async (inputs) => {
|
|
1686
|
+
const variables = {
|
|
1687
|
+
docId: inputs.docId,
|
|
1688
|
+
type: inputs.type,
|
|
1689
|
+
content: inputs.content,
|
|
1690
|
+
};
|
|
1691
|
+
if (inputs.afterBlockId !== undefined) {
|
|
1692
|
+
variables.afterBlockId = inputs.afterBlockId;
|
|
1693
|
+
}
|
|
1694
|
+
if (inputs.parentBlockId !== undefined) {
|
|
1695
|
+
variables.parentBlockId = inputs.parentBlockId;
|
|
1696
|
+
}
|
|
1697
|
+
const response = await inputs.client.raw(CREATE_DOC_BLOCK_MUTATION, variables, { operationName: 'CreateDocBlock' });
|
|
1698
|
+
const data = unwrapOrThrow(createDocBlockResponseSchema.safeParse(response.data), {
|
|
1699
|
+
context: 'Monday returned a malformed CreateDocBlock response',
|
|
1700
|
+
details: { doc_id: inputs.docId, type: inputs.type },
|
|
1701
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1702
|
+
'the response shape and update `createDocBlockResponseSchema` ' +
|
|
1703
|
+
'if Monday\'s contract has changed.',
|
|
1704
|
+
});
|
|
1705
|
+
assertResponseFieldPresent({
|
|
1706
|
+
data,
|
|
1707
|
+
key: 'create_doc_block',
|
|
1708
|
+
operationLabel: 'CreateDocBlock',
|
|
1709
|
+
details: { doc_id: inputs.docId, type: inputs.type },
|
|
1710
|
+
nullHandling: 'caller_handles',
|
|
1711
|
+
});
|
|
1712
|
+
const rawBlock = data.create_doc_block;
|
|
1713
|
+
if (rawBlock === null || rawBlock === undefined) {
|
|
1714
|
+
throw new ApiError('internal_error', `Monday returned no block payload from create_doc_block(doc_id: ${inputs.docId}, type: ${inputs.type}).`, { details: { doc_id: inputs.docId, type: inputs.type } });
|
|
1715
|
+
}
|
|
1716
|
+
const block = unwrapOrThrow(documentBlockSchema.safeParse(rawBlock), {
|
|
1717
|
+
context: `Monday returned a malformed DocumentBlock payload from create_doc_block (doc ${inputs.docId}, type ${inputs.type})`,
|
|
1718
|
+
details: { doc_id: inputs.docId, type: inputs.type },
|
|
1719
|
+
});
|
|
1720
|
+
return {
|
|
1721
|
+
block,
|
|
1722
|
+
source: 'live',
|
|
1723
|
+
cacheAgeSeconds: null,
|
|
1724
|
+
complexity: response.complexity,
|
|
1725
|
+
};
|
|
1726
|
+
};
|
|
1727
|
+
/**
|
|
1728
|
+
* Updates a per-doc rich-text block's content via
|
|
1729
|
+
* `update_doc_block(block_id, content)` with `operationName:
|
|
1730
|
+
* 'UpdateDocBlock'` (R-NEW-37 W2). No type-switching slot on the
|
|
1731
|
+
* wire — agents needing to change a block's content type
|
|
1732
|
+
* `block-delete` + `block-create` (lossy: new id, new position).
|
|
1733
|
+
*
|
|
1734
|
+
* Failure modes:
|
|
1735
|
+
*
|
|
1736
|
+
* - **Missing `update_doc_block` key** → `internal_error` via
|
|
1737
|
+
* `assertResponseFieldPresent` (schema drift).
|
|
1738
|
+
* - **Present-but-null payload** → `not_found` (the probe
|
|
1739
|
+
* description "Update a document block" promises the updated
|
|
1740
|
+
* block on success, so null reads as missing-record — block
|
|
1741
|
+
* bogus or not visible to the token; per R-v0.5-NEW-11
|
|
1742
|
+
* per-fetcher null-payload discipline).
|
|
1743
|
+
* - **Inner OBJECT drift** → `internal_error` via `unwrapOrThrow`
|
|
1744
|
+
* on {@link documentBlockSchema}.
|
|
1745
|
+
*/
|
|
1746
|
+
export const updateDocBlock = async (inputs) => {
|
|
1747
|
+
const response = await inputs.client.raw(UPDATE_DOC_BLOCK_MUTATION, { blockId: inputs.blockId, content: inputs.content }, { operationName: 'UpdateDocBlock' });
|
|
1748
|
+
const data = unwrapOrThrow(updateDocBlockResponseSchema.safeParse(response.data), {
|
|
1749
|
+
context: 'Monday returned a malformed UpdateDocBlock response',
|
|
1750
|
+
details: { block_id: inputs.blockId },
|
|
1751
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1752
|
+
'the response shape and update `updateDocBlockResponseSchema` ' +
|
|
1753
|
+
'if Monday\'s contract has changed.',
|
|
1754
|
+
});
|
|
1755
|
+
assertResponseFieldPresent({
|
|
1756
|
+
data,
|
|
1757
|
+
key: 'update_doc_block',
|
|
1758
|
+
operationLabel: 'UpdateDocBlock',
|
|
1759
|
+
details: { block_id: inputs.blockId },
|
|
1760
|
+
nullHandling: 'caller_handles',
|
|
1761
|
+
});
|
|
1762
|
+
const rawBlock = data.update_doc_block;
|
|
1763
|
+
if (rawBlock === null || rawBlock === undefined) {
|
|
1764
|
+
throw new ApiError('not_found', `Monday returned no block payload from update_doc_block for block ${inputs.blockId}`, { details: { block_id: inputs.blockId } });
|
|
1765
|
+
}
|
|
1766
|
+
const block = unwrapOrThrow(documentBlockSchema.safeParse(rawBlock), {
|
|
1767
|
+
context: `Monday returned a malformed DocumentBlock payload from update_doc_block (block ${inputs.blockId})`,
|
|
1768
|
+
details: { block_id: inputs.blockId },
|
|
1769
|
+
});
|
|
1770
|
+
return {
|
|
1771
|
+
block,
|
|
1772
|
+
source: 'live',
|
|
1773
|
+
cacheAgeSeconds: null,
|
|
1774
|
+
complexity: response.complexity,
|
|
1775
|
+
};
|
|
1776
|
+
};
|
|
1777
|
+
/**
|
|
1778
|
+
* Deletes a per-doc rich-text block via `delete_doc_block(block_id)`
|
|
1779
|
+
* with `operationName: 'DeleteDocBlock'` (R-NEW-37 W2). Wire return
|
|
1780
|
+
* is `DocumentBlockIdOnly` (`{id: String!}`).
|
|
1781
|
+
*
|
|
1782
|
+
* Failure modes:
|
|
1783
|
+
*
|
|
1784
|
+
* - **Missing `delete_doc_block` key** → `internal_error` via
|
|
1785
|
+
* `assertResponseFieldPresent` (schema drift).
|
|
1786
|
+
* - **Present-but-null payload** → `not_found` (id bogus or
|
|
1787
|
+
* block already deleted by a concurrent caller; mirrors M14
|
|
1788
|
+
* workspace-delete + M34 team-delete + M35 doc-delete cadence;
|
|
1789
|
+
* `delete_doc_block`'s probe description "Delete a document
|
|
1790
|
+
* block" promises confirmation on success).
|
|
1791
|
+
* - **Inner OBJECT drift** → `internal_error` via `unwrapOrThrow`
|
|
1792
|
+
* on {@link documentBlockIdOnlySchema}.
|
|
1793
|
+
*
|
|
1794
|
+
* The action body's destructive gate fires BEFORE this fetcher per
|
|
1795
|
+
* the M10 round-1 P2 invariant.
|
|
1796
|
+
*/
|
|
1797
|
+
export const deleteDocBlock = async (inputs) => {
|
|
1798
|
+
const response = await inputs.client.raw(DELETE_DOC_BLOCK_MUTATION, { blockId: inputs.blockId }, { operationName: 'DeleteDocBlock' });
|
|
1799
|
+
const data = unwrapOrThrow(deleteDocBlockResponseSchema.safeParse(response.data), {
|
|
1800
|
+
context: 'Monday returned a malformed DeleteDocBlock response',
|
|
1801
|
+
details: { block_id: inputs.blockId },
|
|
1802
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
1803
|
+
'the response shape and update `deleteDocBlockResponseSchema` ' +
|
|
1804
|
+
'if Monday\'s contract has changed.',
|
|
1805
|
+
});
|
|
1806
|
+
assertResponseFieldPresent({
|
|
1807
|
+
data,
|
|
1808
|
+
key: 'delete_doc_block',
|
|
1809
|
+
operationLabel: 'DeleteDocBlock',
|
|
1810
|
+
details: { block_id: inputs.blockId },
|
|
1811
|
+
nullHandling: 'caller_handles',
|
|
1812
|
+
});
|
|
1813
|
+
const rawBlock = data.delete_doc_block;
|
|
1814
|
+
if (rawBlock === null || rawBlock === undefined) {
|
|
1815
|
+
throw new ApiError('not_found', `Monday returned no payload from delete_doc_block for block ${inputs.blockId}`, { details: { block_id: inputs.blockId } });
|
|
1816
|
+
}
|
|
1817
|
+
const block = unwrapOrThrow(documentBlockIdOnlySchema.safeParse(rawBlock), {
|
|
1818
|
+
context: `Monday returned a malformed DocumentBlockIdOnly payload from delete_doc_block (block ${inputs.blockId})`,
|
|
1819
|
+
details: { block_id: inputs.blockId },
|
|
1820
|
+
});
|
|
1821
|
+
return {
|
|
1822
|
+
block,
|
|
1823
|
+
source: 'live',
|
|
1824
|
+
cacheAgeSeconds: null,
|
|
1825
|
+
complexity: response.complexity,
|
|
1826
|
+
};
|
|
1827
|
+
};
|
|
1828
|
+
// ===========================================================================
|
|
1829
|
+
// v0.5-M37 doc-content import mutation surface
|
|
1830
|
+
// ===========================================================================
|
|
1831
|
+
// Two fetchers land the doc-content import wire surface against Monday's
|
|
1832
|
+
// `import_doc_from_html` + `add_content_to_doc_from_markdown` mutations.
|
|
1833
|
+
// Each issues a single `client.raw` round-trip with a literal-pinned
|
|
1834
|
+
// `operationName` (R-NEW-37 W2 safely-by-construction); the response shape
|
|
1835
|
+
// is a custom `{ success!, doc_id? | block_ids?, error? }` OBJECT (NOT the
|
|
1836
|
+
// opaque-JSON M35 cadence and NOT the typed-OBJECT M36 cadence — distinct
|
|
1837
|
+
// third return shape on the doc-mutation surface).
|
|
1838
|
+
//
|
|
1839
|
+
// **Runtime bodies landed at v0.5-M37 IMPL** (two-stage parse + custom-
|
|
1840
|
+
// OBJECT projection + success/failure routing per D12 + integration test
|
|
1841
|
+
// cassettes covering all 5 D12 branches per fetcher).
|
|
1842
|
+
//
|
|
1843
|
+
// Wire summary (empirical probe at `scripts/probe/v0.5-doc-mutations.ts` +
|
|
1844
|
+
// `v0.5-inputs-and-results.ts`, 2026-05-15, API `2026-01`):
|
|
1845
|
+
//
|
|
1846
|
+
// - `import_doc_from_html(html: String!, workspaceId: ID!,
|
|
1847
|
+
// kind: DocKind, folderId: ID, title: String) →
|
|
1848
|
+
// ImportDocFromHtmlResult` (NON_NULL/OBJECT with 3 fields:
|
|
1849
|
+
// `success: Boolean!`, `doc_id: String` (nullable), `error: String`
|
|
1850
|
+
// (nullable)). Operation name `ImportDocFromHtml` pinned literally
|
|
1851
|
+
// per R-NEW-37 W2.
|
|
1852
|
+
// - `add_content_to_doc_from_markdown(docId: ID!, markdown: String!,
|
|
1853
|
+
// afterBlockId: String) → DocBlocksFromMarkdownResult` (NON_NULL/
|
|
1854
|
+
// OBJECT with 3 fields: `success: Boolean!`, `block_ids:
|
|
1855
|
+
// [String!]` (nullable list of non-null strings), `error: String`
|
|
1856
|
+
// (nullable)). Operation name `AddContentToDocFromMarkdown` pinned.
|
|
1857
|
+
//
|
|
1858
|
+
// **camelCase wire arg names (Finding 7).** Both M37 mutations use
|
|
1859
|
+
// camelCase (`workspaceId`, `folderId`, `docId`, `afterBlockId`) on
|
|
1860
|
+
// Monday's wire — same asymmetry as M35's `update_doc_name` /
|
|
1861
|
+
// `delete_doc` / `duplicate_doc` (NOT the M36 snake_case symmetric
|
|
1862
|
+
// path). The GraphQL operation documents below use camelCase variable
|
|
1863
|
+
// names + camelCase wire arg names verbatim; CLI argv stays kebab-case
|
|
1864
|
+
// throughout (`--workspace <wid>`, `--folder <fid>`, `--after <bid>`);
|
|
1865
|
+
// error envelope `details.*` keys stay snake_case per cli-design §6.5
|
|
1866
|
+
// (`details.workspace_id`, `details.doc_id`). Documented as the 5th
|
|
1867
|
+
// supporting site for R-NEW-41 at the canonical `docs/architecture.md`
|
|
1868
|
+
// "Wire-vs-CLI semantics documentation conventions" section.
|
|
1869
|
+
//
|
|
1870
|
+
// **Custom-OBJECT projection contract (D12 closure).** Distinct third
|
|
1871
|
+
// return-shape on Monday's doc-mutation surface:
|
|
1872
|
+
//
|
|
1873
|
+
// - M35 `create_doc` returns full Document OBJECT (typed schema).
|
|
1874
|
+
// - M35 `update_doc_name` / `delete_doc` / `duplicate_doc` return
|
|
1875
|
+
// opaque `JSON` scalar (D9 projection to `{doc_id, success: true}`).
|
|
1876
|
+
// - M36 `create_doc_block` / `update_doc_block` return full
|
|
1877
|
+
// DocumentBlock OBJECT (typed schema); `delete_doc_block` returns
|
|
1878
|
+
// DocumentBlockIdOnly OBJECT.
|
|
1879
|
+
// - **M37 `import_doc_from_html` / `add_content_to_doc_from_markdown`
|
|
1880
|
+
// return a custom 3-field result OBJECT carrying a `success`
|
|
1881
|
+
// discriminator** + an optional payload (`doc_id` / `block_ids`) +
|
|
1882
|
+
// an optional `error` string (wire-side detail when `success:
|
|
1883
|
+
// false`).
|
|
1884
|
+
//
|
|
1885
|
+
// Per D12 closure, the fetcher projects the custom OBJECT into the
|
|
1886
|
+
// flat `{ doc_id, success: true }` (import-html) / `{ doc_id (echoed),
|
|
1887
|
+
// block_ids, success: true }` (append-markdown) envelope on the success
|
|
1888
|
+
// path, and throws typed `ApiError`s on failure:
|
|
1889
|
+
//
|
|
1890
|
+
// - **`success: true + payload present`** → success envelope. The
|
|
1891
|
+
// `data.success: true` slot pins literal-`true` so agents key off
|
|
1892
|
+
// it for retry/idempotency reasoning across the doc-mutation
|
|
1893
|
+
// surface uniformly (mirrors M35's `{doc_id, success: true}` cadence).
|
|
1894
|
+
// - **`success: false + populated `error`** → throw
|
|
1895
|
+
// `validation_failed` with `details.error: <wire-error-message>`.
|
|
1896
|
+
// Monday's `error` field carries the user-visible rejection reason
|
|
1897
|
+
// (invalid HTML, payload too large, markdown parse failure, etc.);
|
|
1898
|
+
// `validation_failed` is the right typed code because the wire-
|
|
1899
|
+
// side rejection is structural, NOT a transport or permission
|
|
1900
|
+
// error.
|
|
1901
|
+
// - **`success: false + empty/null `error`** → throw
|
|
1902
|
+
// `internal_error` with a wire-regression hint. Monday's contract
|
|
1903
|
+
// is "`error` is detailed message when `success: false`"; missing
|
|
1904
|
+
// `error` on a failed mutation indicates the wire shape regressed
|
|
1905
|
+
// (the field went missing OR Monday returned `success: false`
|
|
1906
|
+
// without populating the documented diagnostic slot).
|
|
1907
|
+
// - **`success: true + missing payload`** → throw `internal_error`
|
|
1908
|
+
// (Monday promises the new `doc_id` / `block_ids` on success).
|
|
1909
|
+
//
|
|
1910
|
+
// The exact per-fetcher null-payload contract decisions (per
|
|
1911
|
+
// R-v0.5-NEW-11 graduation candidate at 3rd supporting instance) are
|
|
1912
|
+
// derived from each mutation's probe-description return-shape promise:
|
|
1913
|
+
//
|
|
1914
|
+
// - `import_doc_from_html` probe description: "Returns the ID of
|
|
1915
|
+
// the newly created document on success" → null `doc_id` on
|
|
1916
|
+
// `success: true` IS a wire-regression signal (`internal_error`).
|
|
1917
|
+
// - `add_content_to_doc_from_markdown` probe description: "Returns
|
|
1918
|
+
// the IDs of the newly created blocks on success" → null
|
|
1919
|
+
// `block_ids` on `success: true` IS a wire-regression signal
|
|
1920
|
+
// (`internal_error`). An EMPTY (`[]`) `block_ids` array on
|
|
1921
|
+
// success is plausible for non-empty markdown that Monday's
|
|
1922
|
+
// parser collapses to zero structural blocks (comments-only,
|
|
1923
|
+
// whitespace post-parse, etc.); the schema accepts empty arrays
|
|
1924
|
+
// as success. Empty / whitespace-only `--markdown-string` is
|
|
1925
|
+
// rejected at the parse boundary so it can never reach the wire.
|
|
1926
|
+
//
|
|
1927
|
+
// **D13 closure: empirical wire-side payload-size threshold.** The
|
|
1928
|
+
// pre-flight `scripts/probe/v0.5-m37-size-limits.ts` probe (2026-05-17,
|
|
1929
|
+
// API `2026-01`) pinned an empirical wire-side rejection threshold
|
|
1930
|
+
// between **250KB (last-known-good) and 500KB (first-known-rejected)**
|
|
1931
|
+
// on BOTH `import_doc_from_html` (`html`) and
|
|
1932
|
+
// `add_content_to_doc_from_markdown` (`markdown`) slots — identical
|
|
1933
|
+
// threshold on both surfaces, consistent with a transport-layer
|
|
1934
|
+
// request-size cap (NOT a per-mutation schema constraint).
|
|
1935
|
+
//
|
|
1936
|
+
// **Wire rejection shape is unusual.** Oversized payloads do NOT
|
|
1937
|
+
// surface via the documented `{ success: false, error: '...' }`
|
|
1938
|
+
// envelope path. Instead, Monday's transport layer rejects the
|
|
1939
|
+
// request BEFORE the schema layer can return the typed envelope,
|
|
1940
|
+
// surfacing a generic GraphQL `errors[]` payload:
|
|
1941
|
+
//
|
|
1942
|
+
// ```
|
|
1943
|
+
// errors: [{ message: 'Internal Server Error',
|
|
1944
|
+
// extensions: { code: 'INTERNAL_SERVER_ERROR' } }]
|
|
1945
|
+
// ```
|
|
1946
|
+
//
|
|
1947
|
+
// This routes through the standard transport-layer `mapMondayError`
|
|
1948
|
+
// path and lands as `internal_error` — misleading for what is
|
|
1949
|
+
// fundamentally a payload-size issue. The CLI pre-empts this by
|
|
1950
|
+
// rejecting oversized `--html-string` / `--markdown-string` payloads
|
|
1951
|
+
// at the argv-parse boundary via {@link MAX_DOC_IMPORT_PAYLOAD_BYTES}
|
|
1952
|
+
// (the parse-time string-length `.refine()` fires `usage_error.
|
|
1953
|
+
// details.issues[{path: 'htmlString'|'markdownString', message:
|
|
1954
|
+
// '--html-string exceeds the 256000-byte wire-side limit ...'}]`
|
|
1955
|
+
// ahead of any wire dispatch — standard zod-issues envelope shape
|
|
1956
|
+
// emitted by `parseArgv`). The file-read path (`--html <file>` /
|
|
1957
|
+
// `--markdown <file>`) lands the same size guard at the runtime read
|
|
1958
|
+
// boundary (IMPL).
|
|
1959
|
+
//
|
|
1960
|
+
// **No new ERROR_CODES at M37.** Existing codes route doc-content-
|
|
1961
|
+
// import failures: `usage_error` (argv-parse rejections — mutual-
|
|
1962
|
+
// exclusion of file vs string source, empty payload, oversized
|
|
1963
|
+
// `--html-string` / `--markdown-string`), `validation_failed`
|
|
1964
|
+
// (Monday-side rejection via `success: false + error`), `internal_
|
|
1965
|
+
// error` (wire-shape regression OR `INTERNAL_SERVER_ERROR` for an
|
|
1966
|
+
// oversized payload that slipped past the parse-boundary check),
|
|
1967
|
+
// `not_found` (`append-markdown` against a non-existent or
|
|
1968
|
+
// inaccessible `<doc-id>`), `forbidden` / `unauthorized` (token
|
|
1969
|
+
// lacks workdoc-write scope).
|
|
1970
|
+
//
|
|
1971
|
+
// **0 destructive verbs at M37.** Both `doc import-html` + `doc
|
|
1972
|
+
// append-markdown` are content-creation surfaces — no `--yes`
|
|
1973
|
+
// destructive gate fires (M10 round-1 P2 invariant doesn't apply).
|
|
1974
|
+
//
|
|
1975
|
+
// **R-NEW-76 discipline preserved post-IMPL.** Both action bodies
|
|
1976
|
+
// run `parseArgv` BEFORE `resolveClient` so invalid argv surfaces
|
|
1977
|
+
// `usage_error` from the parse boundary, NOT `config_error` from
|
|
1978
|
+
// `loadConfig` when no token is configured.
|
|
1979
|
+
/**
|
|
1980
|
+
* Maximum byte-size for the inline `--html-string` / `--markdown-string`
|
|
1981
|
+
* payload at the argv-parse boundary (empirical probe 2026-05-17, API
|
|
1982
|
+
* `2026-01`; `scripts/probe/v0.5-m37-size-limits.report.txt`). Set
|
|
1983
|
+
* conservatively at the LAST-KNOWN-GOOD probe size (250KB) — the wire's
|
|
1984
|
+
* first-known-rejection sits at 500KB, so 250KB leaves the agent contract
|
|
1985
|
+
* comfortably below the threshold without leaving room for the wire-side
|
|
1986
|
+
* `INTERNAL_SERVER_ERROR` surprise.
|
|
1987
|
+
*
|
|
1988
|
+
* Bytes (NOT UTF-16 code units) — the parse-boundary check measures the
|
|
1989
|
+
* payload via `Buffer.byteLength(payload, 'utf8')` so multi-byte
|
|
1990
|
+
* characters in the payload count toward the limit the same way Monday's
|
|
1991
|
+
* transport layer sees them.
|
|
1992
|
+
*
|
|
1993
|
+
* Raising this constant is a contract bump: any agent that relied on
|
|
1994
|
+
* being rejected at the old limit might now silently succeed with a
|
|
1995
|
+
* larger payload (additive change). Lowering it is a tighter rejection
|
|
1996
|
+
* surface (potentially breaking — fold into a Codex review at the v0.6+
|
|
1997
|
+
* pre-flight that touches doc-content import). Future API versions may
|
|
1998
|
+
* shift Monday's wire threshold; re-running the probe at any v0.5.x or
|
|
1999
|
+
* v0.6 pre-flight that re-opens this surface pins the new threshold.
|
|
2000
|
+
*/
|
|
2001
|
+
export const MAX_DOC_IMPORT_PAYLOAD_BYTES = 256_000;
|
|
2002
|
+
/**
|
|
2003
|
+
* Inner OBJECT schema for `import_doc_from_html`'s
|
|
2004
|
+
* `ImportDocFromHtmlResult` wire return shape — 3 fields per the
|
|
2005
|
+
* v0.5 introspection probe (`scripts/probe/v0.5-inputs-and-results.
|
|
2006
|
+
* report.txt`):
|
|
2007
|
+
*
|
|
2008
|
+
* - `success: Boolean!` — discriminator; pinned NON_NULL per
|
|
2009
|
+
* introspection.
|
|
2010
|
+
* - `doc_id: String` — nullable; populated on `success: true`,
|
|
2011
|
+
* null on `success: false` per Monday's documented contract.
|
|
2012
|
+
* Note Monday's wire types this slot as `String` (NOT `ID`!)
|
|
2013
|
+
* despite the value being a doc-id-shaped reference. The CLI
|
|
2014
|
+
* surfaces it as a brand-untyped string at the wire layer; the
|
|
2015
|
+
* CLI projection envelope re-brands via {@link DocIdSchema} on
|
|
2016
|
+
* the way out so agents read a `DocId`-shaped slot.
|
|
2017
|
+
* - `error: String` — nullable; populated on `success: false`,
|
|
2018
|
+
* null on `success: true`. Wire-side rejection reason (invalid
|
|
2019
|
+
* HTML, payload-too-large, etc.).
|
|
2020
|
+
*
|
|
2021
|
+
* `.strict()` mode — Monday's wire OBJECT shape is documented at
|
|
2022
|
+
* exactly 3 fields; an unknown key would indicate wire-shape
|
|
2023
|
+
* regression worth catching loudly (the wrapping `loose()` schema
|
|
2024
|
+
* tolerates side-band debug keys at the response root, not at the
|
|
2025
|
+
* inner OBJECT layer).
|
|
2026
|
+
*/
|
|
2027
|
+
export const importDocFromHtmlResultSchema = z
|
|
2028
|
+
.object({
|
|
2029
|
+
success: z.boolean(),
|
|
2030
|
+
doc_id: z.string().min(1).nullable(),
|
|
2031
|
+
error: z.string().nullable(),
|
|
2032
|
+
})
|
|
2033
|
+
.strict();
|
|
2034
|
+
/**
|
|
2035
|
+
* Inner OBJECT schema for `add_content_to_doc_from_markdown`'s
|
|
2036
|
+
* `DocBlocksFromMarkdownResult` wire return shape — 3 fields per the
|
|
2037
|
+
* v0.5 introspection probe:
|
|
2038
|
+
*
|
|
2039
|
+
* - `success: Boolean!` — discriminator; NON_NULL.
|
|
2040
|
+
* - `block_ids: [String!]` — nullable LIST of NON_NULL strings;
|
|
2041
|
+
* populated on `success: true` with the newly-created block
|
|
2042
|
+
* ids (one per parsed markdown block); null on `success:
|
|
2043
|
+
* false`. EMPTY array on success is plausible for non-empty
|
|
2044
|
+
* markdown that Monday parses to zero convertible blocks (e.g.
|
|
2045
|
+
* comments-only input); the schema accepts empty. Empty /
|
|
2046
|
+
* whitespace-only `--markdown-string` rejects at the parse /
|
|
2047
|
+
* read boundary as `usage_error` and never reaches the wire.
|
|
2048
|
+
* - `error: String` — nullable; populated on `success: false`,
|
|
2049
|
+
* null on `success: true`.
|
|
2050
|
+
*
|
|
2051
|
+
* `.strict()` mode for the same reason as
|
|
2052
|
+
* {@link importDocFromHtmlResultSchema}.
|
|
2053
|
+
*/
|
|
2054
|
+
export const docBlocksFromMarkdownResultSchema = z
|
|
2055
|
+
.object({
|
|
2056
|
+
success: z.boolean(),
|
|
2057
|
+
block_ids: z.array(z.string().min(1)).nullable(),
|
|
2058
|
+
error: z.string().nullable(),
|
|
2059
|
+
})
|
|
2060
|
+
.strict();
|
|
2061
|
+
/**
|
|
2062
|
+
* CLI projection envelope for `monday doc import-html` — flat
|
|
2063
|
+
* `{ doc_id, success: true }` shape mirroring M35's
|
|
2064
|
+
* {@link docMutationResultSchema} cadence so agents read a uniform
|
|
2065
|
+
* `{ doc_id, success }` shape across every doc-mutation success
|
|
2066
|
+
* envelope (rename / delete / duplicate / import-html). `doc_id`
|
|
2067
|
+
* carries the **NEWLY-CREATED** doc's id; `success` is literal-
|
|
2068
|
+
* `true` because failure paths throw typed `ApiError`s
|
|
2069
|
+
* (`validation_failed` for `success: false + error` per D12 / null
|
|
2070
|
+
* for wire regression) BEFORE this projection schema is referenced.
|
|
2071
|
+
*
|
|
2072
|
+
* No `error` slot — failures don't reach this envelope. Agents read
|
|
2073
|
+
* `error.code` + `error.details.error` from the failure envelope
|
|
2074
|
+
* instead.
|
|
2075
|
+
*/
|
|
2076
|
+
export const docImportHtmlOutputSchema = z
|
|
2077
|
+
.object({
|
|
2078
|
+
doc_id: z.string().min(1),
|
|
2079
|
+
success: z.literal(true),
|
|
2080
|
+
})
|
|
2081
|
+
.strict();
|
|
2082
|
+
/**
|
|
2083
|
+
* CLI projection envelope for `monday doc append-markdown` —
|
|
2084
|
+
* `{ doc_id (echoed input), block_ids, success: true }`. Echoes
|
|
2085
|
+
* the input source-doc id so agents that pipe the append envelope
|
|
2086
|
+
* into a follow-up call (e.g. a `monday doc get <doc-id>` to verify
|
|
2087
|
+
* post-append state) have the parent doc context inline.
|
|
2088
|
+
* `block_ids` carries the wire's full list of NEWLY-CREATED block
|
|
2089
|
+
* ids (one per parsed markdown block) preserved in markdown-source
|
|
2090
|
+
* order. Empty array IS a valid success shape for non-empty markdown
|
|
2091
|
+
* that Monday parses to zero convertible blocks (e.g. comments-only
|
|
2092
|
+
* input); empty / whitespace-only `--markdown-string` rejects at the
|
|
2093
|
+
* parse / read boundary as `usage_error` and never reaches the wire.
|
|
2094
|
+
*
|
|
2095
|
+
* `success` is literal-`true` for the same reason as
|
|
2096
|
+
* {@link docImportHtmlOutputSchema}.
|
|
2097
|
+
*/
|
|
2098
|
+
export const docAppendMarkdownOutputSchema = z
|
|
2099
|
+
.object({
|
|
2100
|
+
doc_id: z.string().min(1),
|
|
2101
|
+
block_ids: z.array(z.string().min(1)),
|
|
2102
|
+
success: z.literal(true),
|
|
2103
|
+
})
|
|
2104
|
+
.strict();
|
|
2105
|
+
/**
|
|
2106
|
+
* GraphQL mutation document for `import_doc_from_html(html,
|
|
2107
|
+
* workspaceId, kind?, folderId?, title?) → ImportDocFromHtmlResult`.
|
|
2108
|
+
* Operation name pinned literally to `ImportDocFromHtml` (R-NEW-37
|
|
2109
|
+
* W2 audit-point — operationNames NOT caller-overridable). All wire
|
|
2110
|
+
* args are camelCase (Finding 7); variable names match the wire
|
|
2111
|
+
* shape verbatim.
|
|
2112
|
+
*
|
|
2113
|
+
* Selects every `ImportDocFromHtmlResult` field per the introspection
|
|
2114
|
+
* probe — the 3-field selection lets the fetcher project the
|
|
2115
|
+
* discriminator + payload + error in a single round-trip.
|
|
2116
|
+
*/
|
|
2117
|
+
export const IMPORT_DOC_FROM_HTML_MUTATION = `
|
|
2118
|
+
mutation ImportDocFromHtml(
|
|
2119
|
+
$html: String!,
|
|
2120
|
+
$workspaceId: ID!,
|
|
2121
|
+
$kind: DocKind,
|
|
2122
|
+
$folderId: ID,
|
|
2123
|
+
$title: String
|
|
2124
|
+
) {
|
|
2125
|
+
import_doc_from_html(
|
|
2126
|
+
html: $html,
|
|
2127
|
+
workspaceId: $workspaceId,
|
|
2128
|
+
kind: $kind,
|
|
2129
|
+
folderId: $folderId,
|
|
2130
|
+
title: $title
|
|
2131
|
+
) {
|
|
2132
|
+
success
|
|
2133
|
+
doc_id
|
|
2134
|
+
error
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
`;
|
|
2138
|
+
/**
|
|
2139
|
+
* GraphQL mutation document for `add_content_to_doc_from_markdown(
|
|
2140
|
+
* docId, markdown, afterBlockId?) → DocBlocksFromMarkdownResult`.
|
|
2141
|
+
* Operation name pinned to `AddContentToDocFromMarkdown` (R-NEW-37
|
|
2142
|
+
* W2). camelCase wire arg names (Finding 7).
|
|
2143
|
+
*
|
|
2144
|
+
* `afterBlockId` is the optional opaque block-id positional anchor —
|
|
2145
|
+
* Monday inserts the parsed markdown blocks immediately after the
|
|
2146
|
+
* named block; absent → blocks land at the document tail (append-end
|
|
2147
|
+
* semantics, NOT append-head; Monday's probe description: "Use this
|
|
2148
|
+
* to append content to the end of a document or insert content after
|
|
2149
|
+
* a specific block").
|
|
2150
|
+
*/
|
|
2151
|
+
export const ADD_CONTENT_TO_DOC_FROM_MARKDOWN_MUTATION = `
|
|
2152
|
+
mutation AddContentToDocFromMarkdown(
|
|
2153
|
+
$docId: ID!,
|
|
2154
|
+
$markdown: String!,
|
|
2155
|
+
$afterBlockId: String
|
|
2156
|
+
) {
|
|
2157
|
+
add_content_to_doc_from_markdown(
|
|
2158
|
+
docId: $docId,
|
|
2159
|
+
markdown: $markdown,
|
|
2160
|
+
afterBlockId: $afterBlockId
|
|
2161
|
+
) {
|
|
2162
|
+
success
|
|
2163
|
+
block_ids
|
|
2164
|
+
error
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
`;
|
|
2168
|
+
/**
|
|
2169
|
+
* Wrapping response schema for the `ImportDocFromHtml` mutation. The
|
|
2170
|
+
* `import_doc_from_html` slot widens to `unknown` so the wrapping
|
|
2171
|
+
* parse tolerates Monday's side-band debug keys (the `.loose()` mode
|
|
2172
|
+
* mirrors M27 / M32 / M34 / M35 / M36); the post-parse layer pins
|
|
2173
|
+
* the value against {@link importDocFromHtmlResultSchema} via
|
|
2174
|
+
* `unwrapOrThrow` so per-field drift surfaces with structured
|
|
2175
|
+
* `details.issues` (two-stage parse, mirrors M34 / M35 / M36 cadence).
|
|
2176
|
+
*/
|
|
2177
|
+
const importDocFromHtmlResponseSchema = z
|
|
2178
|
+
.object({
|
|
2179
|
+
import_doc_from_html: z.unknown(),
|
|
2180
|
+
})
|
|
2181
|
+
.loose();
|
|
2182
|
+
/**
|
|
2183
|
+
* Wrapping response schema for the `AddContentToDocFromMarkdown`
|
|
2184
|
+
* mutation. Same shape as {@link importDocFromHtmlResponseSchema} —
|
|
2185
|
+
* loose root tolerance + strict inner OBJECT pin at the post-parse
|
|
2186
|
+
* layer.
|
|
2187
|
+
*/
|
|
2188
|
+
const addContentToDocFromMarkdownResponseSchema = z
|
|
2189
|
+
.object({
|
|
2190
|
+
add_content_to_doc_from_markdown: z.unknown(),
|
|
2191
|
+
})
|
|
2192
|
+
.loose();
|
|
2193
|
+
/**
|
|
2194
|
+
* Imports an HTML payload as a new workdoc via
|
|
2195
|
+
* `import_doc_from_html(html, workspaceId, kind?, folderId?, title?)`
|
|
2196
|
+
* with `operationName: 'ImportDocFromHtml'` (R-NEW-37 W2 — pinned
|
|
2197
|
+
* literally at the call site; NOT a caller-overridable input slot).
|
|
2198
|
+
*
|
|
2199
|
+
* Two-stage parse: the loose {@link importDocFromHtmlResponseSchema}
|
|
2200
|
+
* wrapping schema tolerates side-band keys at the response root;
|
|
2201
|
+
* `assertResponseFieldPresent` rewraps a missing key as
|
|
2202
|
+
* `internal_error`; the inner OBJECT pins via
|
|
2203
|
+
* {@link importDocFromHtmlResultSchema} (`.strict()` — drift surfaces
|
|
2204
|
+
* `internal_error` with structured `details.issues`).
|
|
2205
|
+
*
|
|
2206
|
+
* Custom-OBJECT projection per D12 (5 branches):
|
|
2207
|
+
*
|
|
2208
|
+
* - `success: true + doc_id present` → flat envelope
|
|
2209
|
+
* `{doc_id, success: true}` (mirrors M35
|
|
2210
|
+
* {@link docMutationResultSchema} cadence).
|
|
2211
|
+
* - `success: false + populated error` → throw `validation_failed`
|
|
2212
|
+
* with `details: {workspace_id, error, hint}` (Monday-side
|
|
2213
|
+
* rejection — invalid HTML, payload too large, etc.).
|
|
2214
|
+
* - `success: false + empty/null error` → throw `internal_error`
|
|
2215
|
+
* with a wire-regression hint (Monday's contract: `error` is
|
|
2216
|
+
* populated when `success: false`).
|
|
2217
|
+
* - `success: true + missing doc_id` → throw `internal_error`
|
|
2218
|
+
* (per-fetcher null-payload contract derived from Monday's probe
|
|
2219
|
+
* description "Returns the ID of the newly created document on
|
|
2220
|
+
* success" per R-v0.5-NEW-11).
|
|
2221
|
+
* - `success: true + null doc_id` → same as missing — wire-side
|
|
2222
|
+
* regression on the documented payload promise.
|
|
2223
|
+
*
|
|
2224
|
+
* Omits optional variables (`kind` / `folderId` / `title`) entirely
|
|
2225
|
+
* when unset so Monday's per-arg server-side defaults apply (the M34 /
|
|
2226
|
+
* M35 / M36 omit-vs-null discipline — undefined-keyed JSON serialises
|
|
2227
|
+
* to a missing key, NOT `null`).
|
|
2228
|
+
*/
|
|
2229
|
+
export const importDocFromHtml = async (inputs) => {
|
|
2230
|
+
const variables = {
|
|
2231
|
+
html: inputs.html,
|
|
2232
|
+
workspaceId: inputs.workspaceId,
|
|
2233
|
+
};
|
|
2234
|
+
if (inputs.kind !== undefined) {
|
|
2235
|
+
variables.kind = inputs.kind;
|
|
2236
|
+
}
|
|
2237
|
+
if (inputs.folderId !== undefined) {
|
|
2238
|
+
variables.folderId = inputs.folderId;
|
|
2239
|
+
}
|
|
2240
|
+
if (inputs.title !== undefined) {
|
|
2241
|
+
variables.title = inputs.title;
|
|
2242
|
+
}
|
|
2243
|
+
const response = await inputs.client.raw(IMPORT_DOC_FROM_HTML_MUTATION, variables, { operationName: 'ImportDocFromHtml' });
|
|
2244
|
+
const data = unwrapOrThrow(importDocFromHtmlResponseSchema.safeParse(response.data), {
|
|
2245
|
+
context: 'Monday returned a malformed ImportDocFromHtml response',
|
|
2246
|
+
details: { workspace_id: inputs.workspaceId },
|
|
2247
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
2248
|
+
'the response shape and update `importDocFromHtmlResponseSchema` ' +
|
|
2249
|
+
'if Monday\'s contract has changed.',
|
|
2250
|
+
});
|
|
2251
|
+
assertResponseFieldPresent({
|
|
2252
|
+
data,
|
|
2253
|
+
key: 'import_doc_from_html',
|
|
2254
|
+
operationLabel: 'ImportDocFromHtml',
|
|
2255
|
+
details: { workspace_id: inputs.workspaceId },
|
|
2256
|
+
nullHandling: 'caller_handles',
|
|
2257
|
+
});
|
|
2258
|
+
const rawResult = data.import_doc_from_html;
|
|
2259
|
+
if (rawResult === null || rawResult === undefined) {
|
|
2260
|
+
throw new ApiError('internal_error', `Monday returned no payload from import_doc_from_html for workspace ${inputs.workspaceId}.`, { details: { workspace_id: inputs.workspaceId } });
|
|
2261
|
+
}
|
|
2262
|
+
const parsed = unwrapOrThrow(importDocFromHtmlResultSchema.safeParse(rawResult), {
|
|
2263
|
+
context: `Monday returned a malformed ImportDocFromHtmlResult payload from import_doc_from_html (workspace ${inputs.workspaceId})`,
|
|
2264
|
+
details: { workspace_id: inputs.workspaceId },
|
|
2265
|
+
});
|
|
2266
|
+
if (!parsed.success) {
|
|
2267
|
+
if (parsed.error !== null && parsed.error.length > 0) {
|
|
2268
|
+
throw new ApiError('validation_failed', `Monday rejected import_doc_from_html: ${parsed.error}`, {
|
|
2269
|
+
details: {
|
|
2270
|
+
workspace_id: inputs.workspaceId,
|
|
2271
|
+
error: parsed.error,
|
|
2272
|
+
hint: 'Monday\'s wire-side rejection reason is verbatim in `error`; ' +
|
|
2273
|
+
'inspect it for invalid HTML, payload-size, or permission issues.',
|
|
2274
|
+
},
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
throw new ApiError('internal_error', `Monday returned success: false from import_doc_from_html without a populated error message (workspace ${inputs.workspaceId}).`, {
|
|
2278
|
+
details: {
|
|
2279
|
+
workspace_id: inputs.workspaceId,
|
|
2280
|
+
hint: 'wire-shape regression — Monday\'s contract is that `error` is ' +
|
|
2281
|
+
'a non-empty string when `success: false`. Re-probe via ' +
|
|
2282
|
+
'`scripts/probe/v0.5-doc-mutations.ts` if this persists.',
|
|
2283
|
+
},
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
if (parsed.doc_id === null) {
|
|
2287
|
+
throw new ApiError('internal_error', `Monday returned success: true from import_doc_from_html but a null doc_id (workspace ${inputs.workspaceId}).`, {
|
|
2288
|
+
details: {
|
|
2289
|
+
workspace_id: inputs.workspaceId,
|
|
2290
|
+
hint: 'wire-shape regression — Monday\'s probe description promises a ' +
|
|
2291
|
+
'non-null `doc_id` on success. Re-probe via `scripts/probe/v0.5-' +
|
|
2292
|
+
'doc-mutations.ts` if this persists.',
|
|
2293
|
+
},
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
return {
|
|
2297
|
+
result: { doc_id: parsed.doc_id, success: true },
|
|
2298
|
+
source: 'live',
|
|
2299
|
+
cacheAgeSeconds: null,
|
|
2300
|
+
complexity: response.complexity,
|
|
2301
|
+
};
|
|
2302
|
+
};
|
|
2303
|
+
/**
|
|
2304
|
+
* Appends parsed-markdown blocks to an existing workdoc via
|
|
2305
|
+
* `add_content_to_doc_from_markdown(docId, markdown, afterBlockId?)`
|
|
2306
|
+
* with `operationName: 'AddContentToDocFromMarkdown'` (R-NEW-37 W2).
|
|
2307
|
+
*
|
|
2308
|
+
* Two-stage parse: the loose
|
|
2309
|
+
* {@link addContentToDocFromMarkdownResponseSchema} wrapping schema
|
|
2310
|
+
* tolerates side-band keys; `assertResponseFieldPresent` rewraps a
|
|
2311
|
+
* missing key as `internal_error`; the inner OBJECT pins via
|
|
2312
|
+
* {@link docBlocksFromMarkdownResultSchema}.
|
|
2313
|
+
*
|
|
2314
|
+
* Custom-OBJECT projection per D12 (5 branches):
|
|
2315
|
+
*
|
|
2316
|
+
* - `success: true + block_ids present (incl. EMPTY array)` →
|
|
2317
|
+
* success envelope `{doc_id (echoed), block_ids, success: true}`.
|
|
2318
|
+
* Empty `block_ids: []` IS a valid success shape per probe
|
|
2319
|
+
* finding — for non-empty markdown that Monday parses to zero
|
|
2320
|
+
* convertible blocks (e.g. comments-only payload Monday
|
|
2321
|
+
* collapses to zero structural blocks). Empty / whitespace-only
|
|
2322
|
+
* input is rejected at the parse / read boundary by
|
|
2323
|
+
* {@link readSourceContent}, so it can never reach this fetcher.
|
|
2324
|
+
* - `success: false + populated error` → throw `validation_failed`
|
|
2325
|
+
* with `details: {doc_id, error, hint}`.
|
|
2326
|
+
* - `success: false + empty/null error` → throw `internal_error`
|
|
2327
|
+
* with wire-regression hint.
|
|
2328
|
+
* - `success: true + null block_ids` → throw `internal_error`
|
|
2329
|
+
* (per-fetcher null-payload contract per R-v0.5-NEW-11 — Monday's
|
|
2330
|
+
* probe description "Returns the IDs of the newly created blocks
|
|
2331
|
+
* on success" promises a non-null array).
|
|
2332
|
+
*
|
|
2333
|
+
* Omits `afterBlockId` when undefined (M34 / M35 / M36 omit-vs-null
|
|
2334
|
+
* discipline).
|
|
2335
|
+
*/
|
|
2336
|
+
export const addContentToDocFromMarkdown = async (inputs) => {
|
|
2337
|
+
const variables = {
|
|
2338
|
+
docId: inputs.docId,
|
|
2339
|
+
markdown: inputs.markdown,
|
|
2340
|
+
};
|
|
2341
|
+
if (inputs.afterBlockId !== undefined) {
|
|
2342
|
+
variables.afterBlockId = inputs.afterBlockId;
|
|
2343
|
+
}
|
|
2344
|
+
const response = await inputs.client.raw(ADD_CONTENT_TO_DOC_FROM_MARKDOWN_MUTATION, variables, { operationName: 'AddContentToDocFromMarkdown' });
|
|
2345
|
+
const data = unwrapOrThrow(addContentToDocFromMarkdownResponseSchema.safeParse(response.data), {
|
|
2346
|
+
context: 'Monday returned a malformed AddContentToDocFromMarkdown response',
|
|
2347
|
+
details: { doc_id: inputs.docId },
|
|
2348
|
+
hint: 'this is a data-integrity error in Monday\'s response; verify ' +
|
|
2349
|
+
'the response shape and update ' +
|
|
2350
|
+
'`addContentToDocFromMarkdownResponseSchema` if Monday\'s ' +
|
|
2351
|
+
'contract has changed.',
|
|
2352
|
+
});
|
|
2353
|
+
assertResponseFieldPresent({
|
|
2354
|
+
data,
|
|
2355
|
+
key: 'add_content_to_doc_from_markdown',
|
|
2356
|
+
operationLabel: 'AddContentToDocFromMarkdown',
|
|
2357
|
+
details: { doc_id: inputs.docId },
|
|
2358
|
+
nullHandling: 'caller_handles',
|
|
2359
|
+
});
|
|
2360
|
+
const rawResult = data.add_content_to_doc_from_markdown;
|
|
2361
|
+
if (rawResult === null || rawResult === undefined) {
|
|
2362
|
+
throw new ApiError('internal_error', `Monday returned no payload from add_content_to_doc_from_markdown for doc ${inputs.docId}.`, { details: { doc_id: inputs.docId } });
|
|
2363
|
+
}
|
|
2364
|
+
const parsed = unwrapOrThrow(docBlocksFromMarkdownResultSchema.safeParse(rawResult), {
|
|
2365
|
+
context: `Monday returned a malformed DocBlocksFromMarkdownResult payload from add_content_to_doc_from_markdown (doc ${inputs.docId})`,
|
|
2366
|
+
details: { doc_id: inputs.docId },
|
|
2367
|
+
});
|
|
2368
|
+
if (!parsed.success) {
|
|
2369
|
+
if (parsed.error !== null && parsed.error.length > 0) {
|
|
2370
|
+
throw new ApiError('validation_failed', `Monday rejected add_content_to_doc_from_markdown: ${parsed.error}`, {
|
|
2371
|
+
details: {
|
|
2372
|
+
doc_id: inputs.docId,
|
|
2373
|
+
error: parsed.error,
|
|
2374
|
+
hint: 'Monday\'s wire-side rejection reason is verbatim in `error`; ' +
|
|
2375
|
+
'inspect it for invalid markdown, payload-size, doc visibility, ' +
|
|
2376
|
+
'or permission issues.',
|
|
2377
|
+
},
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
throw new ApiError('internal_error', `Monday returned success: false from add_content_to_doc_from_markdown without a populated error message (doc ${inputs.docId}).`, {
|
|
2381
|
+
details: {
|
|
2382
|
+
doc_id: inputs.docId,
|
|
2383
|
+
hint: 'wire-shape regression — Monday\'s contract is that `error` is ' +
|
|
2384
|
+
'a non-empty string when `success: false`. Re-probe via ' +
|
|
2385
|
+
'`scripts/probe/v0.5-doc-mutations.ts` if this persists.',
|
|
2386
|
+
},
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
if (parsed.block_ids === null) {
|
|
2390
|
+
throw new ApiError('internal_error', `Monday returned success: true from add_content_to_doc_from_markdown but a null block_ids list (doc ${inputs.docId}).`, {
|
|
2391
|
+
details: {
|
|
2392
|
+
doc_id: inputs.docId,
|
|
2393
|
+
hint: 'wire-shape regression — Monday\'s probe description promises a ' +
|
|
2394
|
+
'non-null `block_ids` list on success (empty array is plausible ' +
|
|
2395
|
+
'when the markdown parses to zero convertible blocks). Re-probe ' +
|
|
2396
|
+
'via `scripts/probe/v0.5-doc-mutations.ts` if this persists.',
|
|
2397
|
+
},
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
return {
|
|
2401
|
+
result: {
|
|
2402
|
+
doc_id: inputs.docId,
|
|
2403
|
+
block_ids: parsed.block_ids,
|
|
2404
|
+
success: true,
|
|
2405
|
+
},
|
|
2406
|
+
source: 'live',
|
|
2407
|
+
cacheAgeSeconds: null,
|
|
2408
|
+
complexity: response.complexity,
|
|
2409
|
+
};
|
|
2410
|
+
};
|
|
2411
|
+
//# sourceMappingURL=documents.js.map
|