@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.
- package/README.md +112 -16
- package/dist/.build-complete +0 -0
- package/dist/client.d.ts +53 -11
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +132 -150
- package/dist/client.js.map +1 -1
- package/dist/download.d.ts +7 -0
- package/dist/download.d.ts.map +1 -1
- package/dist/download.js +82 -22
- package/dist/download.js.map +1 -1
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +127 -14
- package/dist/errors.js.map +1 -1
- package/dist/generated/metadata.d.ts.map +1 -1
- package/dist/generated/metadata.js +280 -141
- package/dist/generated/metadata.js.map +1 -1
- package/dist/generated/metadata.ts +280 -141
- package/dist/generated/openapi-stripped.json +6435 -4283
- package/dist/generated/path-mapping.d.ts.map +1 -1
- package/dist/generated/path-mapping.js +27 -16
- package/dist/generated/path-mapping.js.map +1 -1
- package/dist/generated/path-mapping.ts +27 -16
- package/dist/generated/schema.d.ts +4283 -2457
- package/dist/generated/services/bookmarks.d.ts +1 -1
- package/dist/generated/services/bookmarks.d.ts.map +1 -1
- package/dist/generated/services/bookmarks.ts +1 -1
- package/dist/generated/services/boosts.d.ts +10 -0
- package/dist/generated/services/boosts.d.ts.map +1 -1
- package/dist/generated/services/boosts.js +8 -0
- package/dist/generated/services/boosts.js.map +1 -1
- package/dist/generated/services/boosts.ts +12 -0
- package/dist/generated/services/campfires.d.ts +54 -40
- package/dist/generated/services/campfires.d.ts.map +1 -1
- package/dist/generated/services/campfires.js +82 -65
- package/dist/generated/services/campfires.js.map +1 -1
- package/dist/generated/services/campfires.ts +107 -85
- package/dist/generated/services/cards.d.ts +5 -0
- package/dist/generated/services/cards.d.ts.map +1 -1
- package/dist/generated/services/cards.js +4 -0
- package/dist/generated/services/cards.js.map +1 -1
- package/dist/generated/services/cards.ts +6 -0
- package/dist/generated/services/checkins.d.ts +14 -0
- package/dist/generated/services/checkins.d.ts.map +1 -1
- package/dist/generated/services/checkins.js +14 -1
- package/dist/generated/services/checkins.js.map +1 -1
- package/dist/generated/services/checkins.ts +20 -0
- package/dist/generated/services/client-approvals.d.ts +6 -3
- package/dist/generated/services/client-approvals.d.ts.map +1 -1
- package/dist/generated/services/client-approvals.js +8 -5
- package/dist/generated/services/client-approvals.js.map +1 -1
- package/dist/generated/services/client-approvals.ts +10 -5
- package/dist/generated/services/client-correspondences.d.ts +6 -3
- package/dist/generated/services/client-correspondences.d.ts.map +1 -1
- package/dist/generated/services/client-correspondences.js +8 -5
- package/dist/generated/services/client-correspondences.js.map +1 -1
- package/dist/generated/services/client-correspondences.ts +10 -5
- package/dist/generated/services/client-replies.d.ts +11 -4
- package/dist/generated/services/client-replies.d.ts.map +1 -1
- package/dist/generated/services/client-replies.js +16 -8
- package/dist/generated/services/client-replies.js.map +1 -1
- package/dist/generated/services/client-replies.ts +18 -8
- package/dist/generated/services/cloud-files.d.ts +92 -0
- package/dist/generated/services/cloud-files.d.ts.map +1 -0
- package/dist/generated/services/cloud-files.js +121 -0
- package/dist/generated/services/cloud-files.js.map +1 -0
- package/dist/generated/services/cloud-files.ts +188 -0
- package/dist/generated/services/comments.d.ts +5 -0
- package/dist/generated/services/comments.d.ts.map +1 -1
- package/dist/generated/services/comments.js +4 -0
- package/dist/generated/services/comments.js.map +1 -1
- package/dist/generated/services/comments.ts +6 -0
- package/dist/generated/services/documents.d.ts +12 -7
- package/dist/generated/services/documents.d.ts.map +1 -1
- package/dist/generated/services/documents.js +10 -6
- package/dist/generated/services/documents.js.map +1 -1
- package/dist/generated/services/documents.ts +14 -8
- package/dist/generated/services/drafts.d.ts +1 -1
- package/dist/generated/services/drafts.d.ts.map +1 -1
- package/dist/generated/services/drafts.ts +1 -1
- package/dist/generated/services/events.d.ts +5 -0
- package/dist/generated/services/events.d.ts.map +1 -1
- package/dist/generated/services/events.js +4 -0
- package/dist/generated/services/events.js.map +1 -1
- package/dist/generated/services/events.ts +6 -0
- package/dist/generated/services/everything.d.ts +14 -14
- package/dist/generated/services/everything.d.ts.map +1 -1
- package/dist/generated/services/everything.ts +14 -14
- package/dist/generated/services/folders.d.ts +96 -0
- package/dist/generated/services/folders.d.ts.map +1 -0
- package/dist/generated/services/folders.js +140 -0
- package/dist/generated/services/folders.js.map +1 -0
- package/dist/generated/services/folders.ts +201 -0
- package/dist/generated/services/forwards.d.ts +7 -20
- package/dist/generated/services/forwards.d.ts.map +1 -1
- package/dist/generated/services/forwards.js +6 -35
- package/dist/generated/services/forwards.js.map +1 -1
- package/dist/generated/services/forwards.ts +10 -48
- package/dist/generated/services/gauges.d.ts +7 -0
- package/dist/generated/services/gauges.d.ts.map +1 -1
- package/dist/generated/services/gauges.js +5 -1
- package/dist/generated/services/gauges.js.map +1 -1
- package/dist/generated/services/gauges.ts +9 -1
- package/dist/generated/services/google-documents.d.ts +98 -0
- package/dist/generated/services/google-documents.d.ts.map +1 -0
- package/dist/generated/services/google-documents.js +123 -0
- package/dist/generated/services/google-documents.js.map +1 -0
- package/dist/generated/services/google-documents.ts +196 -0
- package/dist/generated/services/index.d.ts +7 -4
- package/dist/generated/services/index.d.ts.map +1 -1
- package/dist/generated/services/index.js +7 -4
- package/dist/generated/services/index.js.map +1 -1
- package/dist/generated/services/index.ts +7 -4
- package/dist/generated/services/messages.d.ts +2 -0
- package/dist/generated/services/messages.d.ts.map +1 -1
- package/dist/generated/services/messages.js +1 -1
- package/dist/generated/services/messages.js.map +1 -1
- package/dist/generated/services/messages.ts +3 -1
- package/dist/generated/services/my-notes.d.ts +1 -1
- package/dist/generated/services/my-notes.js +1 -1
- package/dist/generated/services/my-notes.ts +1 -1
- package/dist/generated/services/my-notifications.d.ts +4 -2
- package/dist/generated/services/my-notifications.d.ts.map +1 -1
- package/dist/generated/services/my-notifications.js.map +1 -1
- package/dist/generated/services/my-notifications.ts +4 -2
- package/dist/generated/services/people.d.ts +11 -1
- package/dist/generated/services/people.d.ts.map +1 -1
- package/dist/generated/services/people.js +13 -2
- package/dist/generated/services/people.js.map +1 -1
- package/dist/generated/services/people.ts +15 -1
- package/dist/generated/services/projects.d.ts +26 -0
- package/dist/generated/services/projects.d.ts.map +1 -1
- package/dist/generated/services/projects.js +49 -1
- package/dist/generated/services/projects.js.map +1 -1
- package/dist/generated/services/projects.ts +61 -1
- package/dist/generated/services/recordings.d.ts +2 -12
- package/dist/generated/services/recordings.d.ts.map +1 -1
- package/dist/generated/services/recordings.js +1 -26
- package/dist/generated/services/recordings.js.map +1 -1
- package/dist/generated/services/recordings.ts +3 -31
- package/dist/generated/services/reports.d.ts +8 -12
- package/dist/generated/services/reports.d.ts.map +1 -1
- package/dist/generated/services/reports.js +11 -5
- package/dist/generated/services/reports.js.map +1 -1
- package/dist/generated/services/reports.ts +13 -14
- package/dist/generated/services/schedules.d.ts +94 -15
- package/dist/generated/services/schedules.d.ts.map +1 -1
- package/dist/generated/services/schedules.js +19 -8
- package/dist/generated/services/schedules.js.map +1 -1
- package/dist/generated/services/schedules.ts +107 -17
- package/dist/generated/services/search.d.ts +2 -0
- package/dist/generated/services/search.d.ts.map +1 -1
- package/dist/generated/services/search.js +1 -1
- package/dist/generated/services/search.js.map +1 -1
- package/dist/generated/services/search.ts +3 -1
- package/dist/generated/services/templates.d.ts +2 -0
- package/dist/generated/services/templates.d.ts.map +1 -1
- package/dist/generated/services/templates.js +1 -1
- package/dist/generated/services/templates.js.map +1 -1
- package/dist/generated/services/templates.ts +3 -1
- package/dist/generated/services/timeline.d.ts +2 -0
- package/dist/generated/services/timeline.d.ts.map +1 -1
- package/dist/generated/services/timeline.js +1 -0
- package/dist/generated/services/timeline.js.map +1 -1
- package/dist/generated/services/timeline.ts +3 -0
- package/dist/generated/services/timesheets.d.ts +16 -0
- package/dist/generated/services/timesheets.d.ts.map +1 -1
- package/dist/generated/services/timesheets.js +26 -2
- package/dist/generated/services/timesheets.js.map +1 -1
- package/dist/generated/services/timesheets.ts +35 -2
- package/dist/generated/services/todolist-groups.d.ts +11 -6
- package/dist/generated/services/todolist-groups.d.ts.map +1 -1
- package/dist/generated/services/todolist-groups.js +7 -3
- package/dist/generated/services/todolist-groups.js.map +1 -1
- package/dist/generated/services/todolist-groups.ts +13 -7
- package/dist/generated/services/todolists.d.ts +15 -13
- package/dist/generated/services/todolists.d.ts.map +1 -1
- package/dist/generated/services/todolists.js +11 -8
- package/dist/generated/services/todolists.js.map +1 -1
- package/dist/generated/services/todolists.ts +19 -14
- package/dist/generated/services/todos.d.ts +2 -12
- package/dist/generated/services/todos.d.ts.map +1 -1
- package/dist/generated/services/todos.js +1 -25
- package/dist/generated/services/todos.js.map +1 -1
- package/dist/generated/services/todos.ts +3 -30
- package/dist/generated/services/uploads.d.ts +5 -0
- package/dist/generated/services/uploads.d.ts.map +1 -1
- package/dist/generated/services/uploads.js +4 -0
- package/dist/generated/services/uploads.js.map +1 -1
- package/dist/generated/services/uploads.ts +6 -0
- package/dist/generated/services/vaults.d.ts +5 -0
- package/dist/generated/services/vaults.d.ts.map +1 -1
- package/dist/generated/services/vaults.js +4 -0
- package/dist/generated/services/vaults.js.map +1 -1
- package/dist/generated/services/vaults.ts +6 -0
- package/dist/index.d.ts +15 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -8
- package/dist/index.js.map +1 -1
- package/dist/pagination-utils.d.ts +46 -0
- package/dist/pagination-utils.d.ts.map +1 -1
- package/dist/pagination-utils.js +85 -2
- package/dist/pagination-utils.js.map +1 -1
- package/dist/pagination.d.ts +11 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js.map +1 -1
- package/dist/retry.d.ts +110 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +253 -0
- package/dist/retry.js.map +1 -0
- package/dist/services/base.d.ts.map +1 -1
- package/dist/services/base.js +59 -5
- package/dist/services/base.js.map +1 -1
- package/dist/services/cards-extensions.d.ts +26 -21
- package/dist/services/cards-extensions.d.ts.map +1 -1
- package/dist/services/cards-extensions.js +28 -31
- package/dist/services/cards-extensions.js.map +1 -1
- package/dist/services/documents-extensions.d.ts +122 -0
- package/dist/services/documents-extensions.d.ts.map +1 -0
- package/dist/services/documents-extensions.js +125 -0
- package/dist/services/documents-extensions.js.map +1 -0
- package/dist/services/merge-safe.d.ts +167 -0
- package/dist/services/merge-safe.d.ts.map +1 -0
- package/dist/services/merge-safe.js +217 -0
- package/dist/services/merge-safe.js.map +1 -0
- package/dist/services/schedules-extensions.d.ts +266 -0
- package/dist/services/schedules-extensions.d.ts.map +1 -0
- package/dist/services/schedules-extensions.js +268 -0
- package/dist/services/schedules-extensions.js.map +1 -0
- package/dist/services/todolists-extensions.d.ts +110 -0
- package/dist/services/todolists-extensions.d.ts.map +1 -0
- package/dist/services/todolists-extensions.js +262 -0
- package/dist/services/todolists-extensions.js.map +1 -0
- package/dist/services/todos-extensions.d.ts +12 -1
- package/dist/services/todos-extensions.d.ts.map +1 -1
- package/dist/services/todos-extensions.js +27 -8
- package/dist/services/todos-extensions.js.map +1 -1
- package/package.json +5 -4
- package/src/generated/metadata.ts +280 -141
- package/src/generated/openapi-stripped.json +6435 -4283
- package/src/generated/path-mapping.ts +27 -16
- package/src/generated/schema.d.ts +4283 -2457
- package/src/generated/services/bookmarks.ts +1 -1
- package/src/generated/services/boosts.ts +12 -0
- package/src/generated/services/campfires.ts +107 -85
- package/src/generated/services/cards.ts +6 -0
- package/src/generated/services/checkins.ts +20 -0
- package/src/generated/services/client-approvals.ts +10 -5
- package/src/generated/services/client-correspondences.ts +10 -5
- package/src/generated/services/client-replies.ts +18 -8
- package/src/generated/services/cloud-files.ts +188 -0
- package/src/generated/services/comments.ts +6 -0
- package/src/generated/services/documents.ts +14 -8
- package/src/generated/services/drafts.ts +1 -1
- package/src/generated/services/events.ts +6 -0
- package/src/generated/services/everything.ts +14 -14
- package/src/generated/services/folders.ts +201 -0
- package/src/generated/services/forwards.ts +10 -48
- package/src/generated/services/gauges.ts +9 -1
- package/src/generated/services/google-documents.ts +196 -0
- package/src/generated/services/index.ts +7 -4
- package/src/generated/services/messages.ts +3 -1
- package/src/generated/services/my-notes.ts +1 -1
- package/src/generated/services/my-notifications.ts +4 -2
- package/src/generated/services/people.ts +15 -1
- package/src/generated/services/projects.ts +61 -1
- package/src/generated/services/recordings.ts +3 -31
- package/src/generated/services/reports.ts +13 -14
- package/src/generated/services/schedules.ts +107 -17
- package/src/generated/services/search.ts +3 -1
- package/src/generated/services/templates.ts +3 -1
- package/src/generated/services/timeline.ts +3 -0
- package/src/generated/services/timesheets.ts +35 -2
- package/src/generated/services/todolist-groups.ts +13 -7
- package/src/generated/services/todolists.ts +19 -14
- package/src/generated/services/todos.ts +3 -30
- package/src/generated/services/uploads.ts +6 -0
- 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
|
|
3
|
+
* CardsService with a presence-aware `update` on top of the generated surface
|
|
4
4
|
* (`get`, `updateVerbatim`, `move`, ...).
|
|
5
5
|
*
|
|
6
|
-
* BC3 builds
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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`
|
|
12
|
-
*
|
|
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
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* round-trip
|
|
26
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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;
|
|
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"}
|