@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.
- package/README.md +79 -16
- package/dist/access.js +1 -1
- package/dist/access.js.map +1 -1
- package/dist/adapter-entry.d.ts +2 -1
- package/dist/adapter-entry.d.ts.map +1 -1
- package/dist/adapter-entry.js +2 -1
- package/dist/adapter-entry.js.map +1 -1
- package/dist/changes.js +3 -3
- package/dist/combine.d.ts.map +1 -1
- package/dist/combine.js +2 -3
- package/dist/combine.js.map +1 -1
- package/dist/errors.d.ts +16 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +23 -2
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +9 -1
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +44 -4
- package/dist/grants.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/query-validation.d.ts +51 -1
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +155 -1
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +15 -1
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +33 -3
- package/dist/record-changes.js.map +1 -1
- package/dist/record-id.js +1 -1
- package/dist/record-id.js.map +1 -1
- package/dist/schema.d.ts +6 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +33 -10
- package/dist/schema.js.map +1 -1
- package/dist/scoped-stack.d.ts +27 -17
- package/dist/scoped-stack.d.ts.map +1 -1
- package/dist/scoped-stack.js +111 -120
- package/dist/scoped-stack.js.map +1 -1
- package/dist/stack.d.ts +120 -44
- package/dist/stack.d.ts.map +1 -1
- package/dist/stack.js +153 -189
- package/dist/stack.js.map +1 -1
- package/dist/testing.d.ts +28 -11
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +42 -37
- package/dist/testing.js.map +1 -1
- package/dist/type-handle.d.ts +157 -0
- package/dist/type-handle.d.ts.map +1 -0
- package/dist/type-handle.js +131 -0
- package/dist/type-handle.js.map +1 -0
- package/dist/types.d.ts +63 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate.d.ts +7 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +122 -7
- package/dist/validate.js.map +1 -1
- package/dist/wire-body.d.ts +79 -0
- package/dist/wire-body.d.ts.map +1 -0
- package/dist/wire-body.js +177 -0
- package/dist/wire-body.js.map +1 -0
- package/dist/wire-entry.d.ts +4 -2
- package/dist/wire-entry.d.ts.map +1 -1
- package/dist/wire-entry.js +8 -3
- package/dist/wire-entry.js.map +1 -1
- package/dist/wire-record.d.ts.map +1 -1
- package/dist/wire-record.js +27 -14
- package/dist/wire-record.js.map +1 -1
- package/dist/wire-request.d.ts +52 -2
- package/dist/wire-request.d.ts.map +1 -1
- package/dist/wire-request.js +222 -23
- package/dist/wire-request.js.map +1 -1
- package/package.json +1 -1
package/dist/wire-entry.d.ts
CHANGED
|
@@ -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
|
package/dist/wire-entry.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/wire-entry.js
CHANGED
|
@@ -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
|
|
31
|
-
//
|
|
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.
|
package/dist/wire-entry.js.map
CHANGED
|
@@ -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,
|
|
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;
|
|
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"}
|
package/dist/wire-record.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
|
|
27
|
-
|
|
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(', ')}. ` +
|
package/dist/wire-record.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire-record.js","sourceRoot":"","sources":["../src/wire-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"wire-record.js","sourceRoot":"","sources":["../src/wire-record.ts"],"names":[],"mappings":"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"}
|
package/dist/wire-request.d.ts
CHANGED
|
@@ -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`.
|
|
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;
|
|
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"}
|
package/dist/wire-request.js
CHANGED
|
@@ -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', '
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
449
|
+
if (booleanParam(url, 'includeDeleted'))
|
|
332
450
|
filter.includeDeleted = true;
|
|
333
|
-
if (url
|
|
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`.
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
// -------------------------------------------------------
|