@haverstack/core 0.36.0 → 0.38.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 +19 -15
- package/dist/access.d.ts +4 -6
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +7 -8
- package/dist/access.js.map +1 -1
- package/dist/adapter-entry.d.ts +7 -6
- package/dist/adapter-entry.d.ts.map +1 -1
- package/dist/adapter-entry.js +6 -5
- package/dist/adapter-entry.js.map +1 -1
- package/dist/attachment-download.d.ts +3 -1
- package/dist/attachment-download.d.ts.map +1 -1
- package/dist/attachment-download.js +1 -1
- package/dist/attachment-download.js.map +1 -1
- package/dist/changes.d.ts +22 -16
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +43 -32
- package/dist/changes.js.map +1 -1
- package/dist/combine.d.ts +1 -1
- package/dist/combine.js +6 -6
- package/dist/combine.js.map +1 -1
- package/dist/errors.d.ts +24 -12
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +32 -17
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +15 -24
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +14 -13
- package/dist/grants.js.map +1 -1
- package/dist/identity-bindings.js +2 -2
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/query-validation.d.ts +11 -11
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +41 -36
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +11 -10
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +33 -25
- package/dist/record-changes.js.map +1 -1
- package/dist/record-id.d.ts +3 -3
- package/dist/record-id.js +8 -8
- package/dist/record-id.js.map +1 -1
- package/dist/scoped-stack.d.ts +78 -33
- package/dist/scoped-stack.d.ts.map +1 -1
- package/dist/scoped-stack.js +119 -89
- package/dist/scoped-stack.js.map +1 -1
- package/dist/stack-reads.d.ts +2 -2
- package/dist/stack-reads.js +5 -5
- package/dist/stack-reads.js.map +1 -1
- package/dist/stack.d.ts +162 -154
- package/dist/stack.d.ts.map +1 -1
- package/dist/stack.js +307 -290
- package/dist/stack.js.map +1 -1
- package/dist/testing.d.ts +17 -17
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +46 -42
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +169 -143
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/wire-entry.d.ts +5 -6
- package/dist/wire-entry.d.ts.map +1 -1
- package/dist/wire-entry.js +5 -11
- package/dist/wire-entry.js.map +1 -1
- package/dist/wire-record.d.ts +4 -4
- package/dist/wire-record.d.ts.map +1 -1
- package/dist/wire-record.js +10 -10
- package/dist/wire-record.js.map +1 -1
- package/dist/wire-request.d.ts +2 -2
- package/dist/wire-request.d.ts.map +1 -1
- package/dist/wire-request.js +103 -61
- package/dist/wire-request.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* See docs/spec/data-model.md § Capability-gated filters.
|
|
18
18
|
*/
|
|
19
19
|
import type { ValidationError } from './validate.js';
|
|
20
|
-
import type { Association, AuthorityAssociation, DataAssociation, JournalQuery, QuerySort, RecordFilter,
|
|
20
|
+
import type { Association, AuthorityAssociation, DataAssociation, JournalQuery, QuerySort, RecordFilter, StackCapabilities } from './types.js';
|
|
21
21
|
/**
|
|
22
22
|
* Fail loud rather than silently widen: a filter the adapter can't honor
|
|
23
23
|
* would otherwise be dropped, returning an unfiltered superset presented
|
|
@@ -25,7 +25,7 @@ import type { Association, AuthorityAssociation, DataAssociation, JournalQuery,
|
|
|
25
25
|
* APIAdapter.queryRecords(). See docs/spec/data-model.md
|
|
26
26
|
* § Capability-gated filters.
|
|
27
27
|
*/
|
|
28
|
-
export declare function assertQueryCapabilities(filter: RecordFilter | undefined, capabilities: Pick<
|
|
28
|
+
export declare function assertQueryCapabilities(filter: RecordFilter | undefined, capabilities: Pick<StackCapabilities, 'filter'>): void;
|
|
29
29
|
/**
|
|
30
30
|
* Whether a single-segment content filter can be pushed down to the
|
|
31
31
|
* adapter. Callers that read a content field pair this with an in-memory
|
|
@@ -33,7 +33,7 @@ export declare function assertQueryCapabilities(filter: RecordFilter | undefined
|
|
|
33
33
|
* adapter that reaches no content at all — the rung comparison lives here
|
|
34
34
|
* rather than at each of them.
|
|
35
35
|
*/
|
|
36
|
-
export declare function filtersContent(features: Pick<
|
|
36
|
+
export declare function filtersContent(features: Pick<StackCapabilities, 'filter'>): boolean;
|
|
37
37
|
/**
|
|
38
38
|
* Split a content filter key into path segments. Write-time validation
|
|
39
39
|
* keeps stored field names free of the path metacharacters, but a filter
|
|
@@ -60,12 +60,12 @@ export declare function assertValidSort(sort: QuerySort | undefined): void;
|
|
|
60
60
|
* assertQueryCapabilities(), split from it because a sort is not a filter
|
|
61
61
|
* — see docs/spec/data-model.md § Capability-gated filters.
|
|
62
62
|
*/
|
|
63
|
-
export declare function assertSortCapability(sort: QuerySort | undefined, capabilities: Pick<
|
|
63
|
+
export declare function assertSortCapability(sort: QuerySort | undefined, capabilities: Pick<StackCapabilities, 'sort'>): void;
|
|
64
64
|
/**
|
|
65
65
|
* Reject a relationship target outside the closed set the types promise.
|
|
66
66
|
* A discriminated union is not a runtime guard — a server mapping a
|
|
67
67
|
* request body onto an association supplies raw JSON — and an
|
|
68
|
-
* unrecognized
|
|
68
|
+
* unrecognized kind would otherwise be stored under the one arm that
|
|
69
69
|
* names a Record in this stack. See docs/spec/data-model.md
|
|
70
70
|
* § Relationship targets.
|
|
71
71
|
*/
|
|
@@ -102,13 +102,13 @@ export declare function assertAuthorityAssociations(permissions: readonly Associ
|
|
|
102
102
|
*/
|
|
103
103
|
export declare function validateAssociations(associations: Association[] | undefined, path?: string): ValidationError[];
|
|
104
104
|
/**
|
|
105
|
-
* Reject
|
|
106
|
-
*
|
|
107
|
-
* always present; without the runtime check a filter
|
|
108
|
-
* request could arrive empty and match every record carrying
|
|
109
|
-
*
|
|
105
|
+
* Reject an association filter that names neither of its halves, or whose
|
|
106
|
+
* relationship target is malformed. `RelatedToFilter` and `AttachmentFilter`
|
|
107
|
+
* promise one half is always present; without the runtime check a filter
|
|
108
|
+
* decoded from a request could arrive empty and match every record carrying
|
|
109
|
+
* any association of that kind. See docs/spec/data-model.md § Filter.
|
|
110
110
|
*/
|
|
111
|
-
export declare function
|
|
111
|
+
export declare function assertValidAssociationFilters(filter: RecordFilter | undefined): void;
|
|
112
112
|
/**
|
|
113
113
|
* Hold a journal window to the shape both adapters can honor identically.
|
|
114
114
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-validation.d.ts","sourceRoot":"","sources":["../src/query-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,YAAY,EAEZ,SAAS,EACT,YAAY,EAGZ,
|
|
1
|
+
{"version":3,"file":"query-validation.d.ts","sourceRoot":"","sources":["../src/query-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,YAAY,EAEZ,SAAS,EACT,YAAY,EAGZ,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,YAAY,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,GAC9C,IAAI,CAmCN;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAEnF;AAKD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwB3D;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CA6BjE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,YAAY,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAC5C,IAAI,CAmBN;AA0CD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,EACxB,IAAI,SAAgB,GACnB,eAAe,EAAE,CAmBnB;AAoED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,IAAI,eAAe,EAAE,CAO3C;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,SAAS,WAAW,EAAE,EACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,IAAI,oBAAoB,EAAE,CAO/C;AAED,kEAAkE;AAClE;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,GAAG,SAAS,EACvC,IAAI,SAAiB,GACpB,eAAe,EAAE,CAenB;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAmBpF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAW7E"}
|
package/dist/query-validation.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* the query — one rule, one answer.
|
|
17
17
|
* See docs/spec/data-model.md § Capability-gated filters.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { StackBadRequestError } from './errors.js';
|
|
20
20
|
import { associationEqual, isAuthorityAssociation } from './record-changes.js';
|
|
21
21
|
import { CONTENT_SEGMENT_METACHARACTERS, SEGMENT_METACHARACTER_RE } from './validate.js';
|
|
22
22
|
import { NATIVE_SORT_FIELDS } from './types.js';
|
|
@@ -30,7 +30,7 @@ import { NATIVE_SORT_FIELDS } from './types.js';
|
|
|
30
30
|
export function assertQueryCapabilities(filter, capabilities) {
|
|
31
31
|
const { content: reach, contentPresent, search } = capabilities.filter;
|
|
32
32
|
if (filter?.search && !search) {
|
|
33
|
-
throw new
|
|
33
|
+
throw new StackBadRequestError('Query uses filter.search, but this adapter does not declare the filter.search capability.', 'filter.search');
|
|
34
34
|
}
|
|
35
35
|
const present = filter?.contentPresent?.length ? filter.contentPresent : undefined;
|
|
36
36
|
if (!filter?.content && !present)
|
|
@@ -38,16 +38,16 @@ export function assertQueryCapabilities(filter, capabilities) {
|
|
|
38
38
|
if (reach === 'none') {
|
|
39
39
|
// Named as `filter.content` whichever key the query used: the reach
|
|
40
40
|
// is what is absent, and it is the entry to look at in discovery.
|
|
41
|
-
throw new
|
|
41
|
+
throw new StackBadRequestError(`Query uses ${present && !filter?.content ? 'filter.contentPresent' : 'filter.content'}, ` +
|
|
42
42
|
'but this adapter declares filter.content: "none".', 'filter.content');
|
|
43
43
|
}
|
|
44
44
|
if (present && !contentPresent) {
|
|
45
|
-
throw new
|
|
45
|
+
throw new StackBadRequestError('Query uses filter.contentPresent, but this adapter does not declare the ' +
|
|
46
46
|
'filter.contentPresent capability.', 'filter.contentPresent');
|
|
47
47
|
}
|
|
48
48
|
for (const key of [...Object.keys(filter?.content ?? {}), ...(present ?? [])]) {
|
|
49
49
|
if (parseContentFilterKey(key).length > 1 && reach !== 'path') {
|
|
50
|
-
throw new
|
|
50
|
+
throw new StackBadRequestError(`Query uses the nested content path "${key}", but this adapter declares ` +
|
|
51
51
|
`filter.content: "${reach}".`, 'filter.content');
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -76,14 +76,14 @@ export function parseContentFilterKey(key) {
|
|
|
76
76
|
// 64-table join limit, so a path past the cap is one the engine could
|
|
77
77
|
// not execute. See docs/spec/data-model.md § Nested content paths.
|
|
78
78
|
if (segments.length > MAX_CONTENT_PATH_SEGMENTS) {
|
|
79
|
-
throw new
|
|
79
|
+
throw new StackBadRequestError(`Invalid content filter path "${key}": at most ${MAX_CONTENT_PATH_SEGMENTS} segments.`);
|
|
80
80
|
}
|
|
81
81
|
for (const segment of segments) {
|
|
82
82
|
if (segment === '') {
|
|
83
|
-
throw new
|
|
83
|
+
throw new StackBadRequestError(`Invalid content filter path "${key}": a path segment cannot be empty.`);
|
|
84
84
|
}
|
|
85
85
|
if (SEGMENT_METACHARACTER_RE.test(segment)) {
|
|
86
|
-
throw new
|
|
86
|
+
throw new StackBadRequestError(`Invalid content filter path "${key}": a segment cannot contain any of ` +
|
|
87
87
|
`${CONTENT_SEGMENT_METACHARACTERS.join(' ')}.`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -113,10 +113,10 @@ export function assertValidSort(sort) {
|
|
|
113
113
|
if (!sort)
|
|
114
114
|
return;
|
|
115
115
|
if (sort.field !== undefined && sort.contentField !== undefined) {
|
|
116
|
-
throw new
|
|
116
|
+
throw new StackBadRequestError('A sort names either a native field or a content field, never both.');
|
|
117
117
|
}
|
|
118
118
|
if (sort.field !== undefined && !VALID_SORT_FIELDS.has(sort.field)) {
|
|
119
|
-
throw new
|
|
119
|
+
throw new StackBadRequestError(`Invalid sort field "${sort.field}": expected one of ${NATIVE_SORT_FIELDS.join(', ')}.`);
|
|
120
120
|
}
|
|
121
121
|
if (sort.contentField !== undefined) {
|
|
122
122
|
// Parsed by the same rule a filter key is, so a name a filter could
|
|
@@ -125,11 +125,11 @@ export function assertValidSort(sort) {
|
|
|
125
125
|
// position to order its record by (docs/spec/data-model.md
|
|
126
126
|
// § Sorting by a content field).
|
|
127
127
|
if (parseContentFilterKey(sort.contentField).length > 1) {
|
|
128
|
-
throw new
|
|
128
|
+
throw new StackBadRequestError(`Invalid sort content field "${sort.contentField}": sorting reaches top-level fields only.`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
if (sort.direction !== undefined && !VALID_SORT_DIRECTIONS.has(sort.direction)) {
|
|
132
|
-
throw new
|
|
132
|
+
throw new StackBadRequestError(`Invalid sort direction "${sort.direction}": expected "asc" or "desc".`);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
@@ -144,18 +144,18 @@ export function assertSortCapability(sort, capabilities) {
|
|
|
144
144
|
return;
|
|
145
145
|
if (sort.contentField !== undefined) {
|
|
146
146
|
if (!capabilities.sort.contentField) {
|
|
147
|
-
throw new
|
|
147
|
+
throw new StackBadRequestError('Query uses sort.contentField, but this adapter does not declare the sort.contentField ' +
|
|
148
148
|
'capability.', 'sort.contentField');
|
|
149
149
|
}
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
const field = sort.field ?? 'createdAt';
|
|
153
153
|
if (!capabilities.sort.fields.includes(field)) {
|
|
154
|
-
throw new
|
|
154
|
+
throw new StackBadRequestError(`Query sorts by "${field}", which this adapter does not declare in sort.fields.`, 'sort.fields');
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
/** The identifier spaces a relationship target may name. */
|
|
158
|
-
const
|
|
158
|
+
const TARGET_KINDS = new Set(['record', 'entity', 'external']);
|
|
159
159
|
/**
|
|
160
160
|
* Collect what makes a relationship target malformed. Absence is
|
|
161
161
|
* meaningful on `stackUrl` and an external `id` — this stack, and the
|
|
@@ -167,10 +167,10 @@ function targetErrors(target, path, opts = {}) {
|
|
|
167
167
|
const fail = (message) => [{ path, message }];
|
|
168
168
|
if (!target || typeof target !== 'object')
|
|
169
169
|
return fail('A relationship target must be an object.');
|
|
170
|
-
if (!
|
|
171
|
-
return fail(`Unknown relationship target
|
|
170
|
+
if (!TARGET_KINDS.has(target.kind)) {
|
|
171
|
+
return fail(`Unknown relationship target kind "${target.kind}": expected "record", "entity" or "external".`);
|
|
172
172
|
}
|
|
173
|
-
if (target.
|
|
173
|
+
if (target.kind === 'record') {
|
|
174
174
|
if (!target.recordId)
|
|
175
175
|
return fail('A record target requires a non-empty recordId.');
|
|
176
176
|
if (target.stackUrl !== undefined && !target.stackUrl) {
|
|
@@ -178,7 +178,7 @@ function targetErrors(target, path, opts = {}) {
|
|
|
178
178
|
}
|
|
179
179
|
return [];
|
|
180
180
|
}
|
|
181
|
-
if (target.
|
|
181
|
+
if (target.kind === 'entity') {
|
|
182
182
|
return target.entityId ? [] : fail('An entity target requires a non-empty entityId.');
|
|
183
183
|
}
|
|
184
184
|
if (!target.ns)
|
|
@@ -192,7 +192,7 @@ function targetErrors(target, path, opts = {}) {
|
|
|
192
192
|
* Reject a relationship target outside the closed set the types promise.
|
|
193
193
|
* A discriminated union is not a runtime guard — a server mapping a
|
|
194
194
|
* request body onto an association supplies raw JSON — and an
|
|
195
|
-
* unrecognized
|
|
195
|
+
* unrecognized kind would otherwise be stored under the one arm that
|
|
196
196
|
* names a Record in this stack. See docs/spec/data-model.md
|
|
197
197
|
* § Relationship targets.
|
|
198
198
|
*/
|
|
@@ -225,7 +225,7 @@ function granteeSuffix(association) {
|
|
|
225
225
|
if (association.kind !== 'permission')
|
|
226
226
|
return '';
|
|
227
227
|
const g = association.grantee;
|
|
228
|
-
return g.
|
|
228
|
+
return g.kind === 'entity' ? ` for "${g.entityId}"` : ` for ${g.role}s of "${g.groupId}"`;
|
|
229
229
|
}
|
|
230
230
|
/** The kinds an association may name — the closed set every surface reads. */
|
|
231
231
|
const ASSOCIATION_KINDS = new Set(['tag', 'attachment', 'relationship', 'permission', 'anyone']);
|
|
@@ -262,17 +262,17 @@ function granteeErrors(association, path) {
|
|
|
262
262
|
const grantee = association.grantee;
|
|
263
263
|
if (!grantee || typeof grantee !== 'object')
|
|
264
264
|
return fail('A permission requires a grantee.');
|
|
265
|
-
if (grantee.
|
|
265
|
+
if (grantee.kind === 'entity') {
|
|
266
266
|
return grantee.entityId ? [] : fail('An entity grantee requires a non-empty entityId.');
|
|
267
267
|
}
|
|
268
|
-
if (grantee.
|
|
268
|
+
if (grantee.kind === 'group') {
|
|
269
269
|
if (!grantee.groupId)
|
|
270
270
|
return fail('A group grantee requires a non-empty groupId.');
|
|
271
271
|
return grantee.role === 'member' || grantee.role === 'admin'
|
|
272
272
|
? []
|
|
273
273
|
: fail('A group grantee requires a role of "member" or "admin".');
|
|
274
274
|
}
|
|
275
|
-
return fail(`Unknown permission grantee
|
|
275
|
+
return fail(`Unknown permission grantee kind "${String(grantee.kind)}": expected "entity" or "group".`);
|
|
276
276
|
}
|
|
277
277
|
/**
|
|
278
278
|
* Refuse an authority element reaching the data half of the partition.
|
|
@@ -290,7 +290,7 @@ export function assertDataAssociations(associations, surface) {
|
|
|
290
290
|
const authority = associations.find((a) => namesKind(a) && isAuthorityAssociation(a));
|
|
291
291
|
if (!authority)
|
|
292
292
|
return;
|
|
293
|
-
throw new
|
|
293
|
+
throw new StackBadRequestError(`${surface} does not carry authority: a "${authority.kind}" association belongs to the ` +
|
|
294
294
|
'`permissions` surface — use grantAccess()/revokeAccess(), or the `permissions` change-set key.');
|
|
295
295
|
}
|
|
296
296
|
/**
|
|
@@ -302,7 +302,7 @@ export function assertAuthorityAssociations(permissions, surface) {
|
|
|
302
302
|
const data = permissions.find((a) => namesKind(a) && !isAuthorityAssociation(a));
|
|
303
303
|
if (!data)
|
|
304
304
|
return;
|
|
305
|
-
throw new
|
|
305
|
+
throw new StackBadRequestError(`${surface} carries authority alone: a "${data.kind}" association belongs to the ` +
|
|
306
306
|
'`associations` surface — use associate()/dissociate(), or the `associations` change-set key.');
|
|
307
307
|
}
|
|
308
308
|
/** validateAssociation() over a create's `associations` array. */
|
|
@@ -331,17 +331,22 @@ export function validateAssociations(associations, path = 'associations') {
|
|
|
331
331
|
];
|
|
332
332
|
}
|
|
333
333
|
/**
|
|
334
|
-
* Reject
|
|
335
|
-
*
|
|
336
|
-
* always present; without the runtime check a filter
|
|
337
|
-
* request could arrive empty and match every record carrying
|
|
338
|
-
*
|
|
334
|
+
* Reject an association filter that names neither of its halves, or whose
|
|
335
|
+
* relationship target is malformed. `RelatedToFilter` and `AttachmentFilter`
|
|
336
|
+
* promise one half is always present; without the runtime check a filter
|
|
337
|
+
* decoded from a request could arrive empty and match every record carrying
|
|
338
|
+
* any association of that kind. See docs/spec/data-model.md § Filter.
|
|
339
339
|
*/
|
|
340
|
-
export function
|
|
340
|
+
export function assertValidAssociationFilters(filter) {
|
|
341
|
+
const attachment = filter?.attachment;
|
|
342
|
+
if (attachment && attachment.label === undefined && attachment.fileId === undefined) {
|
|
343
|
+
throw new StackBadRequestError('filter.attachment must name a label, a fileId, or both — "any attachment at all" is not a filter.');
|
|
344
|
+
}
|
|
345
|
+
const relatedTo = filter?.relatedTo;
|
|
341
346
|
if (!relatedTo)
|
|
342
347
|
return;
|
|
343
348
|
if (relatedTo.label === undefined && relatedTo.target === undefined) {
|
|
344
|
-
throw new
|
|
349
|
+
throw new StackBadRequestError('filter.relatedTo must name a label, a target, or both — "any relationship at all" is not a filter.');
|
|
345
350
|
}
|
|
346
351
|
if (relatedTo.target === undefined)
|
|
347
352
|
return;
|
|
@@ -349,7 +354,7 @@ export function assertValidRelatedTo(relatedTo) {
|
|
|
349
354
|
externalIdOptional: true,
|
|
350
355
|
});
|
|
351
356
|
if (errors.length > 0)
|
|
352
|
-
throw new
|
|
357
|
+
throw new StackBadRequestError(errors[0].message);
|
|
353
358
|
}
|
|
354
359
|
/**
|
|
355
360
|
* Hold a journal window to the shape both adapters can honor identically.
|
|
@@ -367,12 +372,12 @@ export function assertValidRelatedTo(relatedTo) {
|
|
|
367
372
|
export function assertValidJournalQuery(query) {
|
|
368
373
|
if (!query)
|
|
369
374
|
return;
|
|
370
|
-
for (const key of ['
|
|
375
|
+
for (const key of ['afterSeq', 'limit']) {
|
|
371
376
|
const value = query[key];
|
|
372
377
|
if (value === undefined)
|
|
373
378
|
continue;
|
|
374
379
|
if (!Number.isInteger(value) || value < 0) {
|
|
375
|
-
throw new
|
|
380
|
+
throw new StackBadRequestError(`Invalid journal ${key} ${String(value)}: expected a non-negative integer.`);
|
|
376
381
|
}
|
|
377
382
|
}
|
|
378
383
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-validation.js","sourceRoot":"","sources":["../src/query-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"query-validation.js","sourceRoot":"","sources":["../src/query-validation.ts"],"names":[],"mappings":"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"}
|
package/dist/record-changes.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* See docs/spec/events.md § The event shape and docs/spec/versioning.md
|
|
13
13
|
* § The tombstone is literal.
|
|
14
14
|
*/
|
|
15
|
-
import type { Association, AssociationChange, AuthorityAssociation, ChangeOp, DataAssociation, EntityId,
|
|
15
|
+
import type { Association, AssociationChange, AuthorityAssociation, ChangeOp, DataAssociation, EntityId, Grantee, RecordChangeSet, RecordJournalEntry, RelationshipTarget, StackRecord } from './types.js';
|
|
16
16
|
/**
|
|
17
17
|
* Association identity — what dissociate() matches and a second associate()
|
|
18
18
|
* of the same reference is a repeat of. Matches the SQLite adapter's
|
|
@@ -28,7 +28,7 @@ export declare function associationEqual(a: Association, b: Association): boolea
|
|
|
28
28
|
* Structural equality per grantee arm. `role` is required, so there is no
|
|
29
29
|
* absent-means-any spelling to normalize.
|
|
30
30
|
*/
|
|
31
|
-
export declare function granteeEqual(a:
|
|
31
|
+
export declare function granteeEqual(a: Grantee, b: Grantee): boolean;
|
|
32
32
|
/**
|
|
33
33
|
* Whether an association carries authority rather than data — the partition
|
|
34
34
|
* `StackRecord.permissions` and `StackRecord.associations` project, and the
|
|
@@ -76,11 +76,12 @@ export declare function associationDelta(before: Association[], after: Associati
|
|
|
76
76
|
* The two flat lists a change frame carries, derived from the tagged list.
|
|
77
77
|
* The feed reports what is true now, so a `repoint` appears only under its
|
|
78
78
|
* new value and a `remove` by identity alone — the prior state the journal
|
|
79
|
-
* keeps has no place on a notification.
|
|
79
|
+
* keeps has no place on a notification. The data half alone: `reshare` is
|
|
80
|
+
* what announces an ACL move. See docs/spec/events.md § The event shape.
|
|
80
81
|
*/
|
|
81
82
|
export declare function feedAssociationDelta(changes: AssociationChange[]): {
|
|
82
|
-
added:
|
|
83
|
-
removed:
|
|
83
|
+
added: DataAssociation[];
|
|
84
|
+
removed: DataAssociation[];
|
|
84
85
|
};
|
|
85
86
|
/** Structural equality per target arm — what dissociate() matches on. */
|
|
86
87
|
export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget): boolean;
|
|
@@ -91,14 +92,14 @@ export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget
|
|
|
91
92
|
* typically a conditional that built an empty object.
|
|
92
93
|
* See docs/spec/data-model.md § Mutations.
|
|
93
94
|
*/
|
|
94
|
-
export declare function assertNonEmptyChangeSet(changes:
|
|
95
|
+
export declare function assertNonEmptyChangeSet(changes: RecordChangeSet): void;
|
|
95
96
|
/**
|
|
96
97
|
* Which aspects a change set actually moves, against the record as it
|
|
97
98
|
* stands. A key naming the value a record already holds contributes
|
|
98
99
|
* nothing. `merged` is the content the patch produces, computed once by the
|
|
99
100
|
* caller that had to validate it anyway.
|
|
100
101
|
*/
|
|
101
|
-
export declare function changeSetOps(existing: StackRecord, changes:
|
|
102
|
+
export declare function changeSetOps(existing: StackRecord, changes: RecordChangeSet, merged: Record<string, unknown> | undefined): ChangeOp[];
|
|
102
103
|
/**
|
|
103
104
|
* Whether a change set's ops advance `version`/`updatedAt` at all. After the
|
|
104
105
|
* no-bump set above, this is `patch` and the whole-record verbs.
|
|
@@ -111,7 +112,7 @@ export declare function bumpsVersion(ops: ChangeOp[]): boolean;
|
|
|
111
112
|
* wrote rather than the ops the set turns out to move, so a stale caller is
|
|
112
113
|
* told its version is stale whatever its patch says.
|
|
113
114
|
*/
|
|
114
|
-
export declare function takesIfVersion(changes:
|
|
115
|
+
export declare function takesIfVersion(changes: RecordChangeSet): boolean;
|
|
115
116
|
/**
|
|
116
117
|
* The change set narrowed to the aspects that actually moved. An adapter is
|
|
117
118
|
* handed this rather than what the caller wrote, so restating an aspect
|
|
@@ -119,7 +120,7 @@ export declare function takesIfVersion(changes: RecordChanges): boolean;
|
|
|
119
120
|
* otherwise drag `unlistedAt` forward with no op reporting it. Every key an
|
|
120
121
|
* adapter receives is one it must write.
|
|
121
122
|
*/
|
|
122
|
-
export declare function effectiveChanges(changes:
|
|
123
|
+
export declare function effectiveChanges(changes: RecordChangeSet, ops: ChangeOp[]): RecordChangeSet;
|
|
123
124
|
/**
|
|
124
125
|
* Whether a merge patch produced the content the record already held.
|
|
125
126
|
* Compared by serialization: content is JSON by construction and a patch
|
|
@@ -153,7 +154,7 @@ export declare const presentDeleted: (record: StackRecord) => StackRecord;
|
|
|
153
154
|
export declare function movesAuthority(change: AssociationChange): boolean;
|
|
154
155
|
/**
|
|
155
156
|
* A journal entry as a reader who cannot reshare sees it: the authority
|
|
156
|
-
* half of its delta dropped, the `
|
|
157
|
+
* half of its delta dropped, the `reshare` op left standing, so the
|
|
157
158
|
* entry still names *that* the ACL moved. Passing the mutate-surface gate
|
|
158
159
|
* buys the record's content history, which is not a route to its sharing
|
|
159
160
|
* graph. See docs/spec/journal.md § Reading it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-changes.d.ts","sourceRoot":"","sources":["../src/record-changes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,
|
|
1
|
+
{"version":3,"file":"record-changes.d.ts","sourceRoot":"","sources":["../src/record-changes.ts"],"names":[],"mappings":"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"}
|
package/dist/record-changes.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* § The tombstone is literal.
|
|
14
14
|
*/
|
|
15
15
|
import { baseIdOf } from './schema.js';
|
|
16
|
-
import {
|
|
17
|
-
import { SYSTEM_TYPES,
|
|
16
|
+
import { StackBadRequestError } from './errors.js';
|
|
17
|
+
import { SYSTEM_TYPES, RECORD_CHANGE_SET_KEYS } from './types.js';
|
|
18
18
|
/**
|
|
19
19
|
* Association identity — what dissociate() matches and a second associate()
|
|
20
20
|
* of the same reference is a repeat of. Matches the SQLite adapter's
|
|
@@ -49,11 +49,11 @@ export function associationEqual(a, b) {
|
|
|
49
49
|
export function granteeEqual(a, b) {
|
|
50
50
|
if (!a || !b)
|
|
51
51
|
return false;
|
|
52
|
-
if (a.
|
|
52
|
+
if (a.kind !== b.kind)
|
|
53
53
|
return false;
|
|
54
|
-
if (a.
|
|
54
|
+
if (a.kind === 'entity' && b.kind === 'entity')
|
|
55
55
|
return a.entityId === b.entityId;
|
|
56
|
-
if (a.
|
|
56
|
+
if (a.kind === 'group' && b.kind === 'group') {
|
|
57
57
|
return a.groupId === b.groupId && a.role === b.role;
|
|
58
58
|
}
|
|
59
59
|
return false;
|
|
@@ -133,26 +133,34 @@ export function associationDelta(before, after) {
|
|
|
133
133
|
* The two flat lists a change frame carries, derived from the tagged list.
|
|
134
134
|
* The feed reports what is true now, so a `repoint` appears only under its
|
|
135
135
|
* new value and a `remove` by identity alone — the prior state the journal
|
|
136
|
-
* keeps has no place on a notification.
|
|
136
|
+
* keeps has no place on a notification. The data half alone: `reshare` is
|
|
137
|
+
* what announces an ACL move. See docs/spec/events.md § The event shape.
|
|
137
138
|
*/
|
|
138
139
|
export function feedAssociationDelta(changes) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
const added = [];
|
|
141
|
+
const removed = [];
|
|
142
|
+
for (const c of changes) {
|
|
143
|
+
if (c.op === 'remove') {
|
|
144
|
+
const identity = stripAssociationAnnotation(c.previous);
|
|
145
|
+
if (!isAuthorityAssociation(identity))
|
|
146
|
+
removed.push(identity);
|
|
147
|
+
}
|
|
148
|
+
else if (!isAuthorityAssociation(c.association)) {
|
|
149
|
+
added.push(c.association);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return { added, removed };
|
|
145
153
|
}
|
|
146
154
|
/** Structural equality per target arm — what dissociate() matches on. */
|
|
147
155
|
export function targetEqual(a, b) {
|
|
148
|
-
if (a.
|
|
156
|
+
if (a.kind !== b.kind)
|
|
149
157
|
return false;
|
|
150
|
-
if (a.
|
|
158
|
+
if (a.kind === 'record' && b.kind === 'record') {
|
|
151
159
|
return a.recordId === b.recordId && (a.stackUrl ?? '') === (b.stackUrl ?? '');
|
|
152
160
|
}
|
|
153
|
-
if (a.
|
|
161
|
+
if (a.kind === 'entity' && b.kind === 'entity')
|
|
154
162
|
return a.entityId === b.entityId;
|
|
155
|
-
if (a.
|
|
163
|
+
if (a.kind === 'external' && b.kind === 'external')
|
|
156
164
|
return a.ns === b.ns && a.id === b.id;
|
|
157
165
|
return false;
|
|
158
166
|
}
|
|
@@ -166,9 +174,9 @@ export function targetEqual(a, b) {
|
|
|
166
174
|
export function assertNonEmptyChangeSet(changes) {
|
|
167
175
|
// Presence, not truthiness: `unlisted: false` and `parentId: null` are
|
|
168
176
|
// aspects this call names.
|
|
169
|
-
if (
|
|
177
|
+
if (RECORD_CHANGE_SET_KEYS.some((key) => changes[key] !== undefined))
|
|
170
178
|
return;
|
|
171
|
-
throw new
|
|
179
|
+
throw new StackBadRequestError('A change set names at least one of: ' + RECORD_CHANGE_SET_KEYS.join(', ') + '.');
|
|
172
180
|
}
|
|
173
181
|
/**
|
|
174
182
|
* Which aspects a change set actually moves, against the record as it
|
|
@@ -185,7 +193,7 @@ export function changeSetOps(existing, changes, merged) {
|
|
|
185
193
|
}
|
|
186
194
|
if (changes.permissions &&
|
|
187
195
|
associationDelta(existing.permissions ?? [], changes.permissions).length > 0) {
|
|
188
|
-
ops.push('
|
|
196
|
+
ops.push('reshare');
|
|
189
197
|
}
|
|
190
198
|
if (changes.associations) {
|
|
191
199
|
const delta = associationDelta(existing.associations ?? [], changes.associations);
|
|
@@ -208,7 +216,7 @@ export function changeSetOps(existing, changes, merged) {
|
|
|
208
216
|
const NO_BUMP_OPS = new Set([
|
|
209
217
|
'associate',
|
|
210
218
|
'dissociate',
|
|
211
|
-
'
|
|
219
|
+
'reshare',
|
|
212
220
|
'reparent',
|
|
213
221
|
'unlist',
|
|
214
222
|
'list',
|
|
@@ -239,7 +247,7 @@ const NO_PRECONDITION_KEYS = new Set([
|
|
|
239
247
|
* told its version is stale whatever its patch says.
|
|
240
248
|
*/
|
|
241
249
|
export function takesIfVersion(changes) {
|
|
242
|
-
return
|
|
250
|
+
return RECORD_CHANGE_SET_KEYS.some((key) => !NO_PRECONDITION_KEYS.has(key) && changes[key] !== undefined);
|
|
243
251
|
}
|
|
244
252
|
/**
|
|
245
253
|
* The change set narrowed to the aspects that actually moved. An adapter is
|
|
@@ -254,7 +262,7 @@ export function effectiveChanges(changes, ops) {
|
|
|
254
262
|
effective.contentPatch = changes.contentPatch;
|
|
255
263
|
if (ops.includes('reparent'))
|
|
256
264
|
effective.parentId = changes.parentId;
|
|
257
|
-
if (ops.includes('
|
|
265
|
+
if (ops.includes('reshare'))
|
|
258
266
|
effective.permissions = changes.permissions;
|
|
259
267
|
if (ops.includes('associate') || ops.includes('dissociate')) {
|
|
260
268
|
effective.associations = changes.associations;
|
|
@@ -279,13 +287,13 @@ export function stampGroupAdmin(associations, creator) {
|
|
|
279
287
|
const list = associations ?? [];
|
|
280
288
|
const alreadyAdmin = list.some((a) => a.kind === 'relationship' &&
|
|
281
289
|
a.label === 'admin' &&
|
|
282
|
-
a.target.
|
|
290
|
+
a.target.kind === 'entity' &&
|
|
283
291
|
a.target.entityId === creator);
|
|
284
292
|
if (alreadyAdmin)
|
|
285
293
|
return list;
|
|
286
294
|
return [
|
|
287
295
|
...list,
|
|
288
|
-
{ kind: 'relationship', label: 'admin', target: {
|
|
296
|
+
{ kind: 'relationship', label: 'admin', target: { kind: 'entity', entityId: creator } },
|
|
289
297
|
];
|
|
290
298
|
}
|
|
291
299
|
/**
|
|
@@ -326,7 +334,7 @@ export function movesAuthority(change) {
|
|
|
326
334
|
}
|
|
327
335
|
/**
|
|
328
336
|
* A journal entry as a reader who cannot reshare sees it: the authority
|
|
329
|
-
* half of its delta dropped, the `
|
|
337
|
+
* half of its delta dropped, the `reshare` op left standing, so the
|
|
330
338
|
* entry still names *that* the ACL moved. Passing the mutate-surface gate
|
|
331
339
|
* buys the record's content history, which is not a route to its sharing
|
|
332
340
|
* graph. See docs/spec/journal.md § Reading it.
|