@haverstack/core 0.42.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 +5 -7
- package/dist/access.d.ts +30 -32
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +40 -98
- package/dist/access.js.map +1 -1
- package/dist/adapter/combine.d.ts.map +1 -1
- package/dist/adapter/combine.js +11 -3
- package/dist/adapter/combine.js.map +1 -1
- package/dist/adapter/index.d.ts +6 -4
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -3
- package/dist/adapter/index.js.map +1 -1
- 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/associations/identity.d.ts +18 -42
- package/dist/associations/identity.d.ts.map +1 -1
- package/dist/associations/identity.js +60 -84
- package/dist/associations/identity.js.map +1 -1
- package/dist/associations/validation.d.ts +1 -8
- package/dist/associations/validation.d.ts.map +1 -1
- package/dist/associations/validation.js +3 -30
- package/dist/associations/validation.js.map +1 -1
- package/dist/collection.d.ts +7 -11
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +24 -34
- package/dist/collection.js.map +1 -1
- package/dist/content-match.d.ts +2 -2
- package/dist/content-match.js +2 -2
- package/dist/errors.d.ts +23 -8
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +28 -9
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +3 -3
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +4 -21
- package/dist/grants.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.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.js +1 -1
- package/dist/query-validation.d.ts +41 -28
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +85 -71
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +12 -36
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +9 -98
- package/dist/record-changes.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 +51 -38
- package/dist/scoped-stack/authority.d.ts.map +1 -1
- package/dist/scoped-stack/authority.js +160 -136
- package/dist/scoped-stack/authority.js.map +1 -1
- 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 +66 -86
- package/dist/scoped-stack/scoped-stack.d.ts.map +1 -1
- package/dist/scoped-stack/scoped-stack.js +243 -324
- package/dist/scoped-stack/scoped-stack.js.map +1 -1
- package/dist/scoped-stack/subscription.d.ts +14 -33
- package/dist/scoped-stack/subscription.d.ts.map +1 -1
- package/dist/scoped-stack/subscription.js +16 -57
- package/dist/scoped-stack/subscription.js.map +1 -1
- 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 +1 -1
- package/dist/stack/attachments.js +1 -1
- package/dist/stack/changes.d.ts +4 -10
- package/dist/stack/changes.d.ts.map +1 -1
- package/dist/stack/changes.js +8 -8
- package/dist/stack/changes.js.map +1 -1
- package/dist/stack/client.d.ts +34 -58
- package/dist/stack/client.d.ts.map +1 -1
- package/dist/stack/install-app.d.ts.map +1 -1
- package/dist/stack/install-app.js +7 -5
- package/dist/stack/install-app.js.map +1 -1
- package/dist/stack/integrity.js +2 -2
- package/dist/stack/integrity.js.map +1 -1
- package/dist/stack/limits.d.ts +1 -1
- package/dist/stack/limits.js +1 -1
- package/dist/stack/reads.d.ts +9 -5
- package/dist/stack/reads.d.ts.map +1 -1
- package/dist/stack/reads.js +24 -4
- package/dist/stack/reads.js.map +1 -1
- package/dist/stack/stack.d.ts +77 -71
- package/dist/stack/stack.d.ts.map +1 -1
- package/dist/stack/stack.js +229 -256
- package/dist/stack/stack.js.map +1 -1
- package/dist/stack/type-grants.d.ts.map +1 -1
- package/dist/stack/type-grants.js +4 -4
- package/dist/stack/type-grants.js.map +1 -1
- package/dist/stack/write-expectation.d.ts +13 -12
- package/dist/stack/write-expectation.d.ts.map +1 -1
- package/dist/stack/write-expectation.js.map +1 -1
- 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 +25 -9
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +147 -123
- package/dist/testing.js.map +1 -1
- package/dist/types/adapter.d.ts +90 -31
- package/dist/types/adapter.d.ts.map +1 -1
- package/dist/types/associations.d.ts +1 -17
- package/dist/types/associations.d.ts.map +1 -1
- package/dist/types/associations.js +1 -8
- package/dist/types/associations.js.map +1 -1
- package/dist/types/capabilities.d.ts +8 -8
- package/dist/types/capabilities.d.ts.map +1 -1
- package/dist/types/events.d.ts +3 -8
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/query.d.ts +4 -4
- package/dist/types/system.d.ts +1 -1
- package/dist/validate.d.ts +2 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +2 -1
- package/dist/validate.js.map +1 -1
- package/dist/wire/body.d.ts +32 -9
- package/dist/wire/body.d.ts.map +1 -1
- package/dist/wire/body.js +69 -8
- package/dist/wire/body.js.map +1 -1
- package/dist/wire/index.d.ts +4 -4
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +3 -3
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/record.d.ts +1 -8
- package/dist/wire/record.d.ts.map +1 -1
- package/dist/wire/record.js +0 -48
- package/dist/wire/record.js.map +1 -1
- package/dist/wire/request.d.ts +18 -18
- package/dist/wire/request.d.ts.map +1 -1
- package/dist/wire/request.js +41 -36
- package/dist/wire/request.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,14 +74,12 @@ await notes.patchContent(note.id, { title: 'Updated title' });
|
|
|
74
74
|
const same = await notes.get(note.id);
|
|
75
75
|
|
|
76
76
|
// Tag it
|
|
77
|
-
await notes.
|
|
77
|
+
await notes.editAssociations(note.id, [
|
|
78
|
+
{ op: 'add', association: { kind: 'tag', label: 'favourite' } },
|
|
79
|
+
]);
|
|
78
80
|
|
|
79
|
-
//
|
|
80
|
-
await notes.
|
|
81
|
-
contentPatch: { title: 'Final title' },
|
|
82
|
-
permissions: [{ kind: 'anyone', label: 'read' }],
|
|
83
|
-
unlisted: false,
|
|
84
|
-
});
|
|
81
|
+
// Share it with everyone — each aspect of a record has its own verb
|
|
82
|
+
await notes.setAccess(note.id, [{ kind: 'anyone', label: 'read' }]);
|
|
85
83
|
|
|
86
84
|
// Query the family. stack.query() reads across every type you can see instead.
|
|
87
85
|
const { records: favourites } = await notes.query({
|
package/dist/access.d.ts
CHANGED
|
@@ -1,47 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stack — Permission Resolution
|
|
3
3
|
* -------------------------------------------------------
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* and the grant path resolve against, and of the owner-acting-alone tier.
|
|
4
|
+
* The record-level permission rules, with no dependency on a transport
|
|
5
|
+
* layer or storage backend: how a grantee reaches an entity, the
|
|
6
|
+
* write-implies-read invariant, and the `_group` roster rules both layers
|
|
7
|
+
* resolve against, plus the owner-acting-alone tier. What a requester may
|
|
8
|
+
* do is a reach per verb, which scoped-stack/authority.ts builds from these.
|
|
10
9
|
*
|
|
11
10
|
* A permission set is read as data throughout, never as the type: it can
|
|
12
11
|
* arrive from an import or a foreign server, where no compiler has seen
|
|
13
12
|
* it. Every tier is affirmative, so an element read past its own kind —
|
|
14
13
|
* or carrying no kind at all — confers nothing.
|
|
15
14
|
*/
|
|
16
|
-
import type { AuthorityAssociation, DataAssociation, EntityId, GroupRole, RecordId, StackRecord } from './types/index.js';
|
|
15
|
+
import type { AuthorityAssociation, DataAssociation, EntityId, GroupRole, Grantee, RecordId, StackRecord } from './types/index.js';
|
|
17
16
|
import type { ValidationError } from './validate.js';
|
|
18
|
-
|
|
17
|
+
/** Resolves a Record by ID, for walks that need no full adapter. */
|
|
18
|
+
export type RecordResolver = (id: RecordId) => Promise<StackRecord | null>;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* The roles one entity holds on live `_group` rosters, keyed by group id,
|
|
21
|
+
* loaded on first use and at most once per operation, so no resolved role
|
|
22
|
+
* outlives the operation that read it — removal from a group must never go
|
|
23
|
+
* stale. See loadRosterRoles().
|
|
22
24
|
*/
|
|
23
|
-
export type
|
|
25
|
+
export type RosterRoles = () => Promise<ReadonlyMap<RecordId, GroupRole>>;
|
|
26
|
+
/**
|
|
27
|
+
* The elements beside which a `write` naming `grantee` carries its bit: its
|
|
28
|
+
* own `read`, or an `anyone`, which reaches the grantee already. The one
|
|
29
|
+
* statement of write-implies-read — validation and every reach ask it.
|
|
30
|
+
* See docs/spec/access-control.md § Write implies read.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readCompanions(grantee: Grantee): AuthorityAssociation[];
|
|
24
33
|
/**
|
|
25
|
-
* Whether
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* principal. See docs/spec/access-control.md § Delegation: principal and subject.
|
|
34
|
+
* Whether a grantee reaches `entityId`, for both layers alike. Read as data,
|
|
35
|
+
* so an arm the write would refuse confers nothing; `member` is the wider
|
|
36
|
+
* role. See docs/spec/access-control.md § Refused at the write, and again at evaluation.
|
|
29
37
|
*/
|
|
30
|
-
export declare function
|
|
38
|
+
export declare function granteeReaches(grantee: unknown, entityId: EntityId, opts: {
|
|
39
|
+
roles: RosterRoles;
|
|
40
|
+
allowGroup?: boolean;
|
|
41
|
+
}): Promise<boolean>;
|
|
31
42
|
/**
|
|
32
|
-
* Rejects a permission set where some
|
|
33
|
-
*
|
|
43
|
+
* Rejects a permission set where some `write` stands without a
|
|
44
|
+
* readCompanions() element, which no reach would honor. A cross-element
|
|
34
45
|
* invariant, so it is asked of the set the write would *produce*, the same
|
|
35
46
|
* way hasGroupAdmin() reads its post-state below.
|
|
36
47
|
* See docs/spec/access-control.md § Write implies read.
|
|
37
48
|
*/
|
|
38
49
|
export declare function validatePermissions(permissions: AuthorityAssociation[] | undefined, path?: string): ValidationError[];
|
|
39
|
-
/**
|
|
40
|
-
* An entity's role on the `_group` roster `groupId` names. `memo`, when
|
|
41
|
-
* given, is built per operation, so no resolved role outlives the operation
|
|
42
|
-
* that resolved it — removal from a group must never go stale.
|
|
43
|
-
*/
|
|
44
|
-
export declare function resolveGroupRole(groupId: RecordId, entityId: EntityId, resolveRecord: RecordResolver, memo?: Map<string, GroupRole | null>): Promise<GroupRole | null>;
|
|
45
50
|
/** Whether a held role meets a grantee's: an admin is also a member. */
|
|
46
51
|
export declare function roleSatisfies(held: GroupRole | null, required: GroupRole): boolean;
|
|
47
52
|
/**
|
|
@@ -49,17 +54,10 @@ export declare function roleSatisfies(held: GroupRole | null, required: GroupRol
|
|
|
49
54
|
* whose roster rules apply.
|
|
50
55
|
*/
|
|
51
56
|
export declare function isGroupRecord(record: StackRecord): boolean;
|
|
52
|
-
/**
|
|
53
|
-
* Whether a Record is a roster anything may resolve against: a `_group`,
|
|
54
|
-
* or an app's own `member` links would turn records into ACLs, and not
|
|
55
|
-
* deleted, since deleting a Group withdraws it. Shared with the grant path
|
|
56
|
-
* so the two cannot disagree. See docs/spec/identity.md § Group.
|
|
57
|
-
*/
|
|
58
|
-
export declare function carriesRoster(record: StackRecord): boolean;
|
|
59
57
|
/**
|
|
60
58
|
* An entity's role on a `_group` roster; a matching `admin` wins regardless
|
|
61
59
|
* of order. Reads associations alone and cannot tell whose they are, so
|
|
62
|
-
* callers
|
|
60
|
+
* callers ask it of a live `_group` only — see loadRosterRoles().
|
|
63
61
|
*/
|
|
64
62
|
export declare function groupRoleFromAssociations(associations: DataAssociation[] | undefined, entityId: EntityId): GroupRole | null;
|
|
65
63
|
/**
|
package/dist/access.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAEV,oBAAoB,EACpB,eAAe,EACf,QAAQ,EAER,SAAS,EACT,OAAO,EACP,QAAQ,EAER,WAAW,EACZ,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,oEAAoE;AACpE,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,oBAAoB,EAAE,CAKvE;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD,OAAO,CAAC,OAAO,CAAC,CAelB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,oBAAoB,EAAE,GAAG,SAAS,EAC/C,IAAI,SAAgB,GACnB,eAAe,EAAE,CAcnB;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,EAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,CAElF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,eAAe,EAAE,GAAG,SAAS,EAC3C,QAAQ,EAAE,QAAQ,GACjB,SAAS,GAAG,IAAI,CASlB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,SAAS,GAAG,OAAO,CAElF;AAkBD,iEAAiE;AACjE,wBAAgB,eAAe,CAC7B,YAAY,EAAE,eAAe,EAAE,GAAG,SAAS,EAC3C,OAAO,EAAE,QAAQ,GAChB,eAAe,EAAE,CAUnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE;IAAE,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,EACpE,aAAa,EAAE,QAAQ,GACtB,OAAO,CAGT"}
|
package/dist/access.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stack — Permission Resolution
|
|
3
3
|
* -------------------------------------------------------
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* and the grant path resolve against, and of the owner-acting-alone tier.
|
|
4
|
+
* The record-level permission rules, with no dependency on a transport
|
|
5
|
+
* layer or storage backend: how a grantee reaches an entity, the
|
|
6
|
+
* write-implies-read invariant, and the `_group` roster rules both layers
|
|
7
|
+
* resolve against, plus the owner-acting-alone tier. What a requester may
|
|
8
|
+
* do is a reach per verb, which scoped-stack/authority.ts builds from these.
|
|
10
9
|
*
|
|
11
10
|
* A permission set is read as data throughout, never as the type: it can
|
|
12
11
|
* arrive from an import or a foreign server, where no compiler has seen
|
|
@@ -14,115 +13,67 @@
|
|
|
14
13
|
* or carrying no kind at all — confers nothing.
|
|
15
14
|
*/
|
|
16
15
|
import { SYSTEM_TYPES } from './types/index.js';
|
|
17
|
-
import {
|
|
16
|
+
import { associationKey } from './associations/identity.js';
|
|
18
17
|
import { baseIdOf } from './schema.js';
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* principal. See docs/spec/access-control.md § Delegation: principal and subject.
|
|
24
|
-
*/
|
|
25
|
-
export async function checkAccess(record, subjectEntityId, ownerEntityId, mode, resolveRecord) {
|
|
26
|
-
if (subjectEntityId && subjectEntityId === ownerEntityId)
|
|
27
|
-
return true;
|
|
28
|
-
const perms = record.permissions;
|
|
29
|
-
// No permissions = private.
|
|
30
|
-
if (!perms || perms.length === 0)
|
|
31
|
-
return false;
|
|
32
|
-
// Two elements can name one group (a `member` and an `admin` read), and
|
|
33
|
-
// the roster answers both the same way within one check.
|
|
34
|
-
const roles = new Map();
|
|
35
|
-
for (const p of perms) {
|
|
36
|
-
if (p?.kind === 'anyone') {
|
|
37
|
-
if (mode === 'read' && isWorldRead(p))
|
|
38
|
-
return true;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (p?.kind !== 'permission' || !p.grantee)
|
|
42
|
-
continue;
|
|
43
|
-
if (p.label !== mode)
|
|
44
|
-
continue;
|
|
45
|
-
// The write bit is inert without read alongside it: the mutate surface
|
|
46
|
-
// hands back the record it wrote and opens its whole history, so a
|
|
47
|
-
// write element standing alone would withhold nothing. Refused at the
|
|
48
|
-
// write by validatePermissions() and again here.
|
|
49
|
-
// See docs/spec/access-control.md § Write implies read.
|
|
50
|
-
if (mode === 'write' && !holdsRead(perms, p.grantee))
|
|
51
|
-
continue;
|
|
52
|
-
if (await granteeCovers(p.grantee, subjectEntityId, resolveRecord, roles))
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Whether the set conveys read to this grantee — a `read` of its own, or
|
|
59
|
-
* an `anyone` that already reaches everyone. Demanding a second, redundant
|
|
60
|
-
* `read` beside the write would refuse a set that withholds nothing;
|
|
61
|
-
* revoking the `anyone` is what the invariant catches instead.
|
|
19
|
+
* The elements beside which a `write` naming `grantee` carries its bit: its
|
|
20
|
+
* own `read`, or an `anyone`, which reaches the grantee already. The one
|
|
21
|
+
* statement of write-implies-read — validation and every reach ask it.
|
|
62
22
|
* See docs/spec/access-control.md § Write implies read.
|
|
63
23
|
*/
|
|
64
|
-
function
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Whether an `anyone` element reaches the world. `read` is the only bit it
|
|
71
|
-
* can carry, so one labelled otherwise names no reach — refused at the
|
|
72
|
-
* write by validateAssociation() and read as nothing here.
|
|
73
|
-
*/
|
|
74
|
-
function isWorldRead(association) {
|
|
75
|
-
return association.label === 'read';
|
|
24
|
+
export function readCompanions(grantee) {
|
|
25
|
+
return [
|
|
26
|
+
{ kind: 'permission', label: 'read', grantee },
|
|
27
|
+
{ kind: 'anyone', label: 'read' },
|
|
28
|
+
];
|
|
76
29
|
}
|
|
77
30
|
/**
|
|
78
|
-
* Whether a grantee reaches
|
|
79
|
-
*
|
|
31
|
+
* Whether a grantee reaches `entityId`, for both layers alike. Read as data,
|
|
32
|
+
* so an arm the write would refuse confers nothing; `member` is the wider
|
|
33
|
+
* role. See docs/spec/access-control.md § Refused at the write, and again at evaluation.
|
|
80
34
|
*/
|
|
81
|
-
async function
|
|
82
|
-
if (!
|
|
35
|
+
export async function granteeReaches(grantee, entityId, opts) {
|
|
36
|
+
if (!grantee || typeof grantee !== 'object')
|
|
83
37
|
return false;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
38
|
+
const g = grantee;
|
|
39
|
+
switch (g.kind) {
|
|
40
|
+
case 'entity':
|
|
41
|
+
return !!g.entityId && g.entityId === entityId;
|
|
42
|
+
case 'group': {
|
|
43
|
+
if (opts.allowGroup === false || !g.groupId)
|
|
44
|
+
return false;
|
|
45
|
+
if (g.role !== 'member' && g.role !== 'admin')
|
|
46
|
+
return false;
|
|
47
|
+
const held = (await opts.roles()).get(g.groupId) ?? null;
|
|
48
|
+
return roleSatisfies(held, g.role);
|
|
49
|
+
}
|
|
50
|
+
default:
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
88
53
|
}
|
|
89
54
|
/**
|
|
90
|
-
* Rejects a permission set where some
|
|
91
|
-
*
|
|
55
|
+
* Rejects a permission set where some `write` stands without a
|
|
56
|
+
* readCompanions() element, which no reach would honor. A cross-element
|
|
92
57
|
* invariant, so it is asked of the set the write would *produce*, the same
|
|
93
58
|
* way hasGroupAdmin() reads its post-state below.
|
|
94
59
|
* See docs/spec/access-control.md § Write implies read.
|
|
95
60
|
*/
|
|
96
61
|
export function validatePermissions(permissions, path = 'permissions') {
|
|
97
62
|
const list = permissions ?? [];
|
|
63
|
+
const held = new Set(list.map(associationKey));
|
|
98
64
|
const errors = [];
|
|
99
65
|
list.forEach((p, i) => {
|
|
100
66
|
if (p?.kind !== 'permission' || p.label !== 'write')
|
|
101
67
|
return;
|
|
102
|
-
if (
|
|
68
|
+
if (readCompanions(p.grantee).some((c) => held.has(associationKey(c))))
|
|
103
69
|
return;
|
|
104
70
|
errors.push({
|
|
105
71
|
path: `${path}[${i}]`,
|
|
106
|
-
message: 'write requires read: a write-holder reaches the record and its history through the mutate surface, so a `write` element with no `read` for the same grantee withholds nothing. Grant `read` and `write` together
|
|
72
|
+
message: 'write requires read: a write-holder reaches the record and its history through the mutate surface, so a `write` element with no `read` for the same grantee withholds nothing. Grant `read` and `write` together in one editAccess() or setAccess() call',
|
|
107
73
|
});
|
|
108
74
|
});
|
|
109
75
|
return errors;
|
|
110
76
|
}
|
|
111
|
-
/**
|
|
112
|
-
* An entity's role on the `_group` roster `groupId` names. `memo`, when
|
|
113
|
-
* given, is built per operation, so no resolved role outlives the operation
|
|
114
|
-
* that resolved it — removal from a group must never go stale.
|
|
115
|
-
*/
|
|
116
|
-
export async function resolveGroupRole(groupId, entityId, resolveRecord, memo) {
|
|
117
|
-
const key = `${groupId}:${entityId}`;
|
|
118
|
-
const cached = memo?.get(key);
|
|
119
|
-
if (cached !== undefined)
|
|
120
|
-
return cached;
|
|
121
|
-
const group = await resolveRecord(groupId);
|
|
122
|
-
const role = group && carriesRoster(group) ? groupRoleFromAssociations(group.associations, entityId) : null;
|
|
123
|
-
memo?.set(key, role);
|
|
124
|
-
return role;
|
|
125
|
-
}
|
|
126
77
|
/** Whether a held role meets a grantee's: an admin is also a member. */
|
|
127
78
|
export function roleSatisfies(held, required) {
|
|
128
79
|
return required === 'admin' ? held === 'admin' : held !== null;
|
|
@@ -134,19 +85,10 @@ export function roleSatisfies(held, required) {
|
|
|
134
85
|
export function isGroupRecord(record) {
|
|
135
86
|
return baseIdOf(record.typeId) === SYSTEM_TYPES.GROUP;
|
|
136
87
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Whether a Record is a roster anything may resolve against: a `_group`,
|
|
139
|
-
* or an app's own `member` links would turn records into ACLs, and not
|
|
140
|
-
* deleted, since deleting a Group withdraws it. Shared with the grant path
|
|
141
|
-
* so the two cannot disagree. See docs/spec/identity.md § Group.
|
|
142
|
-
*/
|
|
143
|
-
export function carriesRoster(record) {
|
|
144
|
-
return isGroupRecord(record) && !record.deletedAt;
|
|
145
|
-
}
|
|
146
88
|
/**
|
|
147
89
|
* An entity's role on a `_group` roster; a matching `admin` wins regardless
|
|
148
90
|
* of order. Reads associations alone and cannot tell whose they are, so
|
|
149
|
-
* callers
|
|
91
|
+
* callers ask it of a live `_group` only — see loadRosterRoles().
|
|
150
92
|
*/
|
|
151
93
|
export function groupRoleFromAssociations(associations, entityId) {
|
|
152
94
|
let role = null;
|
package/dist/access.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combine.d.ts","sourceRoot":"","sources":["../../src/adapter/combine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"combine.d.ts","sourceRoot":"","sources":["../../src/adapter/combine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAGV,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAGjB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,YAAY,CA8Ef"}
|
package/dist/adapter/combine.js
CHANGED
|
@@ -25,11 +25,16 @@ export function combineAdapters(parts) {
|
|
|
25
25
|
},
|
|
26
26
|
createRecord: (r, opts) => parts.record.createRecord(r, opts),
|
|
27
27
|
getRecord: (id) => parts.record.getRecord(id),
|
|
28
|
-
|
|
28
|
+
patchContent: (id, patch, opts) => parts.record.patchContent(id, patch, opts),
|
|
29
29
|
deleteRecord: (id, opts) => parts.record.deleteRecord(id, opts),
|
|
30
30
|
undeleteRecord: (id, opts) => parts.record.undeleteRecord(id, opts),
|
|
31
31
|
queryRecords: (q) => parts.record.queryRecords(q),
|
|
32
|
-
|
|
32
|
+
setParent: (id, parentId, opts) => parts.record.setParent(id, parentId, opts),
|
|
33
|
+
setUnlisted: (id, unlisted, opts) => parts.record.setUnlisted(id, unlisted, opts),
|
|
34
|
+
editAssociations: (id, edits, opts) => parts.record.editAssociations(id, edits, opts),
|
|
35
|
+
setAssociations: (id, list, opts) => parts.record.setAssociations(id, list, opts),
|
|
36
|
+
editAccess: (id, edits, opts) => parts.record.editAccess(id, edits, opts),
|
|
37
|
+
setAccess: (id, list, opts) => parts.record.setAccess(id, list, opts),
|
|
33
38
|
getVersions: (id, query) => parts.record.getVersions(id, query),
|
|
34
39
|
getVersion: (id, v) => parts.record.getVersion(id, v),
|
|
35
40
|
saveVersion: (id, v) => parts.record.saveVersion(id, v),
|
|
@@ -43,7 +48,10 @@ export function combineAdapters(parts) {
|
|
|
43
48
|
// absent on the combined adapter, rather than silently becoming
|
|
44
49
|
// "supported" via a wrapper that forwards to a missing method.
|
|
45
50
|
...(parts.record.deleteUnreferencedAttachmentRecords && {
|
|
46
|
-
deleteUnreferencedAttachmentRecords: (fileId,
|
|
51
|
+
deleteUnreferencedAttachmentRecords: (fileId, metadataBaseId) => parts.record.deleteUnreferencedAttachmentRecords(fileId, metadataBaseId),
|
|
52
|
+
}),
|
|
53
|
+
...(parts.record.queryReadable && {
|
|
54
|
+
queryReadable: (q, reach) => parts.record.queryReadable(q, reach),
|
|
47
55
|
}),
|
|
48
56
|
// Forwarded on the same terms, and it decides more than it looks:
|
|
49
57
|
// Stack.relaysChanges reads its presence, so dropping it here would
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combine.js","sourceRoot":"","sources":["../../src/adapter/combine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"combine.js","sourceRoot":"","sources":["../../src/adapter/combine.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter/index.d.ts
CHANGED
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* adapters). Types an app reads too, such as StackCapabilities, live in
|
|
8
8
|
* the root entry point instead: every export has exactly one home.
|
|
9
9
|
*/
|
|
10
|
-
export type { StackAdapter, StackRecordAdapter, StackBlobAdapter, BlobInfo,
|
|
11
|
-
export {
|
|
10
|
+
export type { AdapterQuery, PlainQuery, AdapterSort, StackAdapter, StackRecordAdapter, StackBlobAdapter, BlobInfo, SnapshotOptions, JournalEntryInput, JournalOptions, MutateOptions, AspectWriteOptions, ElementWriteOptions, Reach, } from '../types/index.js';
|
|
11
|
+
export { sameAssociationSet } from '../associations/identity.js';
|
|
12
|
+
export { ElementSetMovedError, OwnerMismatchError, softDeletedRefusal } from '../errors.js';
|
|
12
13
|
export { combineAdapters } from './combine.js';
|
|
13
|
-
export {
|
|
14
|
+
export { inReach } from './reach.js';
|
|
15
|
+
export type { ReachedRecord } from './reach.js';
|
|
16
|
+
export { assertQueryCapabilities, assertValidSort, resolveQuery, assertValidAssociationFilters, assertValidJournalQuery, assertValidVersionsQuery, filtersContent, parseContentFilterKey, } from '../query-validation.js';
|
|
14
17
|
export { contentSortEntry, contentSortKey, compareSortEntries, constantSortEntry, sortConstant, } from './sort.js';
|
|
15
18
|
export type { SortEntry } from './sort.js';
|
|
16
|
-
export { AUTHORITY_ASSOCIATION_KINDS, partitionAssociations } from '../associations/identity.js';
|
|
17
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,GACN,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,6BAA6B,EAC7B,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/adapter/index.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* adapters). Types an app reads too, such as StackCapabilities, live in
|
|
8
8
|
* the root entry point instead: every export has exactly one home.
|
|
9
9
|
*/
|
|
10
|
-
export {
|
|
10
|
+
export { sameAssociationSet } from '../associations/identity.js';
|
|
11
|
+
export { ElementSetMovedError, OwnerMismatchError, softDeletedRefusal } from '../errors.js';
|
|
11
12
|
export { combineAdapters } from './combine.js';
|
|
12
|
-
export {
|
|
13
|
+
export { inReach } from './reach.js';
|
|
14
|
+
export { assertQueryCapabilities, assertValidSort, resolveQuery, assertValidAssociationFilters, assertValidJournalQuery, assertValidVersionsQuery, filtersContent, parseContentFilterKey, } from '../query-validation.js';
|
|
13
15
|
export { contentSortEntry, contentSortKey, compareSortEntries, constantSortEntry, sortConstant, } from './sort.js';
|
|
14
|
-
export { AUTHORITY_ASSOCIATION_KINDS, partitionAssociations } from '../associations/identity.js';
|
|
15
16
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkBH,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,6BAA6B,EAC7B,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,GACb,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matching a Reach in memory
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The one reading of a reach in JavaScript: MemoryAdapter matches a query
|
|
5
|
+
* with it, and ScopedStack asks it of each Record a verb would act on. A
|
|
6
|
+
* SQL adapter states the same conditions in its own query language. See
|
|
7
|
+
* docs/spec/access-control.md § Reach.
|
|
8
|
+
*/
|
|
9
|
+
import type { Reach, StackRecord } from '../types/index.js';
|
|
10
|
+
/** What a reach asks of a Record — all a record yet to be created has to show. */
|
|
11
|
+
export type ReachedRecord = Pick<StackRecord, 'typeId' | 'createdBy' | 'permissions'>;
|
|
12
|
+
/** Whether `record` is inside the reach, matched by its family. */
|
|
13
|
+
export declare function inReach(record: ReachedRecord, reach: Reach): boolean;
|
|
14
|
+
//# sourceMappingURL=reach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../../src/adapter/reach.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE5D,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC,CAAC;AAEtF,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAmBpE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matching a Reach in memory
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* The one reading of a reach in JavaScript: MemoryAdapter matches a query
|
|
5
|
+
* with it, and ScopedStack asks it of each Record a verb would act on. A
|
|
6
|
+
* SQL adapter states the same conditions in its own query language. See
|
|
7
|
+
* docs/spec/access-control.md § Reach.
|
|
8
|
+
*/
|
|
9
|
+
import { associationKey } from '../associations/identity.js';
|
|
10
|
+
import { baseIdOf } from '../schema.js';
|
|
11
|
+
/** Whether `record` is inside the reach, matched by its family. */
|
|
12
|
+
export function inReach(record, reach) {
|
|
13
|
+
const family = baseIdOf(record.typeId);
|
|
14
|
+
if (reach.fence && !reach.fence.includes(family))
|
|
15
|
+
return false;
|
|
16
|
+
const within = reach.within;
|
|
17
|
+
if (!within)
|
|
18
|
+
return true;
|
|
19
|
+
if (within.baseIds.includes(family))
|
|
20
|
+
return true;
|
|
21
|
+
const authored = within.authored;
|
|
22
|
+
if (authored &&
|
|
23
|
+
authored.baseIds.includes(family) &&
|
|
24
|
+
record.createdBy?.subjectId === authored.subjectId) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const held = new Set((record.permissions ?? []).map(associationKey));
|
|
28
|
+
held.delete(undefined);
|
|
29
|
+
return within.permissions.some((set) => set.length > 0 && set.every((element) => held.has(associationKey(element))));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=reach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reach.js","sourceRoot":"","sources":["../../src/adapter/reach.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAMxC,mEAAmE;AACnE,MAAM,UAAU,OAAO,CAAC,MAAqB,EAAE,KAAY;IACzD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,IACE,QAAQ;QACR,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QACjC,MAAM,CAAC,SAAS,EAAE,SAAS,KAAK,QAAQ,CAAC,SAAS,EAClD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAC5B,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CACrF,CAAC;AACJ,CAAC"}
|
|
@@ -9,19 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Association, AssociationChange, AssociationEdit, AuthorityAssociation, DataAssociation, Grantee, RelationshipTarget } from '../types/index.js';
|
|
11
11
|
/**
|
|
12
|
-
* Association identity — what
|
|
12
|
+
* Association identity — what a remove edit matches and a repeat add
|
|
13
13
|
* collides with; the SQLite adapter's primary key. An attachment's
|
|
14
14
|
* `attachmentRecordId` annotates a reference rather than naming one, so it
|
|
15
15
|
* is outside; a group grantee's `role` names a different set of people, so
|
|
16
16
|
* it is inside. See docs/spec/data-model.md § Associations.
|
|
17
17
|
*/
|
|
18
|
+
export declare function associationKey(a: Association): string | undefined;
|
|
19
|
+
/** Whether two associations are the same one — see associationKey(). */
|
|
18
20
|
export declare function associationEqual(a: Association, b: Association): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Structural equality per grantee arm. `role` is required, so there is no
|
|
21
|
-
* absent-means-any spelling to normalize.
|
|
22
|
-
*/
|
|
23
21
|
export declare function granteeEqual(a: Grantee, b: Grantee): boolean;
|
|
24
|
-
/** Structural equality per target arm — what
|
|
22
|
+
/** Structural equality per target arm — what a remove edit matches on. */
|
|
25
23
|
export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget): boolean;
|
|
26
24
|
/**
|
|
27
25
|
* Identity plus every annotation outside it, so a write that only re-points
|
|
@@ -30,35 +28,26 @@ export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget
|
|
|
30
28
|
* this decides whether it says anything new.
|
|
31
29
|
*/
|
|
32
30
|
export declare function associationIdentical(a: Association, b: Association): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* The association kinds that carry authority. Every other kind is data.
|
|
35
|
-
* Storage that partitions by kind in a query reads this list rather than
|
|
36
|
-
* restating it, so a new authority kind can't fall into the data half.
|
|
37
|
-
*/
|
|
38
|
-
export declare const AUTHORITY_ASSOCIATION_KINDS: readonly AuthorityAssociation['kind'][];
|
|
39
31
|
/**
|
|
40
32
|
* Whether an association carries authority rather than data — the partition
|
|
41
33
|
* `StackRecord.permissions` and `StackRecord.associations` project, and the
|
|
42
|
-
* line
|
|
34
|
+
* line the association verbs refuse to cross.
|
|
43
35
|
* See docs/spec/access-control.md § Record-level permissions.
|
|
44
36
|
*/
|
|
45
37
|
export declare function isAuthorityAssociation(a: Association): a is AuthorityAssociation;
|
|
46
38
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
39
|
+
* What replacing a record's set with `after` moves, one tagged edit each.
|
|
40
|
+
* An entry matching an existing identity displaced it: a `repoint`,
|
|
41
|
+
* carrying the prior association in full as `previous` — the only place
|
|
42
|
+
* an overwritten `attachmentRecordId` survives.
|
|
43
|
+
* See docs/spec/journal.md § The entry.
|
|
49
44
|
*/
|
|
50
|
-
export declare function
|
|
51
|
-
associations: DataAssociation[];
|
|
52
|
-
permissions: AuthorityAssociation[];
|
|
53
|
-
};
|
|
45
|
+
export declare function associationDelta(before: readonly Association[], after: readonly Association[]): AssociationChange[];
|
|
54
46
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* a `repoint`, carrying the prior association in full as `previous` — the
|
|
58
|
-
* only place an overwritten `attachmentRecordId` survives.
|
|
59
|
-
* See docs/spec/journal.md § The entry.
|
|
47
|
+
* Whether two sets hold the same elements, annotations included, in any
|
|
48
|
+
* order — what an element-set write's `expectSet` is compared by.
|
|
60
49
|
*/
|
|
61
|
-
export declare function
|
|
50
|
+
export declare function sameAssociationSet(a: readonly Association[], b: readonly Association[]): boolean;
|
|
62
51
|
/**
|
|
63
52
|
* The two flat lists a change frame carries, derived from the tagged list.
|
|
64
53
|
* The feed reports what is true now, so a `repoint` appears only under its
|
|
@@ -70,24 +59,11 @@ export declare function feedAssociationDelta(changes: AssociationChange[]): {
|
|
|
70
59
|
added: DataAssociation[];
|
|
71
60
|
removed: DataAssociation[];
|
|
72
61
|
};
|
|
62
|
+
/** The elements an edit list adds. */
|
|
63
|
+
export declare function addsOf(edits: readonly AssociationEdit[]): Association[];
|
|
73
64
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* adapter inherits the same rule.
|
|
77
|
-
* See docs/spec/access-control.md § Storage unifies; the API does not.
|
|
78
|
-
*/
|
|
79
|
-
export declare function assertOneSurface(changes: readonly AssociationEdit[]): void;
|
|
80
|
-
/**
|
|
81
|
-
* The set a list of edits leaves behind: removes first, then adds, an add
|
|
82
|
-
* landing on an identity already held in place. Adds naming one identity
|
|
83
|
-
* collapse, last wins; an add outlives a remove of the same identity.
|
|
84
|
-
* See docs/spec/adapters.md § Amending associations.
|
|
65
|
+
* The set a list of edits leaves behind, an add landing on an identity
|
|
66
|
+
* already held in place. See docs/spec/adapters.md § Editing associations.
|
|
85
67
|
*/
|
|
86
68
|
export declare function applyAssociationEdits(current: Association[], changes: readonly AssociationEdit[]): Association[];
|
|
87
|
-
/**
|
|
88
|
-
* The same op over every element of a list — what associate(),
|
|
89
|
-
* dissociate(), grantAccess() and revokeAccess() hand to the amend verb
|
|
90
|
-
* each is one half of.
|
|
91
|
-
*/
|
|
92
|
-
export declare function editsOf(op: 'add' | 'remove', associations: Association[]): AssociationEdit[];
|
|
93
69
|
//# sourceMappingURL=identity.d.ts.map
|