@37signals/basecamp 0.12.0 → 0.13.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.
Files changed (278) hide show
  1. package/README.md +112 -16
  2. package/dist/.build-complete +0 -0
  3. package/dist/client.d.ts +53 -11
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +132 -150
  6. package/dist/client.js.map +1 -1
  7. package/dist/download.d.ts +7 -0
  8. package/dist/download.d.ts.map +1 -1
  9. package/dist/download.js +82 -22
  10. package/dist/download.js.map +1 -1
  11. package/dist/errors.d.ts +18 -0
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +127 -14
  14. package/dist/errors.js.map +1 -1
  15. package/dist/generated/metadata.d.ts.map +1 -1
  16. package/dist/generated/metadata.js +280 -141
  17. package/dist/generated/metadata.js.map +1 -1
  18. package/dist/generated/metadata.ts +280 -141
  19. package/dist/generated/openapi-stripped.json +6435 -4283
  20. package/dist/generated/path-mapping.d.ts.map +1 -1
  21. package/dist/generated/path-mapping.js +27 -16
  22. package/dist/generated/path-mapping.js.map +1 -1
  23. package/dist/generated/path-mapping.ts +27 -16
  24. package/dist/generated/schema.d.ts +4283 -2457
  25. package/dist/generated/services/bookmarks.d.ts +1 -1
  26. package/dist/generated/services/bookmarks.d.ts.map +1 -1
  27. package/dist/generated/services/bookmarks.ts +1 -1
  28. package/dist/generated/services/boosts.d.ts +10 -0
  29. package/dist/generated/services/boosts.d.ts.map +1 -1
  30. package/dist/generated/services/boosts.js +8 -0
  31. package/dist/generated/services/boosts.js.map +1 -1
  32. package/dist/generated/services/boosts.ts +12 -0
  33. package/dist/generated/services/campfires.d.ts +54 -40
  34. package/dist/generated/services/campfires.d.ts.map +1 -1
  35. package/dist/generated/services/campfires.js +82 -65
  36. package/dist/generated/services/campfires.js.map +1 -1
  37. package/dist/generated/services/campfires.ts +107 -85
  38. package/dist/generated/services/cards.d.ts +5 -0
  39. package/dist/generated/services/cards.d.ts.map +1 -1
  40. package/dist/generated/services/cards.js +4 -0
  41. package/dist/generated/services/cards.js.map +1 -1
  42. package/dist/generated/services/cards.ts +6 -0
  43. package/dist/generated/services/checkins.d.ts +14 -0
  44. package/dist/generated/services/checkins.d.ts.map +1 -1
  45. package/dist/generated/services/checkins.js +14 -1
  46. package/dist/generated/services/checkins.js.map +1 -1
  47. package/dist/generated/services/checkins.ts +20 -0
  48. package/dist/generated/services/client-approvals.d.ts +6 -3
  49. package/dist/generated/services/client-approvals.d.ts.map +1 -1
  50. package/dist/generated/services/client-approvals.js +8 -5
  51. package/dist/generated/services/client-approvals.js.map +1 -1
  52. package/dist/generated/services/client-approvals.ts +10 -5
  53. package/dist/generated/services/client-correspondences.d.ts +6 -3
  54. package/dist/generated/services/client-correspondences.d.ts.map +1 -1
  55. package/dist/generated/services/client-correspondences.js +8 -5
  56. package/dist/generated/services/client-correspondences.js.map +1 -1
  57. package/dist/generated/services/client-correspondences.ts +10 -5
  58. package/dist/generated/services/client-replies.d.ts +11 -4
  59. package/dist/generated/services/client-replies.d.ts.map +1 -1
  60. package/dist/generated/services/client-replies.js +16 -8
  61. package/dist/generated/services/client-replies.js.map +1 -1
  62. package/dist/generated/services/client-replies.ts +18 -8
  63. package/dist/generated/services/cloud-files.d.ts +92 -0
  64. package/dist/generated/services/cloud-files.d.ts.map +1 -0
  65. package/dist/generated/services/cloud-files.js +121 -0
  66. package/dist/generated/services/cloud-files.js.map +1 -0
  67. package/dist/generated/services/cloud-files.ts +188 -0
  68. package/dist/generated/services/comments.d.ts +5 -0
  69. package/dist/generated/services/comments.d.ts.map +1 -1
  70. package/dist/generated/services/comments.js +4 -0
  71. package/dist/generated/services/comments.js.map +1 -1
  72. package/dist/generated/services/comments.ts +6 -0
  73. package/dist/generated/services/documents.d.ts +12 -7
  74. package/dist/generated/services/documents.d.ts.map +1 -1
  75. package/dist/generated/services/documents.js +10 -6
  76. package/dist/generated/services/documents.js.map +1 -1
  77. package/dist/generated/services/documents.ts +14 -8
  78. package/dist/generated/services/drafts.d.ts +1 -1
  79. package/dist/generated/services/drafts.d.ts.map +1 -1
  80. package/dist/generated/services/drafts.ts +1 -1
  81. package/dist/generated/services/events.d.ts +5 -0
  82. package/dist/generated/services/events.d.ts.map +1 -1
  83. package/dist/generated/services/events.js +4 -0
  84. package/dist/generated/services/events.js.map +1 -1
  85. package/dist/generated/services/events.ts +6 -0
  86. package/dist/generated/services/everything.d.ts +14 -14
  87. package/dist/generated/services/everything.d.ts.map +1 -1
  88. package/dist/generated/services/everything.ts +14 -14
  89. package/dist/generated/services/folders.d.ts +96 -0
  90. package/dist/generated/services/folders.d.ts.map +1 -0
  91. package/dist/generated/services/folders.js +140 -0
  92. package/dist/generated/services/folders.js.map +1 -0
  93. package/dist/generated/services/folders.ts +201 -0
  94. package/dist/generated/services/forwards.d.ts +7 -20
  95. package/dist/generated/services/forwards.d.ts.map +1 -1
  96. package/dist/generated/services/forwards.js +6 -35
  97. package/dist/generated/services/forwards.js.map +1 -1
  98. package/dist/generated/services/forwards.ts +10 -48
  99. package/dist/generated/services/gauges.d.ts +7 -0
  100. package/dist/generated/services/gauges.d.ts.map +1 -1
  101. package/dist/generated/services/gauges.js +5 -1
  102. package/dist/generated/services/gauges.js.map +1 -1
  103. package/dist/generated/services/gauges.ts +9 -1
  104. package/dist/generated/services/google-documents.d.ts +98 -0
  105. package/dist/generated/services/google-documents.d.ts.map +1 -0
  106. package/dist/generated/services/google-documents.js +123 -0
  107. package/dist/generated/services/google-documents.js.map +1 -0
  108. package/dist/generated/services/google-documents.ts +196 -0
  109. package/dist/generated/services/index.d.ts +7 -4
  110. package/dist/generated/services/index.d.ts.map +1 -1
  111. package/dist/generated/services/index.js +7 -4
  112. package/dist/generated/services/index.js.map +1 -1
  113. package/dist/generated/services/index.ts +7 -4
  114. package/dist/generated/services/messages.d.ts +2 -0
  115. package/dist/generated/services/messages.d.ts.map +1 -1
  116. package/dist/generated/services/messages.js +1 -1
  117. package/dist/generated/services/messages.js.map +1 -1
  118. package/dist/generated/services/messages.ts +3 -1
  119. package/dist/generated/services/my-notes.d.ts +1 -1
  120. package/dist/generated/services/my-notes.js +1 -1
  121. package/dist/generated/services/my-notes.ts +1 -1
  122. package/dist/generated/services/my-notifications.d.ts +4 -2
  123. package/dist/generated/services/my-notifications.d.ts.map +1 -1
  124. package/dist/generated/services/my-notifications.js.map +1 -1
  125. package/dist/generated/services/my-notifications.ts +4 -2
  126. package/dist/generated/services/people.d.ts +11 -1
  127. package/dist/generated/services/people.d.ts.map +1 -1
  128. package/dist/generated/services/people.js +13 -2
  129. package/dist/generated/services/people.js.map +1 -1
  130. package/dist/generated/services/people.ts +15 -1
  131. package/dist/generated/services/projects.d.ts +26 -0
  132. package/dist/generated/services/projects.d.ts.map +1 -1
  133. package/dist/generated/services/projects.js +49 -1
  134. package/dist/generated/services/projects.js.map +1 -1
  135. package/dist/generated/services/projects.ts +61 -1
  136. package/dist/generated/services/recordings.d.ts +2 -12
  137. package/dist/generated/services/recordings.d.ts.map +1 -1
  138. package/dist/generated/services/recordings.js +1 -26
  139. package/dist/generated/services/recordings.js.map +1 -1
  140. package/dist/generated/services/recordings.ts +3 -31
  141. package/dist/generated/services/reports.d.ts +8 -12
  142. package/dist/generated/services/reports.d.ts.map +1 -1
  143. package/dist/generated/services/reports.js +11 -5
  144. package/dist/generated/services/reports.js.map +1 -1
  145. package/dist/generated/services/reports.ts +13 -14
  146. package/dist/generated/services/schedules.d.ts +94 -15
  147. package/dist/generated/services/schedules.d.ts.map +1 -1
  148. package/dist/generated/services/schedules.js +19 -8
  149. package/dist/generated/services/schedules.js.map +1 -1
  150. package/dist/generated/services/schedules.ts +107 -17
  151. package/dist/generated/services/search.d.ts +2 -0
  152. package/dist/generated/services/search.d.ts.map +1 -1
  153. package/dist/generated/services/search.js +1 -1
  154. package/dist/generated/services/search.js.map +1 -1
  155. package/dist/generated/services/search.ts +3 -1
  156. package/dist/generated/services/templates.d.ts +2 -0
  157. package/dist/generated/services/templates.d.ts.map +1 -1
  158. package/dist/generated/services/templates.js +1 -1
  159. package/dist/generated/services/templates.js.map +1 -1
  160. package/dist/generated/services/templates.ts +3 -1
  161. package/dist/generated/services/timeline.d.ts +2 -0
  162. package/dist/generated/services/timeline.d.ts.map +1 -1
  163. package/dist/generated/services/timeline.js +1 -0
  164. package/dist/generated/services/timeline.js.map +1 -1
  165. package/dist/generated/services/timeline.ts +3 -0
  166. package/dist/generated/services/timesheets.d.ts +16 -0
  167. package/dist/generated/services/timesheets.d.ts.map +1 -1
  168. package/dist/generated/services/timesheets.js +26 -2
  169. package/dist/generated/services/timesheets.js.map +1 -1
  170. package/dist/generated/services/timesheets.ts +35 -2
  171. package/dist/generated/services/todolist-groups.d.ts +11 -6
  172. package/dist/generated/services/todolist-groups.d.ts.map +1 -1
  173. package/dist/generated/services/todolist-groups.js +7 -3
  174. package/dist/generated/services/todolist-groups.js.map +1 -1
  175. package/dist/generated/services/todolist-groups.ts +13 -7
  176. package/dist/generated/services/todolists.d.ts +15 -13
  177. package/dist/generated/services/todolists.d.ts.map +1 -1
  178. package/dist/generated/services/todolists.js +11 -8
  179. package/dist/generated/services/todolists.js.map +1 -1
  180. package/dist/generated/services/todolists.ts +19 -14
  181. package/dist/generated/services/todos.d.ts +2 -12
  182. package/dist/generated/services/todos.d.ts.map +1 -1
  183. package/dist/generated/services/todos.js +1 -25
  184. package/dist/generated/services/todos.js.map +1 -1
  185. package/dist/generated/services/todos.ts +3 -30
  186. package/dist/generated/services/uploads.d.ts +5 -0
  187. package/dist/generated/services/uploads.d.ts.map +1 -1
  188. package/dist/generated/services/uploads.js +4 -0
  189. package/dist/generated/services/uploads.js.map +1 -1
  190. package/dist/generated/services/uploads.ts +6 -0
  191. package/dist/generated/services/vaults.d.ts +5 -0
  192. package/dist/generated/services/vaults.d.ts.map +1 -1
  193. package/dist/generated/services/vaults.js +4 -0
  194. package/dist/generated/services/vaults.js.map +1 -1
  195. package/dist/generated/services/vaults.ts +6 -0
  196. package/dist/index.d.ts +15 -9
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +19 -8
  199. package/dist/index.js.map +1 -1
  200. package/dist/pagination-utils.d.ts +46 -0
  201. package/dist/pagination-utils.d.ts.map +1 -1
  202. package/dist/pagination-utils.js +85 -2
  203. package/dist/pagination-utils.js.map +1 -1
  204. package/dist/pagination.d.ts +11 -0
  205. package/dist/pagination.d.ts.map +1 -1
  206. package/dist/pagination.js.map +1 -1
  207. package/dist/retry.d.ts +110 -0
  208. package/dist/retry.d.ts.map +1 -0
  209. package/dist/retry.js +253 -0
  210. package/dist/retry.js.map +1 -0
  211. package/dist/services/base.d.ts.map +1 -1
  212. package/dist/services/base.js +59 -5
  213. package/dist/services/base.js.map +1 -1
  214. package/dist/services/cards-extensions.d.ts +26 -21
  215. package/dist/services/cards-extensions.d.ts.map +1 -1
  216. package/dist/services/cards-extensions.js +28 -31
  217. package/dist/services/cards-extensions.js.map +1 -1
  218. package/dist/services/documents-extensions.d.ts +122 -0
  219. package/dist/services/documents-extensions.d.ts.map +1 -0
  220. package/dist/services/documents-extensions.js +125 -0
  221. package/dist/services/documents-extensions.js.map +1 -0
  222. package/dist/services/merge-safe.d.ts +167 -0
  223. package/dist/services/merge-safe.d.ts.map +1 -0
  224. package/dist/services/merge-safe.js +217 -0
  225. package/dist/services/merge-safe.js.map +1 -0
  226. package/dist/services/schedules-extensions.d.ts +266 -0
  227. package/dist/services/schedules-extensions.d.ts.map +1 -0
  228. package/dist/services/schedules-extensions.js +268 -0
  229. package/dist/services/schedules-extensions.js.map +1 -0
  230. package/dist/services/todolists-extensions.d.ts +110 -0
  231. package/dist/services/todolists-extensions.d.ts.map +1 -0
  232. package/dist/services/todolists-extensions.js +262 -0
  233. package/dist/services/todolists-extensions.js.map +1 -0
  234. package/dist/services/todos-extensions.d.ts +12 -1
  235. package/dist/services/todos-extensions.d.ts.map +1 -1
  236. package/dist/services/todos-extensions.js +27 -8
  237. package/dist/services/todos-extensions.js.map +1 -1
  238. package/package.json +5 -4
  239. package/src/generated/metadata.ts +280 -141
  240. package/src/generated/openapi-stripped.json +6435 -4283
  241. package/src/generated/path-mapping.ts +27 -16
  242. package/src/generated/schema.d.ts +4283 -2457
  243. package/src/generated/services/bookmarks.ts +1 -1
  244. package/src/generated/services/boosts.ts +12 -0
  245. package/src/generated/services/campfires.ts +107 -85
  246. package/src/generated/services/cards.ts +6 -0
  247. package/src/generated/services/checkins.ts +20 -0
  248. package/src/generated/services/client-approvals.ts +10 -5
  249. package/src/generated/services/client-correspondences.ts +10 -5
  250. package/src/generated/services/client-replies.ts +18 -8
  251. package/src/generated/services/cloud-files.ts +188 -0
  252. package/src/generated/services/comments.ts +6 -0
  253. package/src/generated/services/documents.ts +14 -8
  254. package/src/generated/services/drafts.ts +1 -1
  255. package/src/generated/services/events.ts +6 -0
  256. package/src/generated/services/everything.ts +14 -14
  257. package/src/generated/services/folders.ts +201 -0
  258. package/src/generated/services/forwards.ts +10 -48
  259. package/src/generated/services/gauges.ts +9 -1
  260. package/src/generated/services/google-documents.ts +196 -0
  261. package/src/generated/services/index.ts +7 -4
  262. package/src/generated/services/messages.ts +3 -1
  263. package/src/generated/services/my-notes.ts +1 -1
  264. package/src/generated/services/my-notifications.ts +4 -2
  265. package/src/generated/services/people.ts +15 -1
  266. package/src/generated/services/projects.ts +61 -1
  267. package/src/generated/services/recordings.ts +3 -31
  268. package/src/generated/services/reports.ts +13 -14
  269. package/src/generated/services/schedules.ts +107 -17
  270. package/src/generated/services/search.ts +3 -1
  271. package/src/generated/services/templates.ts +3 -1
  272. package/src/generated/services/timeline.ts +3 -0
  273. package/src/generated/services/timesheets.ts +35 -2
  274. package/src/generated/services/todolist-groups.ts +13 -7
  275. package/src/generated/services/todolists.ts +19 -14
  276. package/src/generated/services/todos.ts +3 -30
  277. package/src/generated/services/uploads.ts +6 -0
  278. package/src/generated/services/vaults.ts +6 -0
@@ -1,38 +1,42 @@
1
1
  import { CardsService as GeneratedCardsService } from "../generated/services/cards.js";
2
2
  /**
3
- * CardsService with a merge-safe `update` on top of the generated surface
3
+ * CardsService with a presence-aware `update` on top of the generated surface
4
4
  * (`get`, `updateVerbatim`, `move`, ...).
5
5
  *
6
- * BC3 builds the card's update params as `{ due_on: nil }.merge(card_params)`
7
- * (`kanban/cards_controller.rb`), so **any** update whose body omits `due_on`
8
- * erases the card's due date. A sparse PUT — the natural thing to write — is
9
- * therefore destructive on the raw endpoint.
6
+ * BC3's `kanban/cards_controller.rb` builds a JSON card update's params
7
+ * straight from `card_params` (basecamp/bc3#12521), so on the JSON
8
+ * representation an omitted key means **leave unchanged** — `due_on` included.
9
+ * Clearing the due date is asked for explicitly: the server accepts JSON `null`
10
+ * or `""`, and this SDK sends `""`, because a null would violate body
11
+ * compaction (SPEC §18). Only the HTML/turbo_stream web forms still default an
12
+ * omitted `due_on` to nil (`card_params.with_defaults(due_on: nil)`), which is
13
+ * how the JSON path could be made presence-aware without disturbing the web
14
+ * contract.
10
15
  *
11
- * `update` composes the public `get` and `updateVerbatim` methods, so hooks
12
- * observe the two real wire operations rather than a synthetic composite.
16
+ * `update` therefore sends exactly what the caller addressed, in a single PUT.
17
+ * It differs from `updateVerbatim` only in accepting `dueOn: null` and encoding
18
+ * it as the wire spelling of a clear.
13
19
  */
14
20
  export class CardsService extends GeneratedCardsService {
15
21
  /**
16
22
  * Updates a card without disturbing fields you did not mention.
17
23
  *
18
- * Fetches the card first and resends its existing due date when you left
19
- * `dueOn` unaddressed — so the extra GET is paid for only in the case where
20
- * the API would otherwise destroy something. Naming `dueOn` explicitly (a
21
- * date, or `null` to clear) skips the fetch entirely.
24
+ * One request. A field you omit is omitted from the body and the server
25
+ * leaves it alone; `dueOn: null` clears the due date.
22
26
  *
23
- * Not atomic: a concurrent due-date change landing between the GET and the
24
- * PUT is overwritten with the value this call read. The window is one
25
- * round-trip. Use `updateVerbatim` to send a single request and manage
26
- * `due_on` yourself.
27
+ * Earlier releases read the card back first and resent its due date, because
28
+ * a JSON update that omitted `due_on` used to erase it. bc3#12521 removed
29
+ * that behaviour, so the extra round-trip — and the race between the read and
30
+ * the write — is gone.
27
31
  *
28
32
  * @param cardId - The card ID
29
- * @param req - Fields to set; omitted fields are preserved
33
+ * @param req - Fields to set; omitted fields are left unchanged
30
34
  * @returns The updated Card
31
35
  * @throws {BasecampError} If the request fails
32
36
  *
33
37
  * @example
34
38
  * ```ts
35
- * // Retitle without losing the due date.
39
+ * // Retitle without touching the due date.
36
40
  * await client.cards.update(123, { title: "New title" });
37
41
  *
38
42
  * // Clear the due date.
@@ -47,20 +51,13 @@ export class CardsService extends GeneratedCardsService {
47
51
  body.content = req.content;
48
52
  if (req.assigneeIds !== undefined)
49
53
  body.assigneeIds = req.assigneeIds;
50
- if (req.dueOn === undefined) {
51
- // Unaddressed: preserve whatever is there now.
52
- const current = await this.get(cardId);
53
- // The Card response carries wire-shaped keys; the request builder takes
54
- // camelCase.
55
- if (current.due_on)
56
- body.dueOn = current.due_on;
57
- }
58
- else if (req.dueOn !== null) {
59
- body.dueOn = req.dueOn;
60
- }
61
- // req.dueOn === null falls through with dueOn unset: an omitted due_on is
62
- // how the API clears the date, so omission IS the clear encoding. Sending
63
- // `{"due_on": null}` would violate body compaction (SPEC §18).
54
+ // `due_on` stays off the wire unless the caller addressed it, which the
55
+ // server reads as "leave the due date alone". A date sets it; `null` asks
56
+ // for a clear and is sent as `""` — the server casts the blank to nil (a
57
+ // BC3 server test pins it), while `{"due_on": null}` would violate body
58
+ // compaction (SPEC §18) and omission would now be silently ignored.
59
+ if (req.dueOn !== undefined)
60
+ body.dueOn = req.dueOn ?? "";
64
61
  return this.updateVerbatim(cardId, body);
65
62
  }
66
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cards-extensions.js","sourceRoot":"","sources":["../../src/services/cards-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAmCvF;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAa,SAAQ,qBAAqB;IACrD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,GAAsB;QACjD,MAAM,IAAI,GAKN,EAAE,CAAC;QAEP,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACpD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAC1D,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;QAEtE,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC5B,+CAA+C;YAC/C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACvC,wEAAwE;YACxE,aAAa;YACb,IAAI,OAAO,CAAC,MAAM;gBAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;QAClD,CAAC;aAAM,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACzB,CAAC;QACD,0EAA0E;QAC1E,0EAA0E;QAC1E,+DAA+D;QAE/D,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;CACF"}
1
+ {"version":3,"file":"cards-extensions.js","sourceRoot":"","sources":["../../src/services/cards-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAoCvF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,YAAa,SAAQ,qBAAqB;IACrD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,GAAsB;QACjD,MAAM,IAAI,GAKN,EAAE,CAAC;QAEP,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACpD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAC1D,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;QAEtE,wEAAwE;QACxE,0EAA0E;QAC1E,yEAAyE;QACzE,wEAAwE;QACxE,oEAAoE;QACpE,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QAE1D,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;CACF"}
@@ -0,0 +1,122 @@
1
+ import { DocumentsService as GeneratedDocumentsService } from "../generated/services/documents.js";
2
+ import type { Document } from "../generated/services/documents.js";
3
+ /**
4
+ * Request parameters for update. Both fields are optional: an omitted field
5
+ * is left untouched on the document, guaranteed. An explicitly-passed empty
6
+ * string is a set (clears the field).
7
+ */
8
+ export interface UpdateDocumentRequest {
9
+ /** Plain-text title. Omit to leave unchanged; `""` clears it. */
10
+ title?: string;
11
+ /** Rich text body (HTML). Omit to leave unchanged; `""` clears it. */
12
+ content?: string;
13
+ }
14
+ /**
15
+ * A document's full writable state, handed to the `edit` callback. The whole
16
+ * object is PUT back to the server, so clearing a field means setting it empty
17
+ * (`""`) — there is no third state. The writable set is exactly
18
+ * `{title, content}`.
19
+ */
20
+ export interface DocumentFields {
21
+ /** Plain-text title. Set `""` to clear — the document then reads back as "Untitled". */
22
+ title: string;
23
+ /** Rich text body (HTML). Set `""` to clear. */
24
+ content: string;
25
+ }
26
+ /**
27
+ * DocumentsService with merge-safe `update` and read-modify-write `edit` on
28
+ * top of the generated surface (`get`, `replace`, ...).
29
+ *
30
+ * `PUT /{accountId}/documents/{documentId}` is a full replace: BC3's
31
+ * `DocumentsController#update` builds a brand-new `Document` from only the
32
+ * permitted params and swaps the recordable wholesale, so a sparse PUT that
33
+ * omits `content` erases it. Omitting `title` erases that too — the document
34
+ * then reads back as `"Untitled"`, because `Document#title` falls back when
35
+ * blank. Neither field is presence-validated, so **neither omission is a 422**;
36
+ * both are a `200` that quietly clears. What BC3 does require is the wrapping
37
+ * `document` object, so a body naming neither field is a `400`.
38
+ *
39
+ * Both methods compose the public `get` and `replace` methods, so hooks
40
+ * observe the two wire operations (`GetDocument` then `ReplaceDocument`), not
41
+ * a synthetic composite.
42
+ *
43
+ * Publishing a draft (`status: "active"`) is not part of this surface: the
44
+ * spec models only `title` and `content`, and BC3 rejects a status-only update
45
+ * for the same reason it rejects an empty body.
46
+ */
47
+ export declare class DocumentsService extends GeneratedDocumentsService {
48
+ /**
49
+ * Sets the given fields on a document and preserves everything else: GETs
50
+ * the current document, overlays the explicitly-set request fields, and PUTs
51
+ * the full representation back. An omitted (`undefined`) field is untouched,
52
+ * guaranteed; an explicitly-passed `""` clears.
53
+ *
54
+ * Not atomic: there is no conditional-update signal on this endpoint, so a
55
+ * concurrent write between the GET and PUT is overwritten — last write wins
56
+ * for the whole representation. The window is one round-trip. Use `replace`
57
+ * to overwrite deliberately.
58
+ *
59
+ * @param documentId - The document ID
60
+ * @param req - Fields to set; omitted fields are preserved
61
+ * @returns The updated Document
62
+ * @throws {BasecampError} If the request fails
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * // Retitle without erasing the body.
67
+ * await client.documents.update(123, { title: "Q3 Plan" });
68
+ * ```
69
+ */
70
+ update(documentId: number, req: UpdateDocumentRequest): Promise<Document>;
71
+ /**
72
+ * Applies a read-modify-write callback to a document: GETs the current
73
+ * document, hands the callback its full writable representation, and PUTs
74
+ * the whole thing back. Clearing a field means setting it empty (`""`) — an
75
+ * untouched field keeps its current value. If the callback throws (or
76
+ * rejects), the edit aborts and nothing is written.
77
+ *
78
+ * Not atomic: there is no conditional-update signal on this endpoint, so a
79
+ * concurrent write between the GET and PUT is overwritten — last write wins
80
+ * for the whole representation. The window is one round-trip. Use `replace`
81
+ * to overwrite deliberately.
82
+ *
83
+ * @param documentId - The document ID
84
+ * @param fn - Callback that mutates the document's writable fields in place
85
+ * @returns The updated Document
86
+ * @throws {BasecampError} If the request fails
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * await client.documents.edit(123, (d) => {
91
+ * d.title = `🚨 ${d.title}`;
92
+ * d.content = ""; // clearing = setting empty on a full object
93
+ * });
94
+ * ```
95
+ */
96
+ edit(documentId: number, fn: (d: DocumentFields) => void | Promise<void>): Promise<Document>;
97
+ /**
98
+ * Fetches the document and derives its full writable state.
99
+ *
100
+ * Every value here is resent in the full-replace PUT, so every value is
101
+ * validated before it is read. `?? ""` coalesces only `null` and
102
+ * `undefined`, leaving corruption wide open: `false`, `0`, `[]`, `{}`, `42`,
103
+ * `true` and friends would all be forwarded **verbatim** and written to the
104
+ * document on a call that never mentioned the field. Nothing below this
105
+ * rejects them — `schema.d.ts` is erased at build time, so `Document` is a
106
+ * compile-time claim about runtime data. See `merge-safe.ts` and #576.
107
+ *
108
+ * The two writable fields read differently because the spec models them
109
+ * differently: `title` is `@required`, so absent or null is malformed;
110
+ * `content` is optional, so absent or null is a genuinely empty body.
111
+ */
112
+ private currentFields;
113
+ /**
114
+ * PUTs the full writable state via `replace`. Both fields are always sent,
115
+ * empties included: on a full-replace endpoint `""` is how a clear is
116
+ * expressed — never JSON null (SPEC §18 body compaction), and never by
117
+ * omission, which would leave the field to the server's own clear-by-default
118
+ * and read as an accident rather than an intent.
119
+ */
120
+ private putFields;
121
+ }
122
+ //# sourceMappingURL=documents-extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents-extensions.d.ts","sourceRoot":"","sources":["../../src/services/documents-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,IAAI,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACnG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAMnE;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,gBAAiB,SAAQ,yBAAyB;IAC7D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAO/E;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,IAAI,CACR,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC9C,OAAO,CAAC,QAAQ,CAAC;IAMpB;;;;;;;;;;;;;;OAcG;YACW,aAAa;IAa3B;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;CAGlB"}
@@ -0,0 +1,125 @@
1
+ import { DocumentsService as GeneratedDocumentsService } from "../generated/services/documents.js";
2
+ import { requireRecord, requiredWritableString, writableString } from "./merge-safe.js";
3
+ /** The deliberate-overwrite escape hatch named in this composite's error hints. */
4
+ const ESCAPE = "replace()";
5
+ /**
6
+ * DocumentsService with merge-safe `update` and read-modify-write `edit` on
7
+ * top of the generated surface (`get`, `replace`, ...).
8
+ *
9
+ * `PUT /{accountId}/documents/{documentId}` is a full replace: BC3's
10
+ * `DocumentsController#update` builds a brand-new `Document` from only the
11
+ * permitted params and swaps the recordable wholesale, so a sparse PUT that
12
+ * omits `content` erases it. Omitting `title` erases that too — the document
13
+ * then reads back as `"Untitled"`, because `Document#title` falls back when
14
+ * blank. Neither field is presence-validated, so **neither omission is a 422**;
15
+ * both are a `200` that quietly clears. What BC3 does require is the wrapping
16
+ * `document` object, so a body naming neither field is a `400`.
17
+ *
18
+ * Both methods compose the public `get` and `replace` methods, so hooks
19
+ * observe the two wire operations (`GetDocument` then `ReplaceDocument`), not
20
+ * a synthetic composite.
21
+ *
22
+ * Publishing a draft (`status: "active"`) is not part of this surface: the
23
+ * spec models only `title` and `content`, and BC3 rejects a status-only update
24
+ * for the same reason it rejects an empty body.
25
+ */
26
+ export class DocumentsService extends GeneratedDocumentsService {
27
+ /**
28
+ * Sets the given fields on a document and preserves everything else: GETs
29
+ * the current document, overlays the explicitly-set request fields, and PUTs
30
+ * the full representation back. An omitted (`undefined`) field is untouched,
31
+ * guaranteed; an explicitly-passed `""` clears.
32
+ *
33
+ * Not atomic: there is no conditional-update signal on this endpoint, so a
34
+ * concurrent write between the GET and PUT is overwritten — last write wins
35
+ * for the whole representation. The window is one round-trip. Use `replace`
36
+ * to overwrite deliberately.
37
+ *
38
+ * @param documentId - The document ID
39
+ * @param req - Fields to set; omitted fields are preserved
40
+ * @returns The updated Document
41
+ * @throws {BasecampError} If the request fails
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * // Retitle without erasing the body.
46
+ * await client.documents.update(123, { title: "Q3 Plan" });
47
+ * ```
48
+ */
49
+ async update(documentId, req) {
50
+ const fields = await this.currentFields(documentId);
51
+ if (req.title !== undefined)
52
+ fields.title = req.title;
53
+ if (req.content !== undefined)
54
+ fields.content = req.content;
55
+ return this.putFields(documentId, fields);
56
+ }
57
+ /**
58
+ * Applies a read-modify-write callback to a document: GETs the current
59
+ * document, hands the callback its full writable representation, and PUTs
60
+ * the whole thing back. Clearing a field means setting it empty (`""`) — an
61
+ * untouched field keeps its current value. If the callback throws (or
62
+ * rejects), the edit aborts and nothing is written.
63
+ *
64
+ * Not atomic: there is no conditional-update signal on this endpoint, so a
65
+ * concurrent write between the GET and PUT is overwritten — last write wins
66
+ * for the whole representation. The window is one round-trip. Use `replace`
67
+ * to overwrite deliberately.
68
+ *
69
+ * @param documentId - The document ID
70
+ * @param fn - Callback that mutates the document's writable fields in place
71
+ * @returns The updated Document
72
+ * @throws {BasecampError} If the request fails
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * await client.documents.edit(123, (d) => {
77
+ * d.title = `🚨 ${d.title}`;
78
+ * d.content = ""; // clearing = setting empty on a full object
79
+ * });
80
+ * ```
81
+ */
82
+ async edit(documentId, fn) {
83
+ const fields = await this.currentFields(documentId);
84
+ await fn(fields);
85
+ return this.putFields(documentId, fields);
86
+ }
87
+ /**
88
+ * Fetches the document and derives its full writable state.
89
+ *
90
+ * Every value here is resent in the full-replace PUT, so every value is
91
+ * validated before it is read. `?? ""` coalesces only `null` and
92
+ * `undefined`, leaving corruption wide open: `false`, `0`, `[]`, `{}`, `42`,
93
+ * `true` and friends would all be forwarded **verbatim** and written to the
94
+ * document on a call that never mentioned the field. Nothing below this
95
+ * rejects them — `schema.d.ts` is erased at build time, so `Document` is a
96
+ * compile-time claim about runtime data. See `merge-safe.ts` and #576.
97
+ *
98
+ * The two writable fields read differently because the spec models them
99
+ * differently: `title` is `@required`, so absent or null is malformed;
100
+ * `content` is optional, so absent or null is a genuinely empty body.
101
+ */
102
+ async currentFields(documentId) {
103
+ const current = requireRecord(await this.get(documentId), {
104
+ record: "Document",
105
+ operation: "GetDocument",
106
+ escape: ESCAPE,
107
+ });
108
+ const opts = { record: "Document", escape: ESCAPE };
109
+ return {
110
+ title: requiredWritableString(current, "title", opts),
111
+ content: writableString(current, "content", opts),
112
+ };
113
+ }
114
+ /**
115
+ * PUTs the full writable state via `replace`. Both fields are always sent,
116
+ * empties included: on a full-replace endpoint `""` is how a clear is
117
+ * expressed — never JSON null (SPEC §18 body compaction), and never by
118
+ * omission, which would leave the field to the server's own clear-by-default
119
+ * and read as an accident rather than an intent.
120
+ */
121
+ putFields(documentId, f) {
122
+ return this.replace(documentId, { title: f.title, content: f.content });
123
+ }
124
+ }
125
+ //# sourceMappingURL=documents-extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents-extensions.js","sourceRoot":"","sources":["../../src/services/documents-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,IAAI,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAEnG,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAExF,mFAAmF;AACnF,MAAM,MAAM,GAAG,WAAW,CAAC;AA2B3B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,gBAAiB,SAAQ,yBAAyB;IAC7D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,MAAM,CAAC,UAAkB,EAAE,GAA0B;QACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACtD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;YAAE,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAC5D,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,CAAC,IAAI,CACR,UAAkB,EAClB,EAA+C;QAE/C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,KAAK,CAAC,aAAa,CAAC,UAAkB;QAC5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YACxD,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,aAAa;YACxB,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QACpD,OAAO;YACL,KAAK,EAAE,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;YACrD,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC;SAClD,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACK,SAAS,CAAC,UAAkB,EAAE,CAAiB;QACrD,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1E,CAAC;CACF"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Response guards shared by the merge-safe composites.
3
+ *
4
+ * A merge-safe `update`/`edit` GETs a record, reads each writable field, and
5
+ * PUTs the **full** representation back. The endpoint is full-replace, so every
6
+ * value read here is written — including one the caller never mentioned. If the
7
+ * read step coerces or forwards a malformed value instead of refusing it, that
8
+ * value lands on the record.
9
+ *
10
+ * Two failure modes, the same defect wearing different clothes:
11
+ *
12
+ * - **erasure** — a falsey value dropped or coalesced away, wiping the field;
13
+ * - **corruption** — a non-string forwarded verbatim, writing a number,
14
+ * boolean, array or object where a string belongs.
15
+ *
16
+ * `?? ""` catches only `null` and `undefined`, so it rules out erasure while
17
+ * leaving corruption wide open — every one of `false`, `0`, `[]`, `{}`, `42`,
18
+ * `true`, `["x"]` and `{a:1}` rides through unchanged. Testing only for erasure
19
+ * is what let this class survive five review passes, so both are refused here.
20
+ *
21
+ * **The rule: a composite is safe exactly when a decoder *rejects* a
22
+ * wrong-typed field at runtime — not when a type merely claims one.** Go
23
+ * (`json.Unmarshal`), Swift (`Codable`) and Kotlin (kotlinx.serialization)
24
+ * genuinely refuse. TypeScript's `schema.d.ts` is erased at build time, so the
25
+ * type on a GET result is a compile-time claim nothing validates; structurally
26
+ * this sits with Python and Ruby, not with Go and Swift (#576).
27
+ *
28
+ * Todolists carries its own copy of these guards (#574). #544 flattened the
29
+ * shape those guards read — dropping the envelope-arm rung, not the guards —
30
+ * but did not unify them here. A generated validating layer (#578) is the
31
+ * intended end state for all of them.
32
+ */
33
+ import { type BasecampError } from "../errors.js";
34
+ /**
35
+ * Renders a value for an error message without ever throwing.
36
+ *
37
+ * The guard's own error path must not fail while explaining a failure.
38
+ * `JSON.stringify` raises `TypeError` on a circular structure, and a value can
39
+ * carry a `toJSON` that throws — either would replace a clean `api_error` with
40
+ * an incidental `TypeError` and lose the diagnosis. The type name is always
41
+ * available; the rendering is a bonus, capped per SPEC §9 and dropped if it
42
+ * fails.
43
+ */
44
+ export declare function describeValue(value: unknown): string;
45
+ /**
46
+ * Builds the malformed-response error, with the message capped per SPEC §9.
47
+ *
48
+ * `api_error`, not `usage`: the value arrived in a successful API response, so
49
+ * nothing the caller passed is at fault. Statusless — the transport succeeded,
50
+ * there is no HTTP status to attribute — and non-retryable, because
51
+ * re-requesting cannot repair a malformed body.
52
+ */
53
+ export declare function malformedResponse(message: string, hint: string): BasecampError;
54
+ /**
55
+ * The response must be a JSON object before any field is read.
56
+ *
57
+ * One level up from the malformed-*field* guards: a successful GET can return a
58
+ * scalar, an array, or null. Reading a property off `null` throws a raw
59
+ * `TypeError`, and off an array or a string it silently yields `undefined`,
60
+ * which the field guards would then read as "genuinely empty" and write back —
61
+ * so the envelope needs checking before the fields.
62
+ */
63
+ export declare function requireRecord(body: unknown, opts: {
64
+ record: string;
65
+ operation: string;
66
+ escape: string;
67
+ }): Record<string, unknown>;
68
+ /**
69
+ * Reads a writable string field, refusing to pass a malformed one through.
70
+ *
71
+ * An absent key or an explicit `null` is genuinely empty — there is nothing to
72
+ * preserve and `""` is what the server already holds. An actual string passes
73
+ * verbatim. Anything else is a malformed response and is refused **before** the
74
+ * PUT, naming the offending field.
75
+ */
76
+ export declare function writableString(body: Record<string, unknown>, key: string, opts: {
77
+ record: string;
78
+ escape: string;
79
+ }): string;
80
+ /**
81
+ * Reads a writable string the record is *required* to carry.
82
+ *
83
+ * {@link writableString} treats an absent key or an explicit `null` as
84
+ * genuinely empty, which is right for an optional field — `""` is what the
85
+ * server already holds. It is wrong for a required one. Where the spec marks a
86
+ * response member `@required` and BC3 can never render it blank, an absent,
87
+ * null or blank value in a 2xx body is a **malformed response**, not an empty
88
+ * field. Coalescing it to `""` and sending that in the full-replace PUT would
89
+ * blank the real value on a call that never mentioned it — #576's defect
90
+ * exactly.
91
+ *
92
+ * Two records rely on this today and for the same reason: `Document#title` is
93
+ * `super.presence || "Untitled"` and `Schedule::Entry#summary` is
94
+ * `super.presence || "Untitled"`, so neither can come back blank from a healthy
95
+ * server.
96
+ *
97
+ * The wrong-type branch is delegated to {@link writableString}, so a required
98
+ * field and an optional one report a non-string identically.
99
+ */
100
+ export declare function requiredWritableString(body: Record<string, unknown>, key: string, opts: {
101
+ record: string;
102
+ escape: string;
103
+ }): string;
104
+ /**
105
+ * Reads a writable boolean the record is *required* to carry.
106
+ *
107
+ * The boolean analogue of {@link requiredWritableString}, and it cannot be
108
+ * expressed with a truthiness test: the value this guard most needs to admit is
109
+ * `false`, which every `||`/`if (!x)` idiom would treat as missing and replace
110
+ * with a default. `ScheduleEntry.all_day` is `NOT NULL` with a `false` default
111
+ * in BC3 and every partial emits it, so absent or null is a malformed
112
+ * response — and defaulting it to `false` would silently convert an all-day
113
+ * event into a midnight-to-midnight timed one on a call that only changed the
114
+ * summary.
115
+ *
116
+ * `0`/`1` are refused rather than coerced, for the same reason
117
+ * {@link writableString} refuses `42`: JSON has a boolean type and the server
118
+ * uses it.
119
+ */
120
+ export declare function requiredWritableBoolean(body: Record<string, unknown>, key: string, opts: {
121
+ record: string;
122
+ escape: string;
123
+ }): boolean;
124
+ /**
125
+ * Reads an *optional* writable boolean, refusing to coerce a malformed one.
126
+ *
127
+ * {@link writableString}'s boolean sibling, standing in the same relation to
128
+ * {@link requiredWritableBoolean} that `writableString` does to
129
+ * {@link requiredWritableString}: an absent key or an explicit `null` is
130
+ * genuinely "not set" and returns `false`, because that is what the server
131
+ * already holds.
132
+ *
133
+ * `ScheduleEntry.highlighted` is the case it exists for. The entry partial
134
+ * emits it unconditionally, but the reduced calendar partial behind
135
+ * `GetUpcomingSchedule` does not, and both render through the same schema — so
136
+ * the member is optional and absence is legitimate rather than malformed.
137
+ *
138
+ * What still cannot be tolerated is the *wrong type*: a `"yes"` or a `1` must be
139
+ * refused, not coerced, because a caller who assigns the seeded value straight
140
+ * back sends whatever it was seeded with. That branch is delegated to
141
+ * {@link requiredWritableBoolean}, so an optional boolean and a required one
142
+ * report a non-boolean identically.
143
+ */
144
+ export declare function writableBoolean(body: Record<string, unknown>, key: string, opts: {
145
+ record: string;
146
+ escape: string;
147
+ }): boolean;
148
+ /**
149
+ * Reads a list of person records and projects it to their integer IDs.
150
+ *
151
+ * The analogue of {@link writableString} for the ID-list fields. The `.map()`
152
+ * it replaces (`(body[key] ?? []).map((p) => p.id)`) has three ways to go wrong
153
+ * on malformed data: a non-array has no `.map` (a raw `TypeError`), a
154
+ * non-object element yields `undefined`, and a non-integer `id` rides through
155
+ * verbatim into the full-replace PUT — the same corruption as a wrong-typed
156
+ * string, one level down.
157
+ *
158
+ * `Number.isInteger` is the test rather than `typeof === "number"`: `1.5` and
159
+ * `NaN` are numbers and neither is a person ID. Booleans fail it outright,
160
+ * which is what we want — JavaScript would happily coerce `true` to `1`
161
+ * downstream.
162
+ */
163
+ export declare function writableIdList(body: Record<string, unknown>, key: string, opts: {
164
+ record: string;
165
+ escape: string;
166
+ }): number[];
167
+ //# sourceMappingURL=merge-safe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-safe.d.ts","sourceRoot":"","sources":["../../src/services/merge-safe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAMhF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAE9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC,MAAM,CAUR;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC,MAAM,CAUR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAgBT;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAIT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC,MAAM,EAAE,CA+BV"}