@haverstack/core 0.28.0 → 0.30.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 +9 -2
- package/dist/access.d.ts +20 -0
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +26 -0
- package/dist/access.js.map +1 -1
- package/dist/adapter-entry.d.ts +1 -1
- package/dist/adapter-entry.d.ts.map +1 -1
- package/dist/adapter-entry.js +1 -1
- package/dist/adapter-entry.js.map +1 -1
- package/dist/changes.d.ts +15 -2
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +62 -6
- package/dist/changes.js.map +1 -1
- package/dist/combine.d.ts.map +1 -1
- package/dist/combine.js +1 -4
- package/dist/combine.js.map +1 -1
- package/dist/errors.d.ts +173 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +208 -0
- package/dist/errors.js.map +1 -0
- package/dist/grants.d.ts +84 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +157 -0
- package/dist/grants.js.map +1 -0
- package/dist/id.d.ts +7 -5
- package/dist/id.d.ts.map +1 -1
- package/dist/id.js +17 -15
- package/dist/id.js.map +1 -1
- package/dist/identity-bindings.d.ts +13 -0
- package/dist/identity-bindings.d.ts.map +1 -0
- package/dist/identity-bindings.js +42 -0
- package/dist/identity-bindings.js.map +1 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +23 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +35 -0
- package/dist/limits.js.map +1 -0
- package/dist/merge.d.ts +1 -1
- package/dist/merge.js +2 -2
- package/dist/merge.js.map +1 -1
- package/dist/query-validation.d.ts +80 -0
- package/dist/query-validation.d.ts.map +1 -0
- package/dist/query-validation.js +223 -0
- package/dist/query-validation.js.map +1 -0
- package/dist/record-changes.d.ts +89 -0
- package/dist/record-changes.d.ts.map +1 -0
- package/dist/record-changes.js +201 -0
- package/dist/record-changes.js.map +1 -0
- package/dist/record-id.d.ts +70 -0
- package/dist/record-id.d.ts.map +1 -0
- package/dist/record-id.js +127 -0
- package/dist/record-id.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +56 -5
- package/dist/schema.js.map +1 -1
- package/dist/scoped-stack.d.ts +460 -0
- package/dist/scoped-stack.d.ts.map +1 -0
- package/dist/scoped-stack.js +1163 -0
- package/dist/scoped-stack.js.map +1 -0
- package/dist/stack-reads.d.ts +38 -0
- package/dist/stack-reads.d.ts.map +1 -0
- package/dist/stack-reads.js +79 -0
- package/dist/stack-reads.js.map +1 -0
- package/dist/stack.d.ts +108 -700
- package/dist/stack.d.ts.map +1 -1
- package/dist/stack.js +235 -2286
- package/dist/stack.js.map +1 -1
- package/dist/testing.d.ts +3 -14
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +30 -42
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +107 -30
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -1
- package/dist/validate.d.ts +31 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +150 -3
- package/dist/validate.js.map +1 -1
- package/dist/wire-entry.d.ts +3 -3
- package/dist/wire-entry.d.ts.map +1 -1
- package/dist/wire-entry.js +3 -3
- package/dist/wire-entry.js.map +1 -1
- package/dist/wire-record.d.ts +16 -1
- package/dist/wire-record.d.ts.map +1 -1
- package/dist/wire-record.js +57 -1
- package/dist/wire-record.js.map +1 -1
- package/dist/wire-request.d.ts.map +1 -1
- package/dist/wire-request.js +3 -2
- package/dist/wire-request.js.map +1 -1
- package/package.json +1 -1
package/dist/errors.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack errors — the error taxonomy
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Every error a Stack operation can raise, in one place. The hierarchy has
|
|
5
|
+
* a single root so a server's error middleware can ask one question —
|
|
6
|
+
* `instanceof StackError` — before serializing a wire body, and each
|
|
7
|
+
* subclass carries its wire discriminator as an instance `code`.
|
|
8
|
+
*
|
|
9
|
+
* Three errors sit deliberately outside that root (StackClosedError,
|
|
10
|
+
* StackRelayScopeError, and — in their own modules — IdGenerationError and
|
|
11
|
+
* InvalidDidError): they report a local programming error or an assembled
|
|
12
|
+
* topology, not a state a request can be in, so no server ever responds
|
|
13
|
+
* with one.
|
|
14
|
+
*/
|
|
15
|
+
import { baseIdOf, parseTypeId } from './schema.js';
|
|
16
|
+
/**
|
|
17
|
+
* Root of the Stack error taxonomy. A single `instanceof StackError` answers
|
|
18
|
+
* "is this a Stack-domain error or a bug?" — the question a server's error
|
|
19
|
+
* middleware asks before serializing a wire body, and one a nine-arm
|
|
20
|
+
* instanceof ladder answers only by exhaustion. Every subclass carries its
|
|
21
|
+
* discriminator as an instance `code`, so serialization is a lookup rather
|
|
22
|
+
* than a chain of class tests.
|
|
23
|
+
*
|
|
24
|
+
* Membership implies a wire mapping: every code has an entry in
|
|
25
|
+
* WIRE_ERROR_STATUS. Errors with no wire representation (IdGenerationError,
|
|
26
|
+
* InvalidDidError) deliberately stay outside this hierarchy.
|
|
27
|
+
*
|
|
28
|
+
* Subclassing adds no hierarchy beyond this root — notably
|
|
29
|
+
* StackVersionConflictError is a sibling of StackConflictError, not a
|
|
30
|
+
* subtype. See docs/spec/wire-format.md § Error responses.
|
|
31
|
+
*/
|
|
32
|
+
export class StackError extends Error {
|
|
33
|
+
}
|
|
34
|
+
export class StackValidationError extends StackError {
|
|
35
|
+
errors;
|
|
36
|
+
static code = 'validation';
|
|
37
|
+
code = StackValidationError.code;
|
|
38
|
+
constructor(errors) {
|
|
39
|
+
super(`Content validation failed:\n` + errors.map((e) => ` ${e.path}: ${e.message}`).join('\n'));
|
|
40
|
+
this.errors = errors;
|
|
41
|
+
this.name = 'StackValidationError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class StackMigrationError extends StackError {
|
|
45
|
+
static code = 'migration';
|
|
46
|
+
code = StackMigrationError.code;
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = 'StackMigrationError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Thrown by ScopedStack when a requester lacks permission for the operation. */
|
|
53
|
+
export class StackPermissionError extends StackError {
|
|
54
|
+
static code = 'permission';
|
|
55
|
+
code = StackPermissionError.code;
|
|
56
|
+
constructor(message = 'Permission denied') {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = 'StackPermissionError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Thrown when a record (or specific version) does not exist. */
|
|
62
|
+
export class StackNotFoundError extends StackError {
|
|
63
|
+
static code = 'not_found';
|
|
64
|
+
code = StackNotFoundError.code;
|
|
65
|
+
constructor(message) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = 'StackNotFoundError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Thrown when an operation cannot proceed due to a constraint violation (e.g. deleting an attachment that is still referenced). */
|
|
71
|
+
export class StackConflictError extends StackError {
|
|
72
|
+
static code = 'conflict';
|
|
73
|
+
code = StackConflictError.code;
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = 'StackConflictError';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Thrown when an `ifVersion` precondition doesn't match a record's current
|
|
81
|
+
* version. Deliberately not a StackConflictError subtype — the two have
|
|
82
|
+
* different recovery stories and HTTP statuses (409 vs. 412). See
|
|
83
|
+
* docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).
|
|
84
|
+
*/
|
|
85
|
+
export class StackVersionConflictError extends StackError {
|
|
86
|
+
recordId;
|
|
87
|
+
expectedVersion;
|
|
88
|
+
actualVersion;
|
|
89
|
+
static code = 'version_conflict';
|
|
90
|
+
code = StackVersionConflictError.code;
|
|
91
|
+
constructor(message, recordId, expectedVersion, actualVersion) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.recordId = recordId;
|
|
94
|
+
this.expectedVersion = expectedVersion;
|
|
95
|
+
this.actualVersion = actualVersion;
|
|
96
|
+
this.name = 'StackVersionConflictError';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Thrown when a request is structurally malformed — not a content-validation
|
|
101
|
+
* failure, but input the adapter/server can't even interpret (e.g. an
|
|
102
|
+
* undecodable pagination cursor, a malformed TypeId, search text the engine
|
|
103
|
+
* cannot parse, or a typeId no definition exists for). Distinct from
|
|
104
|
+
* StackValidationError, which means the request was well-formed but content
|
|
105
|
+
* failed schema validation.
|
|
106
|
+
*
|
|
107
|
+
* Naming something absent belongs here rather than under `not_found`: a
|
|
108
|
+
* request whose *type* is undefined never addressed a record, so answering
|
|
109
|
+
* 404 would say a record was missing when none was asked for.
|
|
110
|
+
*/
|
|
111
|
+
export class StackQueryError extends StackError {
|
|
112
|
+
static code = 'bad_request';
|
|
113
|
+
code = StackQueryError.code;
|
|
114
|
+
constructor(message) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.name = 'StackQueryError';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Thrown when an attachment upload exceeds the adapter's declared
|
|
121
|
+
* `limits.attachmentBytes` ceiling — checked client-side before any bytes
|
|
122
|
+
* are sent; a server still enforces 413 authoritatively regardless. See
|
|
123
|
+
* docs/spec/wire-format.md § Attachments.
|
|
124
|
+
*/
|
|
125
|
+
export class StackPayloadTooLargeError extends StackError {
|
|
126
|
+
static code = 'payload_too_large';
|
|
127
|
+
code = StackPayloadTooLargeError.code;
|
|
128
|
+
constructor(message) {
|
|
129
|
+
super(message);
|
|
130
|
+
this.name = 'StackPayloadTooLargeError';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Thrown when a server abandons an operation for taking too long — in
|
|
135
|
+
* practice a full-text search, the one query whose cost the sanitizers
|
|
136
|
+
* bound the *grammar* of but not the execution of (see
|
|
137
|
+
* docs/spec/data-model.md § Capability-gated filters).
|
|
138
|
+
*
|
|
139
|
+
* Never produced in-process: both SQLite engines run synchronously, so
|
|
140
|
+
* there is nothing to interrupt from inside the call. It exists so a
|
|
141
|
+
* server that bounds query time has a class to serialize, and so the app
|
|
142
|
+
* catching it can tell "too expensive, narrow it and retry" from
|
|
143
|
+
* StackQueryError's "malformed, don't bother retrying" — the distinction
|
|
144
|
+
* that would be lost if a timeout reused `bad_request`.
|
|
145
|
+
*/
|
|
146
|
+
export class StackTimeoutError extends StackError {
|
|
147
|
+
static code = 'timeout';
|
|
148
|
+
code = StackTimeoutError.code;
|
|
149
|
+
constructor(message) {
|
|
150
|
+
super(message);
|
|
151
|
+
this.name = 'StackTimeoutError';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Thrown by defineType() when redefining an existing typeId with a schema
|
|
156
|
+
* change beyond additive evolution. The remedy is a new version, never an
|
|
157
|
+
* in-place redefinition. See docs/spec/data-model.md § Schema drift
|
|
158
|
+
* detection.
|
|
159
|
+
*/
|
|
160
|
+
export class StackSchemaDriftError extends StackError {
|
|
161
|
+
typeId;
|
|
162
|
+
violations;
|
|
163
|
+
static code = 'schema_drift';
|
|
164
|
+
code = StackSchemaDriftError.code;
|
|
165
|
+
constructor(typeId, violations) {
|
|
166
|
+
super(`Schema drift detected for type "${typeId}": the stored schema and the new definition ` +
|
|
167
|
+
`differ beyond additive evolution (new optional fields only). Bump the version instead ` +
|
|
168
|
+
`of redefining "${typeId}" in place — e.g. defineType(\`${baseIdOf(typeId)}@${(parseTypeId(typeId)?.version ?? 0) + 1}\`, ...) plus registerMigration().\n` +
|
|
169
|
+
violations.map((v) => ` ${v.path || '(root)'}: ${v.message}`).join('\n'));
|
|
170
|
+
this.typeId = typeId;
|
|
171
|
+
this.violations = violations;
|
|
172
|
+
this.name = 'StackSchemaDriftError';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Thrown when a Stack or ScopedStack is used after close(). Deliberately
|
|
177
|
+
* outside the StackError taxonomy, alongside IdGenerationError and
|
|
178
|
+
* InvalidDidError: a caller holding a closed client is a local programming
|
|
179
|
+
* error with no wire representation — no server ever responds with it.
|
|
180
|
+
* See docs/spec/adapters.md § Lifecycle.
|
|
181
|
+
*/
|
|
182
|
+
export class StackClosedError extends Error {
|
|
183
|
+
constructor(message = 'This Stack has been closed.') {
|
|
184
|
+
super(message);
|
|
185
|
+
this.name = 'StackClosedError';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Thrown when a scoped view is asked to observe a stack whose adapter
|
|
190
|
+
* relays a remote feed. Outside the StackError taxonomy for the same
|
|
191
|
+
* reason StackClosedError is: it reports a topology the caller assembled,
|
|
192
|
+
* not a state a request can be in.
|
|
193
|
+
*
|
|
194
|
+
* A relayed frame is scoped by the authority that opened the feed, and a
|
|
195
|
+
* narrower scope cannot re-derive that decision — a purge leaves no record
|
|
196
|
+
* to check `canRead` against. Delivering anyway would break the promise
|
|
197
|
+
* that a subscriber never sees what it may not read; delivering only local
|
|
198
|
+
* writes would silently drop every change made elsewhere, which is the
|
|
199
|
+
* failure that looks fine in testing. So it refuses.
|
|
200
|
+
* See docs/spec/events.md § Permission scoping.
|
|
201
|
+
*/
|
|
202
|
+
export class StackRelayScopeError extends Error {
|
|
203
|
+
constructor(message) {
|
|
204
|
+
super(message);
|
|
205
|
+
this.name = 'StackRelayScopeError';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/grants.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grant vocabulary and coverage
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* What a `_grant` Record says, and who it says it about. The coverage
|
|
5
|
+
* question — does this grant reach this grantee — is answered here rather
|
|
6
|
+
* than at each call site so that the access checks and listGrants() cannot
|
|
7
|
+
* drift apart about it.
|
|
8
|
+
*
|
|
9
|
+
* See docs/spec/access-control.md § Type-level grants.
|
|
10
|
+
*/
|
|
11
|
+
import type { GroupRole } from './access.js';
|
|
12
|
+
import type { EntityId, GrantAction, GrantContent, QueryResult, RecordId, StackQuery, StackRecord } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Valid GrantAction values, for runtime validation in Stack.grant().
|
|
15
|
+
* Built from GRANT_ACTIONS (types.ts), the source of truth GrantAction is
|
|
16
|
+
* itself derived from — so this can't drift from the type.
|
|
17
|
+
*/
|
|
18
|
+
export declare const GRANT_ACTION_SET: ReadonlySet<GrantAction>;
|
|
19
|
+
/**
|
|
20
|
+
* The read actions that make a mutate action coherent, scope for scope: a
|
|
21
|
+
* `-any` verb needs read over the same reach it can mutate, a `-own` verb
|
|
22
|
+
* needs read over its author's own. A grant carrying neither conveys the
|
|
23
|
+
* verb to nobody. `create` has no companion — writing a Record you then
|
|
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, not merely somewhere in the grantee's
|
|
31
|
+
* set: a grant is revoked whole, so a rule satisfied across two records
|
|
32
|
+
* would let revoking the read one leave a mutate-without-read grant
|
|
33
|
+
* standing — the configuration this rule exists to refuse, arrived at
|
|
34
|
+
* without anyone writing it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function grantConveys(actions: readonly string[], action: GrantAction): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Who a grant() / revoke() / listGrants() call targets: a specific entity
|
|
39
|
+
* (DID), a `_group` Record's roster (by ID), or `null` for the default
|
|
40
|
+
* grant / default-only listing. See docs/spec/access-control.md § Type-level
|
|
41
|
+
* grants.
|
|
42
|
+
*/
|
|
43
|
+
export type GrantTarget = EntityId | {
|
|
44
|
+
groupId: RecordId;
|
|
45
|
+
} | null;
|
|
46
|
+
/** Direct (non-roster) match between a stored _grant's content and a GrantTarget. */
|
|
47
|
+
export declare function matchesGrantTarget(content: GrantContent, target: GrantTarget): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Reject a grant target that names nobody. An empty groupId or entityId is
|
|
50
|
+
* falsy, so a stored record carrying one reads as a *default* grant — every
|
|
51
|
+
* authenticated entity — instead of the target the caller meant. `null` is
|
|
52
|
+
* the only way to say "default".
|
|
53
|
+
*/
|
|
54
|
+
export declare function validateGrantTarget(target: GrantTarget): void;
|
|
55
|
+
/**
|
|
56
|
+
* Whether a stored _grant covers `grantee`: a direct DID match, roster
|
|
57
|
+
* membership when `allowGroup`, or a default when `allowDefault`. Presence
|
|
58
|
+
* decides the tier, never truthiness — an empty grantee field names nobody.
|
|
59
|
+
* Module-level so the access checks and listGrants() cannot drift apart.
|
|
60
|
+
* See docs/spec/access-control.md § Type-level grants.
|
|
61
|
+
*/
|
|
62
|
+
export declare function grantCoversGrantee(c: GrantContent, grantee: EntityId, opts: {
|
|
63
|
+
allowDefault: boolean;
|
|
64
|
+
allowGroup: boolean;
|
|
65
|
+
groupRoles: Map<string, GroupRole | null>;
|
|
66
|
+
resolveRecord: (id: RecordId) => Promise<StackRecord | null>;
|
|
67
|
+
}): Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* System type families grant() refuses to target: a grant on any of them
|
|
70
|
+
* would let the grantee mint their own grants, touch stack config, or
|
|
71
|
+
* register an app card claiming a DID that isn't theirs — the last of
|
|
72
|
+
* which is what verified app attribution rests on. See
|
|
73
|
+
* docs/spec/access-control.md § Type-level grants.
|
|
74
|
+
*/
|
|
75
|
+
export declare const UNGRANTABLE_SYSTEM_TYPES: ReadonlySet<string>;
|
|
76
|
+
/**
|
|
77
|
+
* Every `_grant` Record, cursor-walked. Read through an unscoped query at
|
|
78
|
+
* every call site: a grant is what decides who may read, so it can never
|
|
79
|
+
* itself sit behind a read check. No content prefilter — a stored grant's
|
|
80
|
+
* typeId may be a bare baseId or versioned, so exact matching would wrongly
|
|
81
|
+
* exclude family versions.
|
|
82
|
+
*/
|
|
83
|
+
export declare function loadGrantRecords(query: (q: StackQuery) => Promise<QueryResult>): Promise<StackRecord[]>;
|
|
84
|
+
//# sourceMappingURL=grants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,WAAW,CAA0B,CAAC;AAEjF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,WAAW,EAAE,SAAS,WAAW,EAAE,CAK3E,CAAC;AAEH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAIrF;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,GAAG,IAAI,CAAC;AAElE,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAYtF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAgB7D;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE;IACJ,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC,CAAC;IAC1C,aAAa,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC9D,GACA,OAAO,CAAC,OAAO,CAAC,CAiBlB;AA+BD;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAIvD,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,WAAW,CAAC,GAC7C,OAAO,CAAC,WAAW,EAAE,CAAC,CAExB"}
|
package/dist/grants.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grant vocabulary and coverage
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* What a `_grant` Record says, and who it says it about. The coverage
|
|
5
|
+
* question — does this grant reach this grantee — is answered here rather
|
|
6
|
+
* than at each call site so that the access checks and listGrants() cannot
|
|
7
|
+
* drift apart about it.
|
|
8
|
+
*
|
|
9
|
+
* See docs/spec/access-control.md § Type-level grants.
|
|
10
|
+
*/
|
|
11
|
+
import { baseIdOf } from './schema.js';
|
|
12
|
+
import { StackQueryError } from './errors.js';
|
|
13
|
+
import { SYSTEM_TYPES, GRANT_ACTIONS } from './types.js';
|
|
14
|
+
import { groupRoleFromAssociations } from './access.js';
|
|
15
|
+
import { queryAllPages } from './stack-reads.js';
|
|
16
|
+
/**
|
|
17
|
+
* Valid GrantAction values, for runtime validation in Stack.grant().
|
|
18
|
+
* Built from GRANT_ACTIONS (types.ts), the source of truth GrantAction is
|
|
19
|
+
* itself derived from — so this can't drift from the type.
|
|
20
|
+
*/
|
|
21
|
+
export const GRANT_ACTION_SET = new Set(GRANT_ACTIONS);
|
|
22
|
+
/**
|
|
23
|
+
* The read actions that make a mutate action coherent, scope for scope: a
|
|
24
|
+
* `-any` verb needs read over the same reach it can mutate, a `-own` verb
|
|
25
|
+
* needs read over its author's own. A grant carrying neither conveys the
|
|
26
|
+
* verb to nobody. `create` has no companion — writing a Record you then
|
|
27
|
+
* cannot read is the drop-box, and it discloses nothing.
|
|
28
|
+
* See docs/spec/access-control.md § Write implies read.
|
|
29
|
+
*/
|
|
30
|
+
export const READ_COMPANIONS = new Map([
|
|
31
|
+
['update-own', ['read-own', 'read-any']],
|
|
32
|
+
['delete-own', ['read-own', 'read-any']],
|
|
33
|
+
['update-any', ['read-any']],
|
|
34
|
+
['delete-any', ['read-any']],
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* Whether one grant's action list conveys `action`. The companion has to
|
|
38
|
+
* sit in the same `_grant` Record, not merely somewhere in the grantee's
|
|
39
|
+
* set: a grant is revoked whole, so a rule satisfied across two records
|
|
40
|
+
* would let revoking the read one leave a mutate-without-read grant
|
|
41
|
+
* standing — the configuration this rule exists to refuse, arrived at
|
|
42
|
+
* without anyone writing it.
|
|
43
|
+
*/
|
|
44
|
+
export function grantConveys(actions, action) {
|
|
45
|
+
if (!actions.includes(action))
|
|
46
|
+
return false;
|
|
47
|
+
const companions = READ_COMPANIONS.get(action);
|
|
48
|
+
return !companions || companions.some((c) => actions.includes(c));
|
|
49
|
+
}
|
|
50
|
+
/** Direct (non-roster) match between a stored _grant's content and a GrantTarget. */
|
|
51
|
+
export function matchesGrantTarget(content, target) {
|
|
52
|
+
if (target !== null && typeof target === 'object') {
|
|
53
|
+
// Guarded so an absent groupId can't match the absent granteeGroupId on
|
|
54
|
+
// every entity-targeted and default grant — `undefined === undefined`
|
|
55
|
+
// would otherwise sweep them all into a revoke aimed at one group.
|
|
56
|
+
if (!target.groupId)
|
|
57
|
+
return false;
|
|
58
|
+
return content.granteeGroupId === target.groupId;
|
|
59
|
+
}
|
|
60
|
+
if (target === null) {
|
|
61
|
+
return !content.granteeEntityId && !content.granteeGroupId;
|
|
62
|
+
}
|
|
63
|
+
return content.granteeEntityId === target;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reject a grant target that names nobody. An empty groupId or entityId is
|
|
67
|
+
* falsy, so a stored record carrying one reads as a *default* grant — every
|
|
68
|
+
* authenticated entity — instead of the target the caller meant. `null` is
|
|
69
|
+
* the only way to say "default".
|
|
70
|
+
*/
|
|
71
|
+
export function validateGrantTarget(target) {
|
|
72
|
+
if (target === null)
|
|
73
|
+
return;
|
|
74
|
+
if (typeof target === 'string') {
|
|
75
|
+
if (target.length === 0) {
|
|
76
|
+
throw new StackQueryError('A grant target entityId cannot be empty. Pass null for a default grant.');
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (typeof target.groupId !== 'string' || target.groupId.length === 0) {
|
|
81
|
+
throw new StackQueryError('A group grant target requires a non-empty groupId.');
|
|
82
|
+
}
|
|
83
|
+
// Not a format check: granteeGroupId is a reference to an existing
|
|
84
|
+
// Record, like parentId or an association's recordId, and none of those
|
|
85
|
+
// are parsed either. One that resolves to nothing simply denies.
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Whether a stored _grant covers `grantee`: a direct DID match, roster
|
|
89
|
+
* membership when `allowGroup`, or a default when `allowDefault`. Presence
|
|
90
|
+
* decides the tier, never truthiness — an empty grantee field names nobody.
|
|
91
|
+
* Module-level so the access checks and listGrants() cannot drift apart.
|
|
92
|
+
* See docs/spec/access-control.md § Type-level grants.
|
|
93
|
+
*/
|
|
94
|
+
export async function grantCoversGrantee(c, grantee, opts) {
|
|
95
|
+
const namesEntity = c.granteeEntityId !== undefined;
|
|
96
|
+
const namesGroup = c.granteeGroupId !== undefined;
|
|
97
|
+
if (!namesEntity && !namesGroup)
|
|
98
|
+
return opts.allowDefault;
|
|
99
|
+
if (namesEntity && c.granteeEntityId !== grantee)
|
|
100
|
+
return false;
|
|
101
|
+
if (namesGroup) {
|
|
102
|
+
if (!opts.allowGroup)
|
|
103
|
+
return false;
|
|
104
|
+
if (!c.granteeGroupId)
|
|
105
|
+
return false;
|
|
106
|
+
const role = await resolveGroupRoleMemoized(c.granteeGroupId, grantee, opts.groupRoles, opts.resolveRecord);
|
|
107
|
+
if (role === null)
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* An entity's role on a `_group` roster, memoized in the caller's
|
|
114
|
+
* `groupRoles` map. That map is built per operation and threaded alongside
|
|
115
|
+
* `prefetchedGrants`, so no resolved role outlives the operation that
|
|
116
|
+
* resolved it — removal from a group must never go stale.
|
|
117
|
+
* See docs/spec/access-control.md § Type-level grants.
|
|
118
|
+
*/
|
|
119
|
+
async function resolveGroupRoleMemoized(groupId, entityId, groupRoles, resolveRecord) {
|
|
120
|
+
const key = `${groupId}:${entityId}`;
|
|
121
|
+
const cached = groupRoles.get(key);
|
|
122
|
+
if (cached !== undefined)
|
|
123
|
+
return cached;
|
|
124
|
+
const group = await resolveRecord(groupId);
|
|
125
|
+
// Only a real `_group` Record carries a roster. Without the family check
|
|
126
|
+
// any Record's relationship associations would serve as one, and a group
|
|
127
|
+
// migrated out of the family would keep resolving after it had stopped
|
|
128
|
+
// being a group.
|
|
129
|
+
const role = group && baseIdOf(group.typeId) === SYSTEM_TYPES.GROUP
|
|
130
|
+
? groupRoleFromAssociations(group.associations, entityId)
|
|
131
|
+
: null;
|
|
132
|
+
groupRoles.set(key, role);
|
|
133
|
+
return role;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* System type families grant() refuses to target: a grant on any of them
|
|
137
|
+
* would let the grantee mint their own grants, touch stack config, or
|
|
138
|
+
* register an app card claiming a DID that isn't theirs — the last of
|
|
139
|
+
* which is what verified app attribution rests on. See
|
|
140
|
+
* docs/spec/access-control.md § Type-level grants.
|
|
141
|
+
*/
|
|
142
|
+
export const UNGRANTABLE_SYSTEM_TYPES = new Set([
|
|
143
|
+
SYSTEM_TYPES.GRANT,
|
|
144
|
+
SYSTEM_TYPES.CONFIG,
|
|
145
|
+
SYSTEM_TYPES.APP,
|
|
146
|
+
]);
|
|
147
|
+
/**
|
|
148
|
+
* Every `_grant` Record, cursor-walked. Read through an unscoped query at
|
|
149
|
+
* every call site: a grant is what decides who may read, so it can never
|
|
150
|
+
* itself sit behind a read check. No content prefilter — a stored grant's
|
|
151
|
+
* typeId may be a bare baseId or versioned, so exact matching would wrongly
|
|
152
|
+
* exclude family versions.
|
|
153
|
+
*/
|
|
154
|
+
export function loadGrantRecords(query) {
|
|
155
|
+
return queryAllPages(query, { filter: { typeId: `${SYSTEM_TYPES.GRANT}@1` } });
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"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"}
|
package/dist/id.d.ts
CHANGED
|
@@ -15,9 +15,6 @@ export declare const RAND_SUFFIX_LENGTH = 3;
|
|
|
15
15
|
export declare class IdGenerationError extends Error {
|
|
16
16
|
constructor(message?: string);
|
|
17
17
|
}
|
|
18
|
-
export declare class IdGenerationOverflowError extends IdGenerationError {
|
|
19
|
-
constructor(message?: string);
|
|
20
|
-
}
|
|
21
18
|
export declare const crockford32Encode: (n: number) => string;
|
|
22
19
|
export declare const crockford32Decode: (s: string) => number;
|
|
23
20
|
export declare const _setLastNowId: (chars: string) => void;
|
|
@@ -39,8 +36,13 @@ export declare const MAX_ID_TIMESTAMP: number;
|
|
|
39
36
|
/**
|
|
40
37
|
* Generate a new Stack record ID. Time-sortable: lexicographic order
|
|
41
38
|
* matches creation order, with same-millisecond IDs monotonically
|
|
42
|
-
* incremented.
|
|
43
|
-
*
|
|
39
|
+
* incremented.
|
|
40
|
+
*
|
|
41
|
+
* A millisecond whose suffix space is spent carries into the next one
|
|
42
|
+
* rather than failing, so how many IDs a millisecond holds never depends
|
|
43
|
+
* on where its random suffix happened to start — which is what keeps the
|
|
44
|
+
* order total and the call free of a capacity a caller cannot see.
|
|
45
|
+
* See docs/spec/data-model.md § Record IDs.
|
|
44
46
|
*
|
|
45
47
|
* @param timestamp - Override the timestamp (ms since epoch). Defaults to Date.now().
|
|
46
48
|
*/
|
package/dist/id.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../src/id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,eAAO,MAAM,IAAI,QAAoB,CAAC;AAGtC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAcpC,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,SAAK;CAIzB;
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../src/id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,eAAO,MAAM,IAAI,QAAoB,CAAC;AAGtC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAcpC,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,SAAK;CAIzB;AAMD,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAiB7C,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAe7C,CAAC;AAkCF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,IAK7C,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,IAErD,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,aAAa,QAAO,IAIhC,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,QAA2C,CAAC;AAEzE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,GAAI,YAAW,MAAmB,KAAG,MAgB3D,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAAI,WAAW,MAAM,KAAG,MAS1D,CAAC;AASF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,OAA6B,CAAC;AAE3E;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,MAAM,KAAG,MACa,CAAC"}
|
package/dist/id.js
CHANGED
|
@@ -30,12 +30,6 @@ export class IdGenerationError extends Error {
|
|
|
30
30
|
this.name = 'IdGenerationError';
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
export class IdGenerationOverflowError extends IdGenerationError {
|
|
34
|
-
constructor(message = '') {
|
|
35
|
-
super(message || 'Too many IDs have been generated in the same millisecond.');
|
|
36
|
-
this.name = 'IdGenerationOverflowError';
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
33
|
// -------------------------------------------------------
|
|
40
34
|
// Encoding / decoding
|
|
41
35
|
// -------------------------------------------------------
|
|
@@ -88,12 +82,10 @@ const generateRandChars = () => {
|
|
|
88
82
|
} while (value > Math.floor(0xffffffff / modulus) * modulus);
|
|
89
83
|
return pad(crockford32Encode(value % modulus), RAND_SUFFIX_LENGTH);
|
|
90
84
|
};
|
|
85
|
+
/** The next suffix after `randChars`, or null once the space is spent. */
|
|
91
86
|
const incrementRandChars = (randChars) => {
|
|
92
87
|
const next = crockford32Encode(crockford32Decode(randChars) + 1);
|
|
93
|
-
|
|
94
|
-
throw new IdGenerationOverflowError();
|
|
95
|
-
}
|
|
96
|
-
return pad(next, RAND_SUFFIX_LENGTH);
|
|
88
|
+
return next.length > RAND_SUFFIX_LENGTH ? null : pad(next, RAND_SUFFIX_LENGTH);
|
|
97
89
|
};
|
|
98
90
|
// -------------------------------------------------------
|
|
99
91
|
// Test hooks (package-private)
|
|
@@ -136,15 +128,25 @@ export const MAX_ID_TIMESTAMP = Math.pow(BASE, MIN_TIMESTAMP_LENGTH) - 1;
|
|
|
136
128
|
/**
|
|
137
129
|
* Generate a new Stack record ID. Time-sortable: lexicographic order
|
|
138
130
|
* matches creation order, with same-millisecond IDs monotonically
|
|
139
|
-
* incremented.
|
|
140
|
-
*
|
|
131
|
+
* incremented.
|
|
132
|
+
*
|
|
133
|
+
* A millisecond whose suffix space is spent carries into the next one
|
|
134
|
+
* rather than failing, so how many IDs a millisecond holds never depends
|
|
135
|
+
* on where its random suffix happened to start — which is what keeps the
|
|
136
|
+
* order total and the call free of a capacity a caller cannot see.
|
|
137
|
+
* See docs/spec/data-model.md § Record IDs.
|
|
141
138
|
*
|
|
142
139
|
* @param timestamp - Override the timestamp (ms since epoch). Defaults to Date.now().
|
|
143
140
|
*/
|
|
144
141
|
export const generateId = (timestamp = Date.now()) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
let effectiveTimestamp = Math.max(timestamp, lastTimestamp);
|
|
143
|
+
let nowId = pad(crockford32Encode(effectiveTimestamp), MIN_TIMESTAMP_LENGTH);
|
|
144
|
+
let randChars = nowId === lastNowId ? incrementRandChars(lastRandChars) : generateRandChars();
|
|
145
|
+
if (randChars === null) {
|
|
146
|
+
effectiveTimestamp += 1;
|
|
147
|
+
nowId = pad(crockford32Encode(effectiveTimestamp), MIN_TIMESTAMP_LENGTH);
|
|
148
|
+
randChars = generateRandChars();
|
|
149
|
+
}
|
|
148
150
|
lastTimestamp = effectiveTimestamp;
|
|
149
151
|
lastNowId = nowId;
|
|
150
152
|
lastRandChars = randChars;
|
package/dist/id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.js","sourceRoot":"","sources":["../src/id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,UAAU,GAAG,kCAAkC,CAAC;AAEtD,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC;AAEtC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,kEAAkE;AAClE,IAAI,SAAS,GAAG,EAAE,CAAC;AACnB,IAAI,aAAa,GAAG,EAAE,CAAC;AACvB,wEAAwE;AACxE,uEAAuE;AACvE,uDAAuD;AACvD,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB,0DAA0D;AAC1D,SAAS;AACT,0DAA0D;AAE1D,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAO,GAAG,EAAE;QACtB,KAAK,CAAC,OAAO,IAAI,+BAA+B,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../src/id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DID bindings
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Which content fields are lookup keys rather than display values, and
|
|
5
|
+
* which of those a stack resolves *by*. Every one is immutable once set;
|
|
6
|
+
* the unique subset is additionally one-per-stack, because a lookup with
|
|
7
|
+
* two answers is all an impersonating card needs.
|
|
8
|
+
*
|
|
9
|
+
* See docs/spec/identity.md § DID bindings.
|
|
10
|
+
*/
|
|
11
|
+
export declare const bindingFieldsOf: (family: string) => readonly ("did" | "appId")[];
|
|
12
|
+
export declare const uniqueBindingFieldsOf: (family: string) => readonly ("did" | "appId")[];
|
|
13
|
+
//# sourceMappingURL=identity-bindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-bindings.d.ts","sourceRoot":"","sources":["../src/identity-bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkCH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,EACzC,CAAC;AAEnC,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,KAAG,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,EACxC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DID bindings
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Which content fields are lookup keys rather than display values, and
|
|
5
|
+
* which of those a stack resolves *by*. Every one is immutable once set;
|
|
6
|
+
* the unique subset is additionally one-per-stack, because a lookup with
|
|
7
|
+
* two answers is all an impersonating card needs.
|
|
8
|
+
*
|
|
9
|
+
* See docs/spec/identity.md § DID bindings.
|
|
10
|
+
*/
|
|
11
|
+
import { SYSTEM_TYPES } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Content fields that are lookup keys rather than display values: a card
|
|
14
|
+
* claims one, and something later resolves through it. Every one of them is
|
|
15
|
+
* immutable once set. See docs/spec/identity.md § DID bindings.
|
|
16
|
+
*/
|
|
17
|
+
const BINDING_FIELDS = new Map([
|
|
18
|
+
[SYSTEM_TYPES.APP, ['did', 'appId']],
|
|
19
|
+
[SYSTEM_TYPES.ENTITY, ['did']],
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* The subset that is additionally unique per stack: the fields something
|
|
23
|
+
* resolves *by*. A Record's `principalId` finds its card by `_app.did` and
|
|
24
|
+
* its `entityId` by `_entity.did`, so a second card claiming either leaves
|
|
25
|
+
* that lookup without a single answer — and ambiguity is all an
|
|
26
|
+
* impersonating card needs.
|
|
27
|
+
*
|
|
28
|
+
* `_app.appId` is deliberately absent. Nothing resolves a card by it — the
|
|
29
|
+
* cross-check reaches the card by `did` and only compares `appId` — so
|
|
30
|
+
* uniqueness would buy no disambiguation, while forbidding the second card
|
|
31
|
+
* key rotation is supposed to produce: `appId` is required, so a
|
|
32
|
+
* replacement card for the same software necessarily repeats it. Moving one
|
|
33
|
+
* card onto another's `appId` is what immutability already refuses.
|
|
34
|
+
* See docs/spec/identity.md § DID bindings.
|
|
35
|
+
*/
|
|
36
|
+
const UNIQUE_BINDING_FIELDS = new Map([
|
|
37
|
+
[SYSTEM_TYPES.APP, ['did']],
|
|
38
|
+
[SYSTEM_TYPES.ENTITY, ['did']],
|
|
39
|
+
]);
|
|
40
|
+
export const bindingFieldsOf = (family) => BINDING_FIELDS.get(family) ?? [];
|
|
41
|
+
export const uniqueBindingFieldsOf = (family) => UNIQUE_BINDING_FIELDS.get(family) ?? [];
|
|
42
|
+
//# sourceMappingURL=identity-bindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-bindings.js","sourceRoot":"","sources":["../src/identity-bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;GAIG;AACH,MAAM,cAAc,GAAsD,IAAI,GAAG,CAAC;IAChF,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,CAAU,CAAC;IAC7C,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,KAAK,CAAU,CAAC;CACxC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,qBAAqB,GAAsD,IAAI,GAAG,CAAC;IACvF,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,KAAK,CAAU,CAAC;IACpC,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,KAAK,CAAU,CAAC;CACxC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAgC,EAAE,CAC9E,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AAEnC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAAc,EAAgC,EAAE,CACpF,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC"}
|