@haverstack/core 0.38.0 → 0.40.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 (76) hide show
  1. package/README.md +79 -16
  2. package/dist/access.js +1 -1
  3. package/dist/access.js.map +1 -1
  4. package/dist/adapter-entry.d.ts +2 -1
  5. package/dist/adapter-entry.d.ts.map +1 -1
  6. package/dist/adapter-entry.js +2 -1
  7. package/dist/adapter-entry.js.map +1 -1
  8. package/dist/changes.js +3 -3
  9. package/dist/combine.d.ts.map +1 -1
  10. package/dist/combine.js +2 -3
  11. package/dist/combine.js.map +1 -1
  12. package/dist/errors.d.ts +16 -3
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/errors.js +23 -2
  15. package/dist/errors.js.map +1 -1
  16. package/dist/grants.d.ts +9 -1
  17. package/dist/grants.d.ts.map +1 -1
  18. package/dist/grants.js +44 -4
  19. package/dist/grants.js.map +1 -1
  20. package/dist/index.d.ts +4 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/query-validation.d.ts +51 -1
  25. package/dist/query-validation.d.ts.map +1 -1
  26. package/dist/query-validation.js +155 -1
  27. package/dist/query-validation.js.map +1 -1
  28. package/dist/record-changes.d.ts +15 -1
  29. package/dist/record-changes.d.ts.map +1 -1
  30. package/dist/record-changes.js +33 -3
  31. package/dist/record-changes.js.map +1 -1
  32. package/dist/record-id.js +1 -1
  33. package/dist/record-id.js.map +1 -1
  34. package/dist/schema.d.ts +6 -7
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +33 -10
  37. package/dist/schema.js.map +1 -1
  38. package/dist/scoped-stack.d.ts +27 -17
  39. package/dist/scoped-stack.d.ts.map +1 -1
  40. package/dist/scoped-stack.js +111 -120
  41. package/dist/scoped-stack.js.map +1 -1
  42. package/dist/stack.d.ts +120 -44
  43. package/dist/stack.d.ts.map +1 -1
  44. package/dist/stack.js +153 -189
  45. package/dist/stack.js.map +1 -1
  46. package/dist/testing.d.ts +28 -11
  47. package/dist/testing.d.ts.map +1 -1
  48. package/dist/testing.js +42 -37
  49. package/dist/testing.js.map +1 -1
  50. package/dist/type-handle.d.ts +157 -0
  51. package/dist/type-handle.d.ts.map +1 -0
  52. package/dist/type-handle.js +131 -0
  53. package/dist/type-handle.js.map +1 -0
  54. package/dist/types.d.ts +63 -20
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/validate.d.ts +7 -0
  58. package/dist/validate.d.ts.map +1 -1
  59. package/dist/validate.js +122 -7
  60. package/dist/validate.js.map +1 -1
  61. package/dist/wire-body.d.ts +79 -0
  62. package/dist/wire-body.d.ts.map +1 -0
  63. package/dist/wire-body.js +177 -0
  64. package/dist/wire-body.js.map +1 -0
  65. package/dist/wire-entry.d.ts +4 -2
  66. package/dist/wire-entry.d.ts.map +1 -1
  67. package/dist/wire-entry.js +8 -3
  68. package/dist/wire-entry.js.map +1 -1
  69. package/dist/wire-record.d.ts.map +1 -1
  70. package/dist/wire-record.js +27 -14
  71. package/dist/wire-record.js.map +1 -1
  72. package/dist/wire-request.d.ts +52 -2
  73. package/dist/wire-request.d.ts.map +1 -1
  74. package/dist/wire-request.js +222 -23
  75. package/dist/wire-request.js.map +1 -1
  76. package/package.json +1 -1
@@ -18,11 +18,13 @@ export { isSafeAttachmentContentType, inferContentTypeFromFilename, resolveAttac
18
18
  export type { AttachmentDownloadContentType } from './attachment-download.js';
19
19
  export { firstRecordedAttachment, resolveReferencedAttachment } from './attachment-download.js';
20
20
  export type { StackTokenStore, TokenInfo, TokenSession } from './types.js';
21
- export { parseQueryParams, parseQueryBody, parseChangeParams, parseJournalParams, parseIfMatch, parseUploadFilename, parsePositiveInt, } from './wire-request.js';
21
+ export { parseQueryParams, parseQueryBody, parseChangeParams, parseJournalParams, parseVersionsParams, parseGetRecordParams, parseDeleteParams, parseDownloadParams, parseAssociationParams, parseIfMatch, parseUploadFilename, parsePositiveInt, } from './wire-request.js';
22
22
  export { assertQueryTravels } from './wire-request.js';
23
- export type { ParsedChangeParams } from './wire-request.js';
23
+ export type { ParsedChangeParams, ParsedDownloadParams, ParsedAssociationParams, } from './wire-request.js';
24
24
  export { parseDate } from './wire-request.js';
25
25
  export { createOptionsFromWireRecord, changesFromWireBody } from './wire-record.js';
26
26
  export type { WireCreateRequest } from './wire-record.js';
27
+ export { parseAssociationEditsBody, parseAuthChallengeBody, parseAuthTokenBody, parseEntityPatchBody, parseTypeBody, parseMigrationBody, } from './wire-body.js';
28
+ export type { WireAuthChallengeRequest, WireAuthTokenRequest, WireEntityPatch, WireMigrationRequest, } from './wire-body.js';
27
29
  export { isOwnerActingAlone } from './access.js';
28
30
  //# sourceMappingURL=wire-entry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wire-entry.d.ts","sourceRoot":"","sources":["../src/wire-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAK9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAK9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,oCAAoC,EACpC,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAM9E,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAIhG,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ3E,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAI5D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAK9C,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"wire-entry.d.ts","sourceRoot":"","sources":["../src/wire-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAK9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAK9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,oCAAoC,EACpC,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAM9E,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAIhG,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAS3E,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAK9C,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
@@ -27,11 +27,12 @@ export { isSafeAttachmentContentType, inferContentTypeFromFilename, resolveAttac
27
27
  export { firstRecordedAttachment, resolveReferencedAttachment } from './attachment-download.js';
28
28
  // No in-repo caller: the parse half of the request encoding adapter-api
29
29
  // builds, so a server decodes GET /records, POST /records/query,
30
- // GET /changes and GET /records/:id/journal with these rather than
31
- // transcribing the parameter table.
30
+ // GET /changes, GET /records/:id/journal, GET /records/:id/versions, GET /records/:id,
31
+ // DELETE /records/:id, GET /attachments/:fileId and GET /records/:id/associations with these
32
+ // rather than transcribing the parameter table.
32
33
  // Contract, not internal — and the round trip against the builders is
33
34
  // pinned by a test, which is what having both halves here buys.
34
- export { parseQueryParams, parseQueryBody, parseChangeParams, parseJournalParams, parseIfMatch, parseUploadFilename, parsePositiveInt, } from './wire-request.js';
35
+ export { parseQueryParams, parseQueryBody, parseChangeParams, parseJournalParams, parseVersionsParams, parseGetRecordParams, parseDeleteParams, parseDownloadParams, parseAssociationParams, parseIfMatch, parseUploadFilename, parsePositiveInt, } from './wire-request.js';
35
36
  // The build-side half of the fields that never travel: adapter-api refuses
36
37
  // them before encoding, so which encoding a server's content reach selects
37
38
  // never decides whether a family query widens or is refused.
@@ -43,6 +44,10 @@ export { parseDate } from './wire-request.js';
43
44
  // inherits which half of a record body it may trust rather than deciding
44
45
  // field by field. See docs/spec/wire-format.md § Records.
45
46
  export { createOptionsFromWireRecord, changesFromWireBody } from './wire-record.js';
47
+ // No in-repo caller: the bodies of the remaining endpoints core specifies,
48
+ // so a server refuses a key they do not define without keeping its own
49
+ // list of the ones they do. See docs/spec/wire-format.md § Unrecognized input.
50
+ export { parseAssociationEditsBody, parseAuthChallengeBody, parseAuthTokenBody, parseEntityPatchBody, parseTypeBody, parseMigrationBody, } from './wire-body.js';
46
51
  // The tier gating purge, commitMigration() and includeUnlisted, which
47
52
  // a server must decide for those routes itself. Computing it as "is the
48
53
  // owner" is a privilege bug with no symptom.
@@ -1 +1 @@
1
- {"version":3,"file":"wire-entry.js","sourceRoot":"","sources":["../src/wire-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAGnB,0EAA0E;AAC1E,0EAA0E;AAC1E,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,6DAA6D;AAC7D,gEAAgE;AAChE,oDAAoD;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,oCAAoC,EACpC,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAGlC,kEAAkE;AAClE,yEAAyE;AACzE,0EAA0E;AAC1E,8DAA8D;AAC9D,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAMhG,wEAAwE;AACxE,iEAAiE;AACjE,mEAAmE;AACnE,oCAAoC;AACpC,sEAAsE;AACtE,gEAAgE;AAChE,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,2EAA2E;AAC3E,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGvD,wEAAwE;AACxE,sCAAsC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,uEAAuE;AACvE,yEAAyE;AACzE,0DAA0D;AAC1D,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGpF,sEAAsE;AACtE,wEAAwE;AACxE,6CAA6C;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"wire-entry.js","sourceRoot":"","sources":["../src/wire-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAGnB,0EAA0E;AAC1E,0EAA0E;AAC1E,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,6DAA6D;AAC7D,gEAAgE;AAChE,oDAAoD;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,oCAAoC,EACpC,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAGlC,kEAAkE;AAClE,yEAAyE;AACzE,0EAA0E;AAC1E,8DAA8D;AAC9D,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAMhG,wEAAwE;AACxE,iEAAiE;AACjE,uFAAuF;AACvF,6FAA6F;AAC7F,gDAAgD;AAChD,sEAAsE;AACtE,gEAAgE;AAChE,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,2EAA2E;AAC3E,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAOvD,wEAAwE;AACxE,sCAAsC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,uEAAuE;AACvE,yEAAyE;AACzE,0DAA0D;AAC1D,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGpF,2EAA2E;AAC3E,uEAAuE;AACvE,+EAA+E;AAC/E,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAQxB,sEAAsE;AACtE,wEAAwE;AACxE,6CAA6C;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"wire-record.d.ts","sourceRoot":"","sources":["../src/wire-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,KAAK,EACV,QAAQ,EAGR,eAAe,EACf,YAAY,EACZ,MAAM,EACP,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,0EAA0E;IAC1E,OAAO,EAAE,IAAI,CAAC,8BAA8B,EAAE,WAAW,CAAC,CAAC;CAC5D,CAAC;AA8CF;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,YAAY,EACrB,aAAa,EAAE,QAAQ,GACtB,iBAAiB,CAoCnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAoDlE"}
1
+ {"version":3,"file":"wire-record.d.ts","sourceRoot":"","sources":["../src/wire-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,KAAK,EACV,QAAQ,EAGR,eAAe,EAEf,YAAY,EACZ,MAAM,EACP,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,0EAA0E;IAC1E,OAAO,EAAE,IAAI,CAAC,8BAA8B,EAAE,WAAW,CAAC,CAAC;CAC5D,CAAC;AAsDF;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,YAAY,EACrB,aAAa,EAAE,QAAQ,GACtB,iBAAiB,CAyCnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAoDlE"}
@@ -11,21 +11,31 @@
11
11
  * here: docs/spec/wire-format.md § Records.
12
12
  */
13
13
  import { isOwnerActingAlone } from './access.js';
14
- import { StackBadRequestError, StackValidationError } from './errors.js';
14
+ import { StackBadRequestError } from './errors.js';
15
+ import { fieldError, requireBody } from './wire-body.js';
15
16
  import { RECORD_CHANGE_SET_KEYS } from './types.js';
16
- function requireBody(body) {
17
- if (typeof body !== 'object' || body === null || Array.isArray(body))
18
- throw new StackBadRequestError('Invalid record body: expected an object');
19
- return body;
20
- }
21
17
  /**
22
- * The refusal a present field earns when its value is the wrong shape.
23
- * `StackValidationError` because the failure names a field of the record
24
- * being written, and only that class carries the path.
18
+ * Every top-level key a wire record carries — a `StackRecord`'s, so a new
19
+ * field fails to compile here until it is listed. A create body is a whole
20
+ * record, so each of these is accepted — stamped ones are then dropped —
21
+ * and anything else is refused. See docs/spec/wire-format.md § Unrecognized input.
25
22
  */
26
- function fieldError(path, message) {
27
- throw new StackValidationError([{ path, message }]);
28
- }
23
+ const WIRE_RECORD_KEYS = Object.keys({
24
+ id: true,
25
+ typeId: true,
26
+ createdAt: true,
27
+ updatedAt: true,
28
+ content: true,
29
+ version: true,
30
+ parentId: true,
31
+ appId: true,
32
+ createdBy: true,
33
+ updatedBy: true,
34
+ deletedAt: true,
35
+ unlistedAt: true,
36
+ permissions: true,
37
+ associations: true,
38
+ });
29
39
  function optionalString(body, key) {
30
40
  const value = body[key];
31
41
  if (value === undefined)
@@ -68,7 +78,10 @@ function ownerDate(body, key) {
68
78
  * would let the two answers drift.
69
79
  */
70
80
  export function createOptionsFromWireRecord(body, session, ownerEntityId) {
71
- const record = requireBody(body);
81
+ const record = requireBody(body, 'record body');
82
+ const unknown = Object.keys(record).filter((key) => !WIRE_RECORD_KEYS.includes(key));
83
+ if (unknown.length > 0)
84
+ throw new StackBadRequestError(`Unknown record key${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}`);
72
85
  const typeId = record.typeId;
73
86
  if (typeof typeId !== 'string' || typeId === '')
74
87
  throw new StackBadRequestError('Invalid record body: typeId is required');
@@ -120,7 +133,7 @@ export function createOptionsFromWireRecord(body, session, ownerEntityId) {
120
133
  * See docs/spec/wire-format.md § Records.
121
134
  */
122
135
  export function changesFromWireBody(body) {
123
- const envelope = requireBody(body);
136
+ const envelope = requireBody(body, 'record body');
124
137
  const unknown = Object.keys(envelope).filter((key) => !RECORD_CHANGE_SET_KEYS.includes(key));
125
138
  if (unknown.length > 0) {
126
139
  throw new StackBadRequestError(`Unknown change-set key${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}. ` +
@@ -1 +1 @@
1
- {"version":3,"file":"wire-record.js","sourceRoot":"","sources":["../src/wire-record.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wire-record.js","sourceRoot":"","sources":["../src/wire-record.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@
24
24
  * servers already map to 400 — see docs/spec/wire-format.md § Error
25
25
  * responses.
26
26
  */
27
- import type { ChangeFilter, JournalQuery, StackQuery } from './types.js';
27
+ import type { DataAssociation, ChangeFilter, JournalQuery, VersionsQuery, StackQuery } from './types.js';
28
28
  /** Parse an ISO date string from a wire body; undefined if absent or invalid. */
29
29
  export declare function parseDate(val: unknown): Date | undefined;
30
30
  /**
@@ -59,7 +59,9 @@ export declare function parseQueryParams(url: URL): StackQuery;
59
59
  * Build a `StackQuery` from a `POST /records/query` JSON body — the
60
60
  * superset form, which additionally carries `filter.content`. Dates arrive
61
61
  * as the ISO strings `JSON.stringify` made of them and are decoded back to
62
- * `Date`. See docs/spec/wire-format.md § Records.
62
+ * `Date`. `undefined` is the absent body; `null` or any other non-object
63
+ * is refused, so a server hands over no body as `undefined`.
64
+ * See docs/spec/wire-format.md § Records.
63
65
  */
64
66
  export declare function parseQueryBody(raw: unknown): StackQuery;
65
67
  /**
@@ -89,6 +91,54 @@ export declare function parseChangeParams(url: URL): ParsedChangeParams;
89
91
  * change feed's opaque cursor. See docs/spec/wire-format.md § Journal.
90
92
  */
91
93
  export declare function parseJournalParams(url: URL): JournalQuery;
94
+ /**
95
+ * Parse `GET /records/:id/versions`'s query params into the `VersionsQuery`
96
+ * `getVersions()` takes. Absent params stay absent, for the reason
97
+ * parseJournalParams() leaves them so.
98
+ *
99
+ * `beforeVersion` is a snapshot's `version`, exclusive.
100
+ * See docs/spec/wire-format.md § Versions.
101
+ */
102
+ export declare function parseVersionsParams(url: URL): VersionsQuery;
103
+ /**
104
+ * Parse `GET /records/:id`'s query params into the `includeDeleted` flag. A
105
+ * misspelled or non-boolean value is refused rather than read as absent.
106
+ * See docs/spec/wire-format.md § Records.
107
+ */
108
+ export declare function parseGetRecordParams(url: URL): {
109
+ includeDeleted: boolean;
110
+ };
111
+ /**
112
+ * Parse `DELETE /records/:id`'s query params into the `purge` flag. A
113
+ * misspelled or non-boolean `purge` is refused rather than read as a soft
114
+ * delete. See docs/spec/wire-format.md § Records.
115
+ */
116
+ export declare function parseDeleteParams(url: URL): {
117
+ purge: boolean;
118
+ };
119
+ /**
120
+ * The download params, in the names `resolveAttachmentDownloadContentType()`
121
+ * takes them by. An empty value stays empty rather than absent, so the
122
+ * resolution treats it exactly as it would an omitted one.
123
+ */
124
+ export type ParsedDownloadParams = {
125
+ contentTypeParam?: string;
126
+ filenameParam?: string;
127
+ };
128
+ /** Parse `GET /attachments/:fileId`'s query params. See docs/spec/wire-format.md § Download. */
129
+ export declare function parseDownloadParams(url: URL): ParsedDownloadParams;
130
+ export type ParsedAssociationParams = {
131
+ kind?: DataAssociation['kind'];
132
+ label?: string;
133
+ };
134
+ /**
135
+ * Parse `GET /records/:id/associations`' query params. An unrecognized
136
+ * `kind` is refused, since reading it as absent would answer with every
137
+ * kind. `kind` repeats on `GET /changes` but names one value here, so a
138
+ * repeat is refused rather than read as its first.
139
+ * See docs/spec/wire-format.md § Associations.
140
+ */
141
+ export declare function parseAssociationParams(url: URL): ParsedAssociationParams;
92
142
  /**
93
143
  * Parse an `If-Match: "5"` header into the version for `ifVersion`.
94
144
  * A value that is not a bare, optionally quoted version is refused rather
@@ -1 +1 @@
1
- {"version":3,"file":"wire-request.d.ts","sourceRoot":"","sources":["../src/wire-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,KAAK,EACV,YAAY,EAEZ,YAAY,EAOZ,UAAU,EACX,MAAM,YAAY,CAAC;AAMpB,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAIxD;AAQD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE;AAkGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAE1D;AA6ID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAsErD;AAaD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAmEvD;AAMD;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAiC9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,CAOzD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAK3E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAYlF"}
1
+ {"version":3,"file":"wire-request.d.ts","sourceRoot":"","sources":["../src/wire-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,YAAY,EACZ,aAAa,EAOb,UAAU,EACX,MAAM,YAAY,CAAC;AAMpB,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAIxD;AA0ED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE;AAmKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAE1D;AA6ID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAuErD;AAaD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAqEvD;AAMD;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAkC9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,CAQzD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,aAAa,CAU3D;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,CAG1E;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAG9D;AAMD;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzF,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAQlE;AAMD,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,uBAAuB,CAYxE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAK3E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAYlF"}
@@ -25,6 +25,7 @@
25
25
  * responses.
26
26
  */
27
27
  import { StackBadRequestError } from './errors.js';
28
+ import { TARGET_KEYS } from './query-validation.js';
28
29
  import { NATIVE_SORT_FIELDS } from './types.js';
29
30
  // -------------------------------------------------------
30
31
  // Shared primitives
@@ -39,8 +40,71 @@ export function parseDate(val) {
39
40
  const POSITIVE_INTEGER = /^\d+$/;
40
41
  const SORT_FIELDS = new Set(NATIVE_SORT_FIELDS);
41
42
  const SORT_DIRECTIONS = new Set(['asc', 'desc']);
42
- const CHANGE_KINDS = new Set(['created', 'changed', 'deleted', 'purged']);
43
+ const CHANGE_KINDS = new Set(['created', 'changed', 'removed', 'purged']);
44
+ const ASSOCIATION_KINDS = new Set(['tag', 'attachment', 'relationship']);
43
45
  const TARGET_KINDS = new Set(['record', 'entity', 'external']);
46
+ /** Every param `GET /records` defines. See docs/spec/wire-format.md § Records. */
47
+ const RECORD_QUERY_PARAMS = [
48
+ 'typeId',
49
+ 'parentId',
50
+ 'appId',
51
+ 'createdBySubject',
52
+ 'createdByPrincipal',
53
+ 'tag',
54
+ 'attachmentLabel',
55
+ 'attachmentFileId',
56
+ 'referencesFileId',
57
+ 'relatedTo',
58
+ 'relatedToStack',
59
+ 'relatedToEntity',
60
+ 'relatedToNs',
61
+ 'relatedToId',
62
+ 'relatedToLabel',
63
+ 'search',
64
+ 'createdBefore',
65
+ 'createdAfter',
66
+ 'updatedBefore',
67
+ 'updatedAfter',
68
+ 'includeDeleted',
69
+ 'includeUnlisted',
70
+ 'sort',
71
+ 'sortContent',
72
+ 'direction',
73
+ 'limit',
74
+ 'cursor',
75
+ ];
76
+ const QUERY_BODY_FILTER_KEYS = [
77
+ 'typeId',
78
+ 'parentId',
79
+ 'appId',
80
+ 'createdBy',
81
+ 'tags',
82
+ 'attachment',
83
+ 'referencesFileId',
84
+ 'relatedTo',
85
+ 'content',
86
+ 'contentPresent',
87
+ 'search',
88
+ 'includeDeleted',
89
+ 'includeUnlisted',
90
+ 'createdAt',
91
+ 'updatedAt',
92
+ ];
93
+ /**
94
+ * Every param `GET /changes` defines. `since` is the resume cursor, which
95
+ * the server reads itself — see parseChangeParams().
96
+ */
97
+ const CHANGE_PARAMS = [
98
+ 'typeId',
99
+ 'baseId',
100
+ 'parentId',
101
+ 'createdBySubject',
102
+ 'createdByPrincipal',
103
+ 'kind',
104
+ 'include',
105
+ 'includeUnlisted',
106
+ 'since',
107
+ ];
44
108
  /**
45
109
  * Strict positive-integer parse for a URL param — rejects "1abc", "2.7",
46
110
  * "-5". Also serves the `:version` path params, which share this
@@ -110,6 +174,57 @@ function requirePlainObject(raw, label) {
110
174
  throw new StackBadRequestError(`Invalid ${label}: expected an object`);
111
175
  return raw;
112
176
  }
177
+ /**
178
+ * A plain object carrying only `keys`. An unrecognized key is refused
179
+ * rather than ignored, since ignoring it answers a different request than
180
+ * the one sent. See docs/spec/wire-format.md § Unrecognized input.
181
+ */
182
+ function requireKnownKeys(raw, keys, label) {
183
+ const obj = requirePlainObject(raw, label);
184
+ const unknown = Object.keys(obj).filter((key) => !keys.includes(key));
185
+ if (unknown.length > 0)
186
+ throw new StackBadRequestError(`Unknown key${unknown.length > 1 ? 's' : ''} in ${label}: ${unknown.join(', ')}`);
187
+ return obj;
188
+ }
189
+ /**
190
+ * Params a filter repeats to name several values. Any other param names
191
+ * one value, so a repeat of it is refused rather than read as its first.
192
+ */
193
+ const REPEATABLE_PARAMS = new Set([
194
+ 'typeId',
195
+ 'baseId',
196
+ 'appId',
197
+ 'createdBySubject',
198
+ 'createdByPrincipal',
199
+ 'tag',
200
+ 'kind',
201
+ ]);
202
+ /** The URL-param form of requireKnownKeys(), which also refuses a repeat. */
203
+ function requireKnownParams(url, names) {
204
+ const present = [...new Set(url.searchParams.keys())];
205
+ const unknown = present.filter((name) => !names.includes(name));
206
+ if (unknown.length > 0)
207
+ throw new StackBadRequestError(`Unknown query param${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}`);
208
+ const repeated = present.filter((name) => !REPEATABLE_PARAMS.has(name) && url.searchParams.getAll(name).length > 1);
209
+ if (repeated.length > 0)
210
+ throw new StackBadRequestError(`Repeated query param${repeated.length > 1 ? 's' : ''}: ${repeated.join(', ')}`);
211
+ }
212
+ /** A boolean URL param: absent is false, and only `true`/`false` are values. */
213
+ function booleanParam(url, name) {
214
+ const value = url.searchParams.get(name);
215
+ if (value === null || value === 'false')
216
+ return false;
217
+ if (value === 'true')
218
+ return true;
219
+ throw new StackBadRequestError(`Invalid ${name}: expected true or false, got "${value}"`);
220
+ }
221
+ function optionalBoolean(raw, label) {
222
+ if (raw === undefined)
223
+ return undefined;
224
+ if (typeof raw !== 'boolean')
225
+ throw new StackBadRequestError(`Invalid ${label}: expected a boolean`);
226
+ return raw;
227
+ }
113
228
  // -------------------------------------------------------
114
229
  // Filter fields that never travel
115
230
  // -------------------------------------------------------
@@ -156,9 +271,11 @@ export function assertQueryTravels(query) {
156
271
  * a kind it does not recognize.
157
272
  */
158
273
  function parseRelatedToTarget(raw) {
159
- const t = requirePlainObject(raw, 'filter.relatedTo.target');
160
- if (typeof t.kind !== 'string' || !TARGET_KINDS.has(t.kind))
161
- throw new StackBadRequestError(`Invalid filter.relatedTo.target.kind: ${JSON.stringify(t.kind)}`);
274
+ const label = 'filter.relatedTo.target';
275
+ const kind = requirePlainObject(raw, label).kind;
276
+ if (typeof kind !== 'string' || !TARGET_KINDS.has(kind))
277
+ throw new StackBadRequestError(`Invalid filter.relatedTo.target.kind: ${JSON.stringify(kind)}`);
278
+ const t = requireKnownKeys(raw, TARGET_KEYS[kind], label);
162
279
  if (t.kind === 'record') {
163
280
  return {
164
281
  kind: 'record',
@@ -182,7 +299,7 @@ function parseRelatedToTarget(raw) {
182
299
  }
183
300
  /** A label, a target, or both — the body form of the same filter. */
184
301
  function parseRelatedToBody(raw) {
185
- const r = requirePlainObject(raw, 'filter.relatedTo');
302
+ const r = requireKnownKeys(raw, ['label', 'target'], 'filter.relatedTo');
186
303
  const label = r.label !== undefined ? requireString(r.label, 'filter.relatedTo.label') : undefined;
187
304
  const target = r.target !== undefined ? parseRelatedToTarget(r.target) : undefined;
188
305
  return {
@@ -253,7 +370,7 @@ function parseAttachmentParams(url) {
253
370
  };
254
371
  }
255
372
  function parseAttachmentBody(raw) {
256
- const a = requirePlainObject(raw, 'filter.attachment');
373
+ const a = requireKnownKeys(raw, ['label', 'fileId'], 'filter.attachment');
257
374
  return {
258
375
  ...(a.label !== undefined && { label: requireString(a.label, 'filter.attachment.label') }),
259
376
  ...(a.fileId !== undefined && { fileId: requireString(a.fileId, 'filter.attachment.fileId') }),
@@ -284,6 +401,7 @@ function parseCreatedByParams(url) {
284
401
  */
285
402
  export function parseQueryParams(url) {
286
403
  assertNoUntravelableFields(url.searchParams.has('baseId'), url.searchParams.has('presentAt'));
404
+ requireKnownParams(url, RECORD_QUERY_PARAMS);
287
405
  const filter = {};
288
406
  const typeIds = url.searchParams.getAll('typeId');
289
407
  if (typeIds.length)
@@ -328,9 +446,9 @@ export function parseQueryParams(url) {
328
446
  ...(updatedAfter && { after: requireDate(updatedAfter, 'updatedAfter') }),
329
447
  };
330
448
  }
331
- if (url.searchParams.get('includeDeleted') === 'true')
449
+ if (booleanParam(url, 'includeDeleted'))
332
450
  filter.includeDeleted = true;
333
- if (url.searchParams.get('includeUnlisted') === 'true')
451
+ if (booleanParam(url, 'includeUnlisted'))
334
452
  filter.includeUnlisted = true;
335
453
  const query = {};
336
454
  if (Object.keys(filter).length)
@@ -359,14 +477,19 @@ function parseLimitValue(raw) {
359
477
  * Build a `StackQuery` from a `POST /records/query` JSON body — the
360
478
  * superset form, which additionally carries `filter.content`. Dates arrive
361
479
  * as the ISO strings `JSON.stringify` made of them and are decoded back to
362
- * `Date`. See docs/spec/wire-format.md § Records.
480
+ * `Date`. `undefined` is the absent body; `null` or any other non-object
481
+ * is refused, so a server hands over no body as `undefined`.
482
+ * See docs/spec/wire-format.md § Records.
363
483
  */
364
484
  export function parseQueryBody(raw) {
365
- if (!raw || typeof raw !== 'object')
485
+ if (raw === undefined)
366
486
  return {};
367
- const body = raw;
487
+ const body = requirePlainObject(raw, 'query body');
368
488
  const f = body.filter !== undefined ? requirePlainObject(body.filter, 'filter') : undefined;
369
489
  assertNoUntravelableFields(f !== undefined && 'baseId' in f, 'presentAt' in body);
490
+ requireKnownKeys(body, ['filter', 'sort', 'limit', 'cursor'], 'query body');
491
+ if (f)
492
+ requireKnownKeys(f, QUERY_BODY_FILTER_KEYS, 'filter');
370
493
  const query = {};
371
494
  if (f) {
372
495
  const filter = {};
@@ -377,7 +500,7 @@ export function parseQueryBody(raw) {
377
500
  if (f.appId !== undefined)
378
501
  filter.appId = requireStringOrArray(f.appId, 'filter.appId');
379
502
  if (f.createdBy !== undefined) {
380
- const by = requirePlainObject(f.createdBy, 'filter.createdBy');
503
+ const by = requireKnownKeys(f.createdBy, ['subjectId', 'principalId'], 'filter.createdBy');
381
504
  filter.createdBy = {
382
505
  ...(by.subjectId !== undefined && {
383
506
  subjectId: requireStringOrArray(by.subjectId, 'filter.createdBy.subjectId'),
@@ -401,19 +524,19 @@ export function parseQueryBody(raw) {
401
524
  filter.contentPresent = requireStringArray(f.contentPresent, 'filter.contentPresent');
402
525
  if (f.search !== undefined)
403
526
  filter.search = requireString(f.search, 'filter.search');
404
- if (f.includeDeleted)
527
+ if (optionalBoolean(f.includeDeleted, 'filter.includeDeleted'))
405
528
  filter.includeDeleted = true;
406
- if (f.includeUnlisted)
529
+ if (optionalBoolean(f.includeUnlisted, 'filter.includeUnlisted'))
407
530
  filter.includeUnlisted = true;
408
- if (f.createdAt) {
409
- const r = requirePlainObject(f.createdAt, 'filter.createdAt');
531
+ if (f.createdAt !== undefined) {
532
+ const r = requireKnownKeys(f.createdAt, ['before', 'after'], 'filter.createdAt');
410
533
  filter.createdAt = {
411
534
  ...(r.before !== undefined && { before: requireDate(r.before, 'filter.createdAt.before') }),
412
535
  ...(r.after !== undefined && { after: requireDate(r.after, 'filter.createdAt.after') }),
413
536
  };
414
537
  }
415
- if (f.updatedAt) {
416
- const r = requirePlainObject(f.updatedAt, 'filter.updatedAt');
538
+ if (f.updatedAt !== undefined) {
539
+ const r = requireKnownKeys(f.updatedAt, ['before', 'after'], 'filter.updatedAt');
417
540
  filter.updatedAt = {
418
541
  ...(r.before !== undefined && { before: requireDate(r.before, 'filter.updatedAt.before') }),
419
542
  ...(r.after !== undefined && { after: requireDate(r.after, 'filter.updatedAt.after') }),
@@ -421,8 +544,8 @@ export function parseQueryBody(raw) {
421
544
  }
422
545
  query.filter = filter;
423
546
  }
424
- if (body.sort) {
425
- const s = requirePlainObject(body.sort, 'sort');
547
+ if (body.sort !== undefined) {
548
+ const s = requireKnownKeys(body.sort, ['field', 'contentField', 'direction'], 'sort');
426
549
  const sort = buildSort(s.field, s.contentField, s.direction);
427
550
  if (!sort)
428
551
  throw new StackBadRequestError('Invalid sort: expected a field or a contentField.');
@@ -430,8 +553,8 @@ export function parseQueryBody(raw) {
430
553
  }
431
554
  if (body.limit !== undefined)
432
555
  query.limit = parseLimitValue(body.limit);
433
- if (typeof body.cursor === 'string')
434
- query.cursor = body.cursor;
556
+ if (body.cursor !== undefined)
557
+ query.cursor = requireString(body.cursor, 'cursor');
435
558
  return query;
436
559
  }
437
560
  /**
@@ -440,6 +563,7 @@ export function parseQueryBody(raw) {
440
563
  * the server's own resumption machinery rather than request encoding.
441
564
  */
442
565
  export function parseChangeParams(url) {
566
+ requireKnownParams(url, CHANGE_PARAMS);
443
567
  const filter = {};
444
568
  const typeIds = url.searchParams.getAll('typeId');
445
569
  if (typeIds.length)
@@ -467,7 +591,7 @@ export function parseChangeParams(url) {
467
591
  return {
468
592
  filter,
469
593
  includeRecords: include === 'record',
470
- includeUnlisted: url.searchParams.get('includeUnlisted') === 'true',
594
+ includeUnlisted: booleanParam(url, 'includeUnlisted'),
471
595
  };
472
596
  }
473
597
  /**
@@ -480,6 +604,7 @@ export function parseChangeParams(url) {
480
604
  * change feed's opaque cursor. See docs/spec/wire-format.md § Journal.
481
605
  */
482
606
  export function parseJournalParams(url) {
607
+ requireKnownParams(url, ['afterSeq', 'limit']);
483
608
  const query = {};
484
609
  const afterSeq = url.searchParams.get('afterSeq');
485
610
  if (afterSeq !== null)
@@ -489,6 +614,80 @@ export function parseJournalParams(url) {
489
614
  query.limit = parsePositiveInt(limit, 'limit');
490
615
  return query;
491
616
  }
617
+ /**
618
+ * Parse `GET /records/:id/versions`'s query params into the `VersionsQuery`
619
+ * `getVersions()` takes. Absent params stay absent, for the reason
620
+ * parseJournalParams() leaves them so.
621
+ *
622
+ * `beforeVersion` is a snapshot's `version`, exclusive.
623
+ * See docs/spec/wire-format.md § Versions.
624
+ */
625
+ export function parseVersionsParams(url) {
626
+ requireKnownParams(url, ['beforeVersion', 'limit']);
627
+ const query = {};
628
+ const beforeVersion = url.searchParams.get('beforeVersion');
629
+ if (beforeVersion !== null) {
630
+ query.beforeVersion = parsePositiveInt(beforeVersion, 'beforeVersion');
631
+ }
632
+ const limit = url.searchParams.get('limit');
633
+ if (limit !== null)
634
+ query.limit = parsePositiveInt(limit, 'limit');
635
+ return query;
636
+ }
637
+ // -------------------------------------------------------
638
+ // GET /records/:id
639
+ // -------------------------------------------------------
640
+ /**
641
+ * Parse `GET /records/:id`'s query params into the `includeDeleted` flag. A
642
+ * misspelled or non-boolean value is refused rather than read as absent.
643
+ * See docs/spec/wire-format.md § Records.
644
+ */
645
+ export function parseGetRecordParams(url) {
646
+ requireKnownParams(url, ['includeDeleted']);
647
+ return { includeDeleted: booleanParam(url, 'includeDeleted') };
648
+ }
649
+ // -------------------------------------------------------
650
+ // DELETE /records/:id
651
+ // -------------------------------------------------------
652
+ /**
653
+ * Parse `DELETE /records/:id`'s query params into the `purge` flag. A
654
+ * misspelled or non-boolean `purge` is refused rather than read as a soft
655
+ * delete. See docs/spec/wire-format.md § Records.
656
+ */
657
+ export function parseDeleteParams(url) {
658
+ requireKnownParams(url, ['purge']);
659
+ return { purge: booleanParam(url, 'purge') };
660
+ }
661
+ /** Parse `GET /attachments/:fileId`'s query params. See docs/spec/wire-format.md § Download. */
662
+ export function parseDownloadParams(url) {
663
+ requireKnownParams(url, ['contentType', 'filename']);
664
+ const contentType = url.searchParams.get('contentType');
665
+ const filename = url.searchParams.get('filename');
666
+ return {
667
+ ...(contentType !== null && { contentTypeParam: contentType }),
668
+ ...(filename !== null && { filenameParam: filename }),
669
+ };
670
+ }
671
+ /**
672
+ * Parse `GET /records/:id/associations`' query params. An unrecognized
673
+ * `kind` is refused, since reading it as absent would answer with every
674
+ * kind. `kind` repeats on `GET /changes` but names one value here, so a
675
+ * repeat is refused rather than read as its first.
676
+ * See docs/spec/wire-format.md § Associations.
677
+ */
678
+ export function parseAssociationParams(url) {
679
+ requireKnownParams(url, ['kind', 'label']);
680
+ if (url.searchParams.getAll('kind').length > 1)
681
+ throw new StackBadRequestError('Repeated query param: kind');
682
+ const kind = url.searchParams.get('kind');
683
+ if (kind !== null && !ASSOCIATION_KINDS.has(kind))
684
+ throw new StackBadRequestError(`Invalid kind: "${kind}"`);
685
+ const label = url.searchParams.get('label');
686
+ return {
687
+ ...(kind !== null && { kind: kind }),
688
+ ...(label !== null && { label }),
689
+ };
690
+ }
492
691
  // -------------------------------------------------------
493
692
  // Headers
494
693
  // -------------------------------------------------------