@haverstack/core 0.41.0 → 0.43.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 +124 -64
- package/dist/access.d.ts +43 -47
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +66 -101
- package/dist/access.js.map +1 -1
- package/dist/{combine.d.ts → adapter/combine.d.ts} +8 -1
- package/dist/adapter/combine.d.ts.map +1 -0
- package/dist/{combine.js → adapter/combine.js} +18 -3
- package/dist/adapter/combine.js.map +1 -0
- package/dist/adapter/index.d.ts +19 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +16 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/adapter/reach.d.ts +14 -0
- package/dist/adapter/reach.d.ts.map +1 -0
- package/dist/adapter/reach.js +31 -0
- package/dist/adapter/reach.js.map +1 -0
- package/dist/{sort.d.ts → adapter/sort.d.ts} +20 -20
- package/dist/adapter/sort.d.ts.map +1 -0
- package/dist/{sort.js → adapter/sort.js} +37 -27
- package/dist/adapter/sort.js.map +1 -0
- package/dist/associations/identity.d.ts +69 -0
- package/dist/associations/identity.d.ts.map +1 -0
- package/dist/associations/identity.js +174 -0
- package/dist/associations/identity.js.map +1 -0
- package/dist/associations/validation.d.ts +83 -0
- package/dist/associations/validation.d.ts.map +1 -0
- package/dist/associations/validation.js +312 -0
- package/dist/associations/validation.js.map +1 -0
- package/dist/attachment-records.d.ts +48 -0
- package/dist/attachment-records.d.ts.map +1 -0
- package/dist/attachment-records.js +52 -0
- package/dist/attachment-records.js.map +1 -0
- package/dist/collection.d.ts +178 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +397 -0
- package/dist/collection.js.map +1 -0
- package/dist/content-match.d.ts +25 -0
- package/dist/content-match.d.ts.map +1 -0
- package/dist/content-match.js +51 -0
- package/dist/content-match.js.map +1 -0
- package/dist/content-translation.d.ts +42 -0
- package/dist/content-translation.d.ts.map +1 -0
- package/dist/content-translation.js +108 -0
- package/dist/content-translation.js.map +1 -0
- package/dist/did/did.d.ts.map +1 -0
- package/dist/{did.js → did/did.js} +3 -4
- package/dist/did/did.js.map +1 -0
- package/dist/{did-entry.d.ts → did/index.d.ts} +1 -1
- package/dist/did/index.d.ts.map +1 -0
- package/dist/{did-entry.js → did/index.js} +3 -3
- package/dist/did/index.js.map +1 -0
- package/dist/errors.d.ts +54 -66
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +58 -62
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +20 -44
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +69 -151
- package/dist/grants.js.map +1 -1
- package/dist/id.d.ts +13 -31
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +13 -31
- package/dist/id.js.map +1 -1
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +3 -6
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +6 -10
- package/dist/install.js.map +1 -1
- package/dist/lazy.d.ts +13 -0
- package/dist/lazy.d.ts.map +1 -0
- package/dist/lazy.js +27 -0
- package/dist/lazy.js.map +1 -0
- package/dist/merge.d.ts +1 -1
- package/dist/merge.d.ts.map +1 -1
- package/dist/merge.js +6 -12
- package/dist/merge.js.map +1 -1
- package/dist/migration-steps.d.ts +102 -0
- package/dist/migration-steps.d.ts.map +1 -0
- package/dist/migration-steps.js +293 -0
- package/dist/migration-steps.js.map +1 -0
- package/dist/query-validation.d.ts +68 -121
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +214 -380
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +11 -150
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +14 -305
- package/dist/record-changes.js.map +1 -1
- package/dist/schema.d.ts +18 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +59 -30
- package/dist/schema.js.map +1 -1
- package/dist/scoped-stack/authority-snapshot.d.ts +37 -0
- package/dist/scoped-stack/authority-snapshot.d.ts.map +1 -0
- package/dist/scoped-stack/authority-snapshot.js +49 -0
- package/dist/scoped-stack/authority-snapshot.js.map +1 -0
- package/dist/scoped-stack/authority.d.ts +169 -0
- package/dist/scoped-stack/authority.d.ts.map +1 -0
- package/dist/scoped-stack/authority.js +408 -0
- package/dist/scoped-stack/authority.js.map +1 -0
- package/dist/scoped-stack/family-policy.d.ts +59 -0
- package/dist/scoped-stack/family-policy.d.ts.map +1 -0
- package/dist/scoped-stack/family-policy.js +68 -0
- package/dist/scoped-stack/family-policy.js.map +1 -0
- package/dist/scoped-stack/scoped-stack.d.ts +318 -0
- package/dist/scoped-stack/scoped-stack.d.ts.map +1 -0
- package/dist/scoped-stack/scoped-stack.js +695 -0
- package/dist/scoped-stack/scoped-stack.js.map +1 -0
- package/dist/scoped-stack/subscription.d.ts +36 -0
- package/dist/scoped-stack/subscription.d.ts.map +1 -0
- package/dist/scoped-stack/subscription.js +69 -0
- package/dist/scoped-stack/subscription.js.map +1 -0
- package/dist/stack/arguments.d.ts +27 -0
- package/dist/stack/arguments.d.ts.map +1 -0
- package/dist/stack/arguments.js +53 -0
- package/dist/stack/arguments.js.map +1 -0
- package/dist/stack/attachments.d.ts +24 -0
- package/dist/stack/attachments.d.ts.map +1 -0
- package/dist/stack/attachments.js +113 -0
- package/dist/stack/attachments.js.map +1 -0
- package/dist/{changes.d.ts → stack/changes.d.ts} +30 -72
- package/dist/stack/changes.d.ts.map +1 -0
- package/dist/{changes.js → stack/changes.js} +40 -74
- package/dist/stack/changes.js.map +1 -0
- package/dist/stack/client.d.ts +247 -0
- package/dist/stack/client.d.ts.map +1 -0
- package/dist/stack/client.js +9 -0
- package/dist/stack/client.js.map +1 -0
- package/dist/stack/identity-bindings.d.ts.map +1 -0
- package/dist/{identity-bindings.js → stack/identity-bindings.js} +5 -14
- package/dist/stack/identity-bindings.js.map +1 -0
- package/dist/stack/install-app.d.ts +22 -0
- package/dist/stack/install-app.d.ts.map +1 -0
- package/dist/stack/install-app.js +262 -0
- package/dist/stack/install-app.js.map +1 -0
- package/dist/stack/integrity.d.ts +94 -0
- package/dist/stack/integrity.d.ts.map +1 -0
- package/dist/stack/integrity.js +268 -0
- package/dist/stack/integrity.js.map +1 -0
- package/dist/{limits.d.ts → stack/limits.d.ts} +2 -5
- package/dist/stack/limits.d.ts.map +1 -0
- package/dist/{limits.js → stack/limits.js} +3 -6
- package/dist/stack/limits.js.map +1 -0
- package/dist/stack/migrations.d.ts +59 -0
- package/dist/stack/migrations.d.ts.map +1 -0
- package/dist/stack/migrations.js +141 -0
- package/dist/stack/migrations.js.map +1 -0
- package/dist/stack/reads.d.ts +69 -0
- package/dist/stack/reads.d.ts.map +1 -0
- package/dist/stack/reads.js +141 -0
- package/dist/stack/reads.js.map +1 -0
- package/dist/stack/record-id.d.ts +35 -0
- package/dist/stack/record-id.d.ts.map +1 -0
- package/dist/stack/record-id.js +82 -0
- package/dist/stack/record-id.js.map +1 -0
- package/dist/stack/stack.d.ts +444 -0
- package/dist/stack/stack.d.ts.map +1 -0
- package/dist/stack/stack.js +1339 -0
- package/dist/stack/stack.js.map +1 -0
- package/dist/stack/system-types.d.ts +16 -0
- package/dist/stack/system-types.d.ts.map +1 -0
- package/dist/stack/system-types.js +111 -0
- package/dist/stack/system-types.js.map +1 -0
- package/dist/stack/type-grants.d.ts +20 -0
- package/dist/stack/type-grants.d.ts.map +1 -0
- package/dist/stack/type-grants.js +69 -0
- package/dist/stack/type-grants.js.map +1 -0
- package/dist/stack/write-expectation.d.ts +79 -0
- package/dist/stack/write-expectation.d.ts.map +1 -0
- package/dist/stack/write-expectation.js +71 -0
- package/dist/stack/write-expectation.js.map +1 -0
- package/dist/stack/write-gate.d.ts +23 -0
- package/dist/stack/write-gate.d.ts.map +1 -0
- package/dist/stack/write-gate.js +17 -0
- package/dist/stack/write-gate.js.map +1 -0
- package/dist/testing.d.ts +44 -33
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +180 -176
- package/dist/testing.js.map +1 -1
- package/dist/type-handle.d.ts +67 -78
- package/dist/type-handle.d.ts.map +1 -1
- package/dist/type-handle.js +44 -111
- package/dist/type-handle.js.map +1 -1
- package/dist/types/adapter.d.ts +282 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/adapter.js +5 -0
- package/dist/types/adapter.js.map +1 -0
- package/dist/types/associations.d.ts +140 -0
- package/dist/types/associations.d.ts.map +1 -0
- package/dist/types/associations.js +6 -0
- package/dist/types/associations.js.map +1 -0
- package/dist/types/capabilities.d.ts +89 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +5 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/events.d.ts +150 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +5 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/ids.d.ts +43 -0
- package/dist/types/ids.d.ts.map +1 -0
- package/dist/types/ids.js +5 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/journal.d.ts +47 -0
- package/dist/types/journal.d.ts.map +1 -0
- package/dist/types/journal.js +5 -0
- package/dist/types/journal.js.map +1 -0
- package/dist/types/query.d.ts +185 -0
- package/dist/types/query.d.ts.map +1 -0
- package/dist/types/query.js +10 -0
- package/dist/types/query.js.map +1 -0
- package/dist/types/records.d.ts +76 -0
- package/dist/types/records.d.ts.map +1 -0
- package/dist/types/records.js +5 -0
- package/dist/types/records.js.map +1 -0
- package/dist/types/schema.d.ts +74 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +5 -0
- package/dist/types/schema.js.map +1 -0
- package/dist/types/system.d.ts +161 -0
- package/dist/types/system.d.ts.map +1 -0
- package/dist/types/system.js +32 -0
- package/dist/types/system.js.map +1 -0
- package/dist/types/tokens.d.ts +39 -0
- package/dist/types/tokens.d.ts.map +1 -0
- package/dist/types/tokens.js +5 -0
- package/dist/types/tokens.js.map +1 -0
- package/dist/validate.d.ts +31 -70
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +52 -92
- package/dist/validate.js.map +1 -1
- package/dist/{attachment-download.d.ts → wire/attachment-download.d.ts} +0 -57
- package/dist/wire/attachment-download.d.ts.map +1 -0
- package/dist/{attachment-download.js → wire/attachment-download.js} +0 -61
- package/dist/wire/attachment-download.js.map +1 -0
- package/dist/{auth.d.ts → wire/auth.d.ts} +5 -8
- package/dist/wire/auth.d.ts.map +1 -0
- package/dist/{auth.js → wire/auth.js} +5 -8
- package/dist/wire/auth.js.map +1 -0
- package/dist/{wire-body.d.ts → wire/body.d.ts} +35 -12
- package/dist/wire/body.d.ts.map +1 -0
- package/dist/{wire-body.js → wire/body.js} +74 -13
- package/dist/wire/body.js.map +1 -0
- package/dist/{wire-entry.d.ts → wire/index.d.ts} +12 -12
- package/dist/wire/index.d.ts.map +1 -0
- package/dist/{wire-entry.js → wire/index.js} +13 -17
- package/dist/wire/index.js.map +1 -0
- package/dist/wire/record.d.ts +31 -0
- package/dist/wire/record.d.ts.map +1 -0
- package/dist/{wire-record.js → wire/record.js} +7 -67
- package/dist/wire/record.js.map +1 -0
- package/dist/{wire-request.d.ts → wire/request.d.ts} +32 -49
- package/dist/wire/request.d.ts.map +1 -0
- package/dist/{wire-request.js → wire/request.js} +113 -83
- package/dist/wire/request.js.map +1 -0
- package/package.json +7 -7
- package/dist/adapter-entry.d.ts +0 -16
- package/dist/adapter-entry.d.ts.map +0 -1
- package/dist/adapter-entry.js +0 -14
- package/dist/adapter-entry.js.map +0 -1
- package/dist/attachment-download.d.ts.map +0 -1
- package/dist/attachment-download.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/changes.d.ts.map +0 -1
- package/dist/changes.js.map +0 -1
- package/dist/combine.d.ts.map +0 -1
- package/dist/combine.js.map +0 -1
- package/dist/did-entry.d.ts.map +0 -1
- package/dist/did-entry.js.map +0 -1
- package/dist/did.d.ts.map +0 -1
- package/dist/did.js.map +0 -1
- package/dist/identity-bindings.d.ts.map +0 -1
- package/dist/identity-bindings.js.map +0 -1
- package/dist/limits.d.ts.map +0 -1
- package/dist/limits.js.map +0 -1
- package/dist/record-id.d.ts +0 -70
- package/dist/record-id.d.ts.map +0 -1
- package/dist/record-id.js +0 -127
- package/dist/record-id.js.map +0 -1
- package/dist/scoped-stack.d.ts +0 -549
- package/dist/scoped-stack.d.ts.map +0 -1
- package/dist/scoped-stack.js +0 -1301
- package/dist/scoped-stack.js.map +0 -1
- package/dist/sort.d.ts.map +0 -1
- package/dist/sort.js.map +0 -1
- package/dist/stack-reads.d.ts +0 -38
- package/dist/stack-reads.d.ts.map +0 -1
- package/dist/stack-reads.js +0 -79
- package/dist/stack-reads.js.map +0 -1
- package/dist/stack.d.ts +0 -1032
- package/dist/stack.d.ts.map +0 -1
- package/dist/stack.js +0 -2337
- package/dist/stack.js.map +0 -1
- package/dist/types.d.ts +0 -1278
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -51
- package/dist/types.js.map +0 -1
- package/dist/wire-body.d.ts.map +0 -1
- package/dist/wire-body.js.map +0 -1
- package/dist/wire-entry.d.ts.map +0 -1
- package/dist/wire-entry.js.map +0 -1
- package/dist/wire-record.d.ts +0 -50
- package/dist/wire-record.d.ts.map +0 -1
- package/dist/wire-record.js.map +0 -1
- package/dist/wire-request.d.ts.map +0 -1
- package/dist/wire-request.js.map +0 -1
- /package/dist/{did.d.ts → did/did.d.ts} +0 -0
- /package/dist/{identity-bindings.d.ts → stack/identity-bindings.d.ts} +0 -0
package/dist/errors.js
CHANGED
|
@@ -7,39 +7,39 @@
|
|
|
7
7
|
* subclass carries its wire discriminator as an instance `code`.
|
|
8
8
|
*
|
|
9
9
|
* Several errors sit deliberately outside that root (UseAfterCloseError,
|
|
10
|
-
* InvalidAdapterError, OwnerMismatchError,
|
|
11
|
-
* modules — IdGenerationError
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* InvalidAdapterError, OwnerMismatchError, ElementSetMovedError, ScopeUnavailableError, and — in their own
|
|
11
|
+
* modules — IdGenerationError, InvalidDidError, InvalidAuthChallengeError,
|
|
12
|
+
* StoredVersionError and MisfitError): they report a local programming error, malformed
|
|
13
|
+
* local input or an assembled topology, not a state a request can be in, so
|
|
14
|
+
* no server ever responds with one.
|
|
14
15
|
*
|
|
15
16
|
* The `Stack` prefix is reserved for the taxonomy: a class named
|
|
16
17
|
* `Stack…Error` extends StackError, and nothing else carries the prefix.
|
|
17
18
|
*/
|
|
18
19
|
import { baseIdOf, parseTypeId } from './schema.js';
|
|
19
20
|
/**
|
|
20
|
-
* Root of the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* than a chain of class tests.
|
|
26
|
-
*
|
|
27
|
-
* Membership implies a wire mapping: every code has an entry in
|
|
28
|
-
* WIRE_ERROR_STATUS. Errors with no wire representation (IdGenerationError,
|
|
29
|
-
* InvalidDidError) deliberately stay outside this hierarchy.
|
|
30
|
-
*
|
|
31
|
-
* Subclassing adds no hierarchy beyond this root — notably
|
|
32
|
-
* StackVersionConflictError is a sibling of StackConflictError, not a
|
|
33
|
-
* subtype. See docs/spec/wire-format.md § Error responses.
|
|
21
|
+
* Root of the taxonomy: `instanceof StackError` is the one question a
|
|
22
|
+
* server's error middleware asks, and each subclass's `code` makes
|
|
23
|
+
* serialization a lookup. Every code has a WIRE_ERROR_STATUS entry.
|
|
24
|
+
* Subclasses add no hierarchy — StackVersionConflictError is a sibling of
|
|
25
|
+
* StackConflictError. See docs/spec/wire-format.md § Error responses.
|
|
34
26
|
*/
|
|
35
27
|
export class StackError extends Error {
|
|
36
28
|
}
|
|
29
|
+
const CONTENT_INVALID = 'Content validation failed';
|
|
30
|
+
export const ARGUMENTS_INVALID = 'Invalid arguments';
|
|
31
|
+
export const SCHEMA_INVALID = 'Schema validation failed';
|
|
32
|
+
/**
|
|
33
|
+
* `header` names what was validated — record content, or the arguments a
|
|
34
|
+
* call was given — so a refusal of `editAssociations(id, [])` doesn't read as a
|
|
35
|
+
* content failure. Only the message varies: `code` is the contract.
|
|
36
|
+
*/
|
|
37
37
|
export class StackValidationError extends StackError {
|
|
38
38
|
errors;
|
|
39
39
|
static code = 'validation';
|
|
40
40
|
code = StackValidationError.code;
|
|
41
|
-
constructor(errors) {
|
|
42
|
-
super(
|
|
41
|
+
constructor(errors, header = CONTENT_INVALID) {
|
|
42
|
+
super(`${header}:\n` + errors.map((e) => ` ${e.path}: ${e.message}`).join('\n'));
|
|
43
43
|
this.errors = errors;
|
|
44
44
|
this.name = 'StackValidationError';
|
|
45
45
|
}
|
|
@@ -79,6 +79,12 @@ export class StackConflictError extends StackError {
|
|
|
79
79
|
this.name = 'StackConflictError';
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The refusal a mutation of a soft-deleted record earns, from `Stack` or
|
|
84
|
+
* from inside an adapter's aspect write: one message, wherever it is asked.
|
|
85
|
+
* See docs/spec/versioning.md § Mutations are refused, not applied to a tombstone.
|
|
86
|
+
*/
|
|
87
|
+
export const softDeletedRefusal = (id) => new StackConflictError(`Record "${id}" is soft-deleted; undelete it before mutating it.`);
|
|
82
88
|
/**
|
|
83
89
|
* Thrown when an `ifVersion` precondition doesn't match a record's current
|
|
84
90
|
* version. Deliberately not a StackConflictError subtype — the two have
|
|
@@ -100,16 +106,10 @@ export class StackVersionConflictError extends StackError {
|
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* StackValidationError, which means the request was well-formed but content
|
|
108
|
-
* failed schema validation.
|
|
109
|
-
*
|
|
110
|
-
* Naming something absent belongs here rather than under `not_found`: a
|
|
111
|
-
* request whose *type* is undefined never addressed a record, so answering
|
|
112
|
-
* 404 would say a record was missing when none was asked for.
|
|
109
|
+
* A request structurally malformed — an undecodable cursor, a malformed
|
|
110
|
+
* TypeId, unparseable search text — as opposed to StackValidationError's
|
|
111
|
+
* well-formed request with invalid content. Naming an undefined type is
|
|
112
|
+
* here, not `not_found`: no record was asked for, so none is missing.
|
|
113
113
|
*/
|
|
114
114
|
export class StackBadRequestError extends StackError {
|
|
115
115
|
capability;
|
|
@@ -117,12 +117,9 @@ export class StackBadRequestError extends StackError {
|
|
|
117
117
|
code = StackBadRequestError.code;
|
|
118
118
|
constructor(message,
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* A client mapping this onto an error of its own reads the name from
|
|
124
|
-
* here rather than re-deriving it from the query, which is how one
|
|
125
|
-
* rule becomes two answers that can disagree.
|
|
120
|
+
* The declared capability the query needed and the adapter lacks, or
|
|
121
|
+
* undefined when the query's own shape was wrong. A client reads it from
|
|
122
|
+
* here rather than re-deriving it, so one rule has one answer.
|
|
126
123
|
*/
|
|
127
124
|
capability) {
|
|
128
125
|
super(message);
|
|
@@ -145,17 +142,10 @@ export class StackPayloadTooLargeError extends StackError {
|
|
|
145
142
|
}
|
|
146
143
|
}
|
|
147
144
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* docs/spec/data-model.md § Capability-gated filters
|
|
152
|
-
*
|
|
153
|
-
* Never produced in-process: both SQLite engines run synchronously, so
|
|
154
|
-
* there is nothing to interrupt from inside the call. It exists so a
|
|
155
|
-
* server that bounds query time has a class to serialize, and so the app
|
|
156
|
-
* catching it can tell "too expensive, narrow it and retry" from
|
|
157
|
-
* StackBadRequestError's "malformed, don't bother retrying" — the distinction
|
|
158
|
-
* that would be lost if a timeout reused `bad_request`.
|
|
145
|
+
* A server abandoned an operation for taking too long — in practice a
|
|
146
|
+
* full-text search. Never produced in-process; it lets an app tell "too
|
|
147
|
+
* expensive, narrow and retry" from `bad_request`'s "malformed".
|
|
148
|
+
* See docs/spec/data-model.md § Capability-gated filters.
|
|
159
149
|
*/
|
|
160
150
|
export class StackTimeoutError extends StackError {
|
|
161
151
|
static code = 'timeout';
|
|
@@ -179,7 +169,7 @@ export class StackSchemaDriftError extends StackError {
|
|
|
179
169
|
constructor(typeId, violations) {
|
|
180
170
|
super(`Schema drift detected for type "${typeId}": the stored schema and the new definition ` +
|
|
181
171
|
`differ beyond additive evolution (new optional fields only). Bump the version instead ` +
|
|
182
|
-
`of redefining "${typeId}" in place — e.g. defineType({ id: \`${baseIdOf(typeId)}@${(parseTypeId(typeId)?.version ?? 0) + 1}\`, ... }) plus
|
|
172
|
+
`of redefining "${typeId}" in place — e.g. defineType({ id: \`${baseIdOf(typeId)}@${(parseTypeId(typeId)?.version ?? 0) + 1}\`, ... }) plus a migration() passed to Stack.open().\n` +
|
|
183
173
|
violations.map((v) => ` ${v.path || '(root)'}: ${v.message}`).join('\n'));
|
|
184
174
|
this.typeId = typeId;
|
|
185
175
|
this.violations = violations;
|
|
@@ -233,23 +223,29 @@ export class OwnerMismatchError extends Error {
|
|
|
233
223
|
}
|
|
234
224
|
}
|
|
235
225
|
/**
|
|
236
|
-
* Thrown
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
226
|
+
* Thrown by an adapter's element-set write when the stored set no longer
|
|
227
|
+
* matches the `expectSet` it was handed. Outside the StackError taxonomy:
|
|
228
|
+
* Stack catches it and reads again, so no request ever answers with it.
|
|
229
|
+
* See docs/spec/versioning.md § Element-set writes compare the set they read.
|
|
230
|
+
*/
|
|
231
|
+
export class ElementSetMovedError extends Error {
|
|
232
|
+
recordId;
|
|
233
|
+
constructor(recordId) {
|
|
234
|
+
super(`Record "${recordId}"'s set changed since it was read`);
|
|
235
|
+
this.recordId = recordId;
|
|
236
|
+
this.name = 'ElementSetMovedError';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* A scoped view asked of a stack that does not own its storage. A server
|
|
241
|
+
* scopes every request to the session that opened it, so such a stack is
|
|
242
|
+
* scoped by opening it with the session meant.
|
|
243
|
+
* See docs/spec/access-control.md § Enforcement: Stack.asEntity().
|
|
248
244
|
*/
|
|
249
|
-
export class
|
|
245
|
+
export class ScopeUnavailableError extends Error {
|
|
250
246
|
constructor(message) {
|
|
251
247
|
super(message);
|
|
252
|
-
this.name = '
|
|
248
|
+
this.name = 'ScopeUnavailableError';
|
|
253
249
|
}
|
|
254
250
|
}
|
|
255
251
|
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/grants.d.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* See docs/spec/access-control.md § Type-level grants.
|
|
10
10
|
*/
|
|
11
|
-
import type {
|
|
11
|
+
import type { RosterRoles } from './access.js';
|
|
12
|
+
import type { BaseId, EntityId, GrantAction, GrantContent, GrantGrantee, GroupRole, RecordId, TypeId } from './types/index.js';
|
|
12
13
|
import type { ValidationError } from './validate.js';
|
|
13
14
|
/**
|
|
14
15
|
* Valid GrantAction values, for runtime validation in Stack.grantType(). Built
|
|
@@ -17,33 +18,18 @@ import type { ValidationError } from './validate.js';
|
|
|
17
18
|
*/
|
|
18
19
|
export declare const GRANT_ACTION_SET: ReadonlySet<GrantAction>;
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* cannot read is the drop-box, and it discloses nothing.
|
|
25
|
-
* See docs/spec/access-control.md § Write implies read.
|
|
26
|
-
*/
|
|
27
|
-
export declare const READ_COMPANIONS: ReadonlyMap<GrantAction, readonly GrantAction[]>;
|
|
28
|
-
/**
|
|
29
|
-
* Whether one grant's action list conveys `action`. The companion has to
|
|
30
|
-
* sit in the same `_grant` Record: a grant is revoked whole, so a rule
|
|
31
|
-
* satisfied across two records would let revoking the read one leave a
|
|
32
|
-
* mutate-without-read grant standing.
|
|
33
|
-
*
|
|
34
|
-
* Takes the list grantReach() produced, never a stored field: `includes()`
|
|
35
|
-
* over a string is substring matching, so one would answer for every verb
|
|
36
|
-
* spelled inside it.
|
|
21
|
+
* Whether one grant's action list conveys `action`. The companion must sit
|
|
22
|
+
* in the same `_grant`: a grant is revoked whole, so a rule met across two
|
|
23
|
+
* records could leave mutate-without-read standing. Takes grantReach()'s
|
|
24
|
+
* list, never a stored field, whose `includes()` would match substrings.
|
|
37
25
|
*/
|
|
38
26
|
export declare function grantConveys(actions: readonly GrantAction[], action: GrantAction): boolean;
|
|
39
27
|
/**
|
|
40
28
|
* What a stored `_grant` reaches, or null where it names nothing the
|
|
41
|
-
* evaluator recognizes. Read as data
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* list, on the same terms an unknown grantee `kind` confers nothing.
|
|
46
|
-
* See docs/spec/access-control.md § Refused at the write, and again at evaluation.
|
|
29
|
+
* evaluator recognizes. Read as data — a grant can arrive from an import or
|
|
30
|
+
* a foreign server no schema saw — so an unknown action is dropped, as an
|
|
31
|
+
* unknown grantee confers nothing. See docs/spec/access-control.md
|
|
32
|
+
* § Refused at the write, and again at evaluation.
|
|
47
33
|
*/
|
|
48
34
|
export declare function grantReach(content: unknown): {
|
|
49
35
|
familyId: string;
|
|
@@ -61,6 +47,8 @@ export type GrantQuery = Exclude<GrantGrantee, {
|
|
|
61
47
|
groupId: RecordId;
|
|
62
48
|
role: GroupRole | 'any';
|
|
63
49
|
};
|
|
50
|
+
/** Whether two action lists name the same verbs, order and repeats aside. */
|
|
51
|
+
export declare function sameActions(a: readonly GrantAction[], b: readonly GrantAction[]): boolean;
|
|
64
52
|
/**
|
|
65
53
|
* Direct (non-roster) match between a stored _grant's content and a target:
|
|
66
54
|
* the whole grantee, `role` included. A target is the identity grantType()
|
|
@@ -69,13 +57,10 @@ export type GrantQuery = Exclude<GrantGrantee, {
|
|
|
69
57
|
*/
|
|
70
58
|
export declare function matchesGrantTarget(content: GrantContent, target: GrantQuery): boolean;
|
|
71
59
|
/**
|
|
72
|
-
* Reject a grant target that names no tier, or whose tier names nobody
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* body or an import has whatever shape it arrived with, so a key its tier
|
|
77
|
-
* does not define is refused too. `allowAny` admits
|
|
78
|
-
* the listing-only `role: 'any'`, which grantType() and revokeType() refuse.
|
|
60
|
+
* Reject a grant target that names no tier, or whose tier names nobody: it
|
|
61
|
+
* would store a grant that can only deny while looking like a share that
|
|
62
|
+
* worked. Read as data, so a key its tier does not define is refused too.
|
|
63
|
+
* `allowAny` admits the listing-only `role: 'any'`.
|
|
79
64
|
*/
|
|
80
65
|
export declare function validateGrantTarget(target: GrantQuery, allowAny?: boolean): void;
|
|
81
66
|
/**
|
|
@@ -102,8 +87,7 @@ export declare function validateGrantBaseId(typeId: TypeId, content: unknown): V
|
|
|
102
87
|
export declare function grantCoversGrantee(c: GrantContent, grantee: EntityId, opts: {
|
|
103
88
|
allowDefault: boolean;
|
|
104
89
|
allowGroup: boolean;
|
|
105
|
-
|
|
106
|
-
resolveRecord: (id: RecordId) => Promise<StackRecord | null>;
|
|
90
|
+
roles: RosterRoles;
|
|
107
91
|
}): Promise<boolean>;
|
|
108
92
|
/**
|
|
109
93
|
* System type families grantType() refuses to target: a grant on any of them
|
|
@@ -114,16 +98,8 @@ export declare function grantCoversGrantee(c: GrantContent, grantee: EntityId, o
|
|
|
114
98
|
*/
|
|
115
99
|
export declare const UNGRANTABLE_SYSTEM_TYPES: ReadonlySet<string>;
|
|
116
100
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* itself sit behind a read check.
|
|
120
|
-
*
|
|
121
|
-
* `includeUnlisted`, because withholding a Record from enumeration decides
|
|
122
|
-
* nothing about what it confers. Deleted grants are excluded on the
|
|
123
|
-
* opposite grounds — a soft delete is how revokeType() withdraws one.
|
|
124
|
-
* See docs/spec/unlisted.md.
|
|
101
|
+
* Actions must be known GrantAction values; the target is held to the
|
|
102
|
+
* same rule every `_grant` write meets — see validateGrantBaseId().
|
|
125
103
|
*/
|
|
126
|
-
export declare function
|
|
127
|
-
content: GrantContent;
|
|
128
|
-
})[]>;
|
|
104
|
+
export declare function checkGrantValid(baseId: BaseId, actions: GrantAction[]): void;
|
|
129
105
|
//# sourceMappingURL=grants.d.ts.map
|
package/dist/grants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,
|
|
1
|
+
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EACV,MAAM,EACN,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,WAAW,CAA0B,CAAC;AAejF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAI1F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,GAAG,IAAI,CAUhG;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACxC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,KAAK,CAAA;CAAE,CAAC;AAElE,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,WAAW,EAAE,EAAE,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CASrF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,UAAQ,GAAG,IAAI,CAa9E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAMnF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAiBvF;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GACvE,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAKvD,CAAC;AAEH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,CA2B5E"}
|
package/dist/grants.js
CHANGED
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
* See docs/spec/access-control.md § Type-level grants.
|
|
10
10
|
*/
|
|
11
11
|
import { baseIdOf, familyIdProblem } from './schema.js';
|
|
12
|
-
import { StackBadRequestError } from './errors.js';
|
|
13
|
-
import {
|
|
14
|
-
import { SYSTEM_TYPES, GRANT_ACTIONS } from './types.js';
|
|
15
|
-
import {
|
|
16
|
-
import { queryAllPages } from './stack-reads.js';
|
|
12
|
+
import { ARGUMENTS_INVALID, StackBadRequestError, StackValidationError } from './errors.js';
|
|
13
|
+
import { granteeErrors } from './associations/validation.js';
|
|
14
|
+
import { SYSTEM_TYPES, GRANT_ACTIONS } from './types/index.js';
|
|
15
|
+
import { granteeReaches } from './access.js';
|
|
17
16
|
/**
|
|
18
17
|
* Valid GrantAction values, for runtime validation in Stack.grantType(). Built
|
|
19
18
|
* from GRANT_ACTIONS, which GrantAction itself derives from, so the set and
|
|
@@ -21,28 +20,22 @@ import { queryAllPages } from './stack-reads.js';
|
|
|
21
20
|
*/
|
|
22
21
|
export const GRANT_ACTION_SET = new Set(GRANT_ACTIONS);
|
|
23
22
|
/**
|
|
24
|
-
* The read actions that make a mutate action coherent, scope for scope
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* verb to nobody. `create` has no companion — writing a Record you then
|
|
28
|
-
* cannot read is the drop-box, and it discloses nothing.
|
|
23
|
+
* The read actions that make a mutate action coherent, scope for scope. A
|
|
24
|
+
* grant carrying none conveys the verb to nobody. `create` has none:
|
|
25
|
+
* writing a Record you cannot read is the drop-box, and discloses nothing.
|
|
29
26
|
* See docs/spec/access-control.md § Write implies read.
|
|
30
27
|
*/
|
|
31
|
-
|
|
28
|
+
const READ_COMPANIONS = new Map([
|
|
32
29
|
['update-own', ['read-own', 'read-any']],
|
|
33
30
|
['delete-own', ['read-own', 'read-any']],
|
|
34
31
|
['update-any', ['read-any']],
|
|
35
32
|
['delete-any', ['read-any']],
|
|
36
33
|
]);
|
|
37
34
|
/**
|
|
38
|
-
* Whether one grant's action list conveys `action`. The companion
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* Takes the list grantReach() produced, never a stored field: `includes()`
|
|
44
|
-
* over a string is substring matching, so one would answer for every verb
|
|
45
|
-
* spelled inside it.
|
|
35
|
+
* Whether one grant's action list conveys `action`. The companion must sit
|
|
36
|
+
* in the same `_grant`: a grant is revoked whole, so a rule met across two
|
|
37
|
+
* records could leave mutate-without-read standing. Takes grantReach()'s
|
|
38
|
+
* list, never a stored field, whose `includes()` would match substrings.
|
|
46
39
|
*/
|
|
47
40
|
export function grantConveys(actions, action) {
|
|
48
41
|
if (!actions.includes(action))
|
|
@@ -52,12 +45,10 @@ export function grantConveys(actions, action) {
|
|
|
52
45
|
}
|
|
53
46
|
/**
|
|
54
47
|
* What a stored `_grant` reaches, or null where it names nothing the
|
|
55
|
-
* evaluator recognizes. Read as data
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* list, on the same terms an unknown grantee `kind` confers nothing.
|
|
60
|
-
* See docs/spec/access-control.md § Refused at the write, and again at evaluation.
|
|
48
|
+
* evaluator recognizes. Read as data — a grant can arrive from an import or
|
|
49
|
+
* a foreign server no schema saw — so an unknown action is dropped, as an
|
|
50
|
+
* unknown grantee confers nothing. See docs/spec/access-control.md
|
|
51
|
+
* § Refused at the write, and again at evaluation.
|
|
61
52
|
*/
|
|
62
53
|
export function grantReach(content) {
|
|
63
54
|
const c = content;
|
|
@@ -73,6 +64,12 @@ export function grantReach(content) {
|
|
|
73
64
|
actions: c.actions.filter((a) => GRANT_ACTION_SET.has(a)),
|
|
74
65
|
};
|
|
75
66
|
}
|
|
67
|
+
/** Whether two action lists name the same verbs, order and repeats aside. */
|
|
68
|
+
export function sameActions(a, b) {
|
|
69
|
+
const as = new Set(a);
|
|
70
|
+
const bs = new Set(b);
|
|
71
|
+
return as.size === bs.size && [...bs].every((x) => as.has(x));
|
|
72
|
+
}
|
|
76
73
|
/**
|
|
77
74
|
* Direct (non-roster) match between a stored _grant's content and a target:
|
|
78
75
|
* the whole grantee, `role` included. A target is the identity grantType()
|
|
@@ -92,40 +89,21 @@ export function matchesGrantTarget(content, target) {
|
|
|
92
89
|
return g.kind === 'authenticated';
|
|
93
90
|
}
|
|
94
91
|
/**
|
|
95
|
-
* Reject a grant target that names no tier, or whose tier names nobody
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* body or an import has whatever shape it arrived with, so a key its tier
|
|
100
|
-
* does not define is refused too. `allowAny` admits
|
|
101
|
-
* the listing-only `role: 'any'`, which grantType() and revokeType() refuse.
|
|
92
|
+
* Reject a grant target that names no tier, or whose tier names nobody: it
|
|
93
|
+
* would store a grant that can only deny while looking like a share that
|
|
94
|
+
* worked. Read as data, so a key its tier does not define is refused too.
|
|
95
|
+
* `allowAny` admits the listing-only `role: 'any'`.
|
|
102
96
|
*/
|
|
103
97
|
export function validateGrantTarget(target, allowAny = false) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
case 'group':
|
|
116
|
-
if (typeof t.groupId !== 'string' || t.groupId.length === 0) {
|
|
117
|
-
throw new StackBadRequestError('A group grant target requires a non-empty groupId.');
|
|
118
|
-
}
|
|
119
|
-
if (t.role !== 'member' && t.role !== 'admin' && !(allowAny && t.role === 'any')) {
|
|
120
|
-
throw new StackBadRequestError(allowAny
|
|
121
|
-
? "A group grant target requires role 'member', 'admin' or 'any'."
|
|
122
|
-
: "A group grant target requires role 'member' or 'admin'.");
|
|
123
|
-
}
|
|
124
|
-
// No format check: groupId is a reference, like parentId or an
|
|
125
|
-
// association's recordId. One that resolves to nothing simply denies.
|
|
126
|
-
return;
|
|
127
|
-
default:
|
|
128
|
-
throw new StackBadRequestError("A grant target must name its tier: { kind: 'entity' }, { kind: 'group' } or { kind: 'authenticated' }.");
|
|
98
|
+
// No format check on a groupId: it is a reference, like parentId or an
|
|
99
|
+
// association's recordId. One that resolves to nothing simply denies.
|
|
100
|
+
const errors = granteeErrors(target, 'grant target', {
|
|
101
|
+
unknownKeys: 'throw',
|
|
102
|
+
authenticated: true,
|
|
103
|
+
anyRole: allowAny,
|
|
104
|
+
});
|
|
105
|
+
if (errors.length > 0) {
|
|
106
|
+
throw new StackBadRequestError(`Invalid grant target: ${errors.map((e) => e.message).join(' ')}`);
|
|
129
107
|
}
|
|
130
108
|
}
|
|
131
109
|
/**
|
|
@@ -137,55 +115,11 @@ export function validateGrantTarget(target, allowAny = false) {
|
|
|
137
115
|
export function validateGrantee(typeId, content) {
|
|
138
116
|
if (baseIdOf(typeId) !== SYSTEM_TYPES.GRANT)
|
|
139
117
|
return [];
|
|
140
|
-
const
|
|
141
|
-
const g = c?.grantee;
|
|
118
|
+
const g = content?.grantee;
|
|
142
119
|
// An absent or non-object grantee is the schema's to refuse, and it does.
|
|
143
120
|
if (!g || typeof g !== 'object')
|
|
144
121
|
return [];
|
|
145
|
-
|
|
146
|
-
const unknown = unknownKeys(g, GRANTEE_KEYS[g.kind]);
|
|
147
|
-
if (unknown.length > 0)
|
|
148
|
-
return unknown.map((key) => ({
|
|
149
|
-
path: `grantee.${key}`,
|
|
150
|
-
message: `A ${String(g.kind)} grantee does not carry ${key}`,
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
switch (g.kind) {
|
|
154
|
-
case 'authenticated':
|
|
155
|
-
return [];
|
|
156
|
-
case 'entity':
|
|
157
|
-
return typeof g.entityId === 'string' && g.entityId.length > 0
|
|
158
|
-
? []
|
|
159
|
-
: [
|
|
160
|
-
{
|
|
161
|
-
path: 'grantee.entityId',
|
|
162
|
-
message: 'An entity grantee requires a non-empty entityId',
|
|
163
|
-
},
|
|
164
|
-
];
|
|
165
|
-
case 'group': {
|
|
166
|
-
const errors = [];
|
|
167
|
-
if (typeof g.groupId !== 'string' || g.groupId.length === 0) {
|
|
168
|
-
errors.push({
|
|
169
|
-
path: 'grantee.groupId',
|
|
170
|
-
message: 'A group grantee requires a non-empty groupId',
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
if (g.role !== 'member' && g.role !== 'admin') {
|
|
174
|
-
errors.push({
|
|
175
|
-
path: 'grantee.role',
|
|
176
|
-
message: "A group grantee requires role 'member' or 'admin'",
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return errors;
|
|
180
|
-
}
|
|
181
|
-
default:
|
|
182
|
-
return [
|
|
183
|
-
{
|
|
184
|
-
path: 'grantee.kind',
|
|
185
|
-
message: "A grantee must name its tier: 'entity', 'group' or 'authenticated'",
|
|
186
|
-
},
|
|
187
|
-
];
|
|
188
|
-
}
|
|
122
|
+
return granteeErrors(g, 'grantee', { unknownKeys: 'collect', authenticated: true });
|
|
189
123
|
}
|
|
190
124
|
/**
|
|
191
125
|
* A `_grant`'s target, asked of its content on every write: a bare baseId
|
|
@@ -223,42 +157,9 @@ export function validateGrantBaseId(typeId, content) {
|
|
|
223
157
|
*/
|
|
224
158
|
export async function grantCoversGrantee(c, grantee, opts) {
|
|
225
159
|
const g = c.grantee;
|
|
226
|
-
if (
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
case 'authenticated':
|
|
230
|
-
return opts.allowDefault;
|
|
231
|
-
case 'entity':
|
|
232
|
-
return !!g.entityId && g.entityId === grantee;
|
|
233
|
-
case 'group': {
|
|
234
|
-
if (!opts.allowGroup)
|
|
235
|
-
return false;
|
|
236
|
-
if (!g.groupId)
|
|
237
|
-
return false;
|
|
238
|
-
if (g.role !== 'member' && g.role !== 'admin')
|
|
239
|
-
return false;
|
|
240
|
-
const role = await resolveGroupRoleMemoized(g.groupId, grantee, opts.groupRoles, opts.resolveRecord);
|
|
241
|
-
return g.role === 'admin' ? role === 'admin' : role !== null;
|
|
242
|
-
}
|
|
243
|
-
default:
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* An entity's role on a `_group` roster, memoized in the caller's
|
|
249
|
-
* `groupRoles` map. That map is built per operation, so no resolved role
|
|
250
|
-
* outlives the operation that resolved it — removal from a group must never
|
|
251
|
-
* go stale.
|
|
252
|
-
*/
|
|
253
|
-
async function resolveGroupRoleMemoized(groupId, entityId, groupRoles, resolveRecord) {
|
|
254
|
-
const key = `${groupId}:${entityId}`;
|
|
255
|
-
const cached = groupRoles.get(key);
|
|
256
|
-
if (cached !== undefined)
|
|
257
|
-
return cached;
|
|
258
|
-
const group = await resolveRecord(groupId);
|
|
259
|
-
const role = group && carriesRoster(group) ? groupRoleFromAssociations(group.associations, entityId) : null;
|
|
260
|
-
groupRoles.set(key, role);
|
|
261
|
-
return role;
|
|
160
|
+
if (g?.kind === 'authenticated')
|
|
161
|
+
return opts.allowDefault;
|
|
162
|
+
return granteeReaches(g, grantee, opts);
|
|
262
163
|
}
|
|
263
164
|
/**
|
|
264
165
|
* System type families grantType() refuses to target: a grant on any of them
|
|
@@ -274,19 +175,36 @@ export const UNGRANTABLE_SYSTEM_TYPES = new Set([
|
|
|
274
175
|
SYSTEM_TYPES.INSTALL,
|
|
275
176
|
]);
|
|
276
177
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* itself sit behind a read check.
|
|
280
|
-
*
|
|
281
|
-
* `includeUnlisted`, because withholding a Record from enumeration decides
|
|
282
|
-
* nothing about what it confers. Deleted grants are excluded on the
|
|
283
|
-
* opposite grounds — a soft delete is how revokeType() withdraws one.
|
|
284
|
-
* See docs/spec/unlisted.md.
|
|
178
|
+
* Actions must be known GrantAction values; the target is held to the
|
|
179
|
+
* same rule every `_grant` write meets — see validateGrantBaseId().
|
|
285
180
|
*/
|
|
286
|
-
export
|
|
287
|
-
const
|
|
288
|
-
|
|
181
|
+
export function checkGrantValid(baseId, actions) {
|
|
182
|
+
const errors = [];
|
|
183
|
+
actions.forEach((action, j) => {
|
|
184
|
+
if (!GRANT_ACTION_SET.has(action)) {
|
|
185
|
+
errors.push({ path: `actions[${j}]`, message: `Unknown grant action "${action}"` });
|
|
186
|
+
}
|
|
289
187
|
});
|
|
290
|
-
|
|
188
|
+
const problem = familyIdProblem(baseId, 'grantType');
|
|
189
|
+
if (problem) {
|
|
190
|
+
errors.push({ path: 'baseId', message: problem });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
errors.push(...validateGrantBaseId(`${SYSTEM_TYPES.GRANT}@1`, { baseId }));
|
|
194
|
+
}
|
|
195
|
+
actions.forEach((action, j) => {
|
|
196
|
+
if (grantConveys(actions, action))
|
|
197
|
+
return;
|
|
198
|
+
const companions = READ_COMPANIONS.get(action);
|
|
199
|
+
if (!companions)
|
|
200
|
+
return;
|
|
201
|
+
errors.push({
|
|
202
|
+
path: `actions[${j}]`,
|
|
203
|
+
message: `"${action}" requires ${companions.map((c) => `"${c}"`).join(' or ')} in the same grant: a mutate verb reaches the record and its history, so it conveys nothing without read. Name both in one grant: actions: [${companions.map((c) => `'${c}'`).join(' | ')}, '${action}']`,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
if (errors.length > 0) {
|
|
207
|
+
throw new StackValidationError(errors, ARGUMENTS_INVALID);
|
|
208
|
+
}
|
|
291
209
|
}
|
|
292
210
|
//# sourceMappingURL=grants.js.map
|