@haverstack/core 0.40.0 → 0.42.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 +121 -59
- package/dist/access.d.ts +34 -36
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +65 -42
- 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} +7 -0
- package/dist/adapter/combine.js.map +1 -0
- package/dist/{adapter-entry.d.ts → adapter/index.d.ts} +6 -5
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/{adapter-entry.js → adapter/index.js} +5 -4
- package/dist/adapter/index.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 +93 -0
- package/dist/associations/identity.d.ts.map +1 -0
- package/dist/associations/identity.js +198 -0
- package/dist/associations/identity.js.map +1 -0
- package/dist/associations/validation.d.ts +90 -0
- package/dist/associations/validation.d.ts.map +1 -0
- package/dist/associations/validation.js +339 -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 +182 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +407 -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 +36 -63
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +35 -58
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +20 -43
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +71 -134
- 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 +14 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +141 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +219 -0
- package/dist/install.js.map +1 -0
- package/dist/merge.d.ts.map +1 -1
- package/dist/merge.js +5 -11
- 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 +28 -94
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +156 -336
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +2 -117
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +10 -212
- 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.d.ts +156 -0
- package/dist/scoped-stack/authority.d.ts.map +1 -0
- package/dist/scoped-stack/authority.js +384 -0
- package/dist/scoped-stack/authority.js.map +1 -0
- package/dist/scoped-stack/scoped-stack.d.ts +338 -0
- package/dist/scoped-stack/scoped-stack.d.ts.map +1 -0
- package/dist/scoped-stack/scoped-stack.js +776 -0
- package/dist/scoped-stack/scoped-stack.js.map +1 -0
- package/dist/scoped-stack/subscription.d.ts +55 -0
- package/dist/scoped-stack/subscription.d.ts.map +1 -0
- package/dist/scoped-stack/subscription.js +110 -0
- package/dist/scoped-stack/subscription.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} +29 -65
- package/dist/stack/changes.d.ts.map +1 -0
- package/dist/{changes.js → stack/changes.js} +32 -66
- package/dist/stack/changes.js.map +1 -0
- package/dist/stack/client.d.ts +271 -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} +8 -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 +260 -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 → stack/reads.d.ts} +33 -6
- package/dist/stack/reads.d.ts.map +1 -0
- package/dist/{stack-reads.js → stack/reads.js} +49 -7
- 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 +438 -0
- package/dist/stack/stack.d.ts.map +1 -0
- package/dist/stack/stack.js +1366 -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 +78 -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/testing.d.ts +21 -26
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +50 -70
- 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 +223 -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 +156 -0
- package/dist/types/associations.d.ts.map +1 -0
- package/dist/types/associations.js +13 -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 +155 -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 +29 -70
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +65 -97
- 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} +13 -5
- package/dist/wire/body.d.ts.map +1 -0
- package/dist/{wire-body.js → wire/body.js} +65 -7
- 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 → wire/record.d.ts} +10 -22
- package/dist/wire/record.d.ts.map +1 -0
- package/dist/{wire-record.js → wire/record.js} +12 -24
- package/dist/wire/record.js.map +1 -0
- package/dist/{wire-request.d.ts → wire/request.d.ts} +20 -37
- package/dist/wire/request.d.ts.map +1 -0
- package/dist/{wire-request.js → wire/request.js} +79 -54
- package/dist/wire/request.js.map +1 -0
- package/package.json +7 -7
- package/dist/adapter-entry.d.ts.map +0 -1
- 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 -532
- package/dist/scoped-stack.d.ts.map +0 -1
- package/dist/scoped-stack.js +0 -1219
- 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.map +0 -1
- package/dist/stack-reads.js.map +0 -1
- package/dist/stack.d.ts +0 -971
- package/dist/stack.d.ts.map +0 -1
- package/dist/stack.js +0 -2012
- package/dist/stack.js.map +0 -1
- package/dist/types.d.ts +0 -1250
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -49
- 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.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/README.md
CHANGED
|
@@ -46,40 +46,46 @@ const adapter = await LocalAdapter.open({
|
|
|
46
46
|
// safe to keep passing on every open, it's a no-op once the record exists.
|
|
47
47
|
const stack = await Stack.open(adapter, { ownerProfile: { name: 'Jane Smith' } });
|
|
48
48
|
|
|
49
|
-
// Define a type. The handle carries the id and schema, and the compiler
|
|
49
|
+
// Define a type. The handle carries the id, name and schema, and the compiler
|
|
50
50
|
// derives the content type from it — no separate interface to keep in step.
|
|
51
|
-
const Note = typeHandle(
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const Note = typeHandle({
|
|
52
|
+
id: 'com.example.myapp/note@1',
|
|
53
|
+
name: 'Note',
|
|
54
|
+
schema: {
|
|
55
|
+
text: { kind: 'text', required: true },
|
|
56
|
+
title: { kind: 'string' },
|
|
57
|
+
},
|
|
54
58
|
});
|
|
55
|
-
await stack.defineType(
|
|
59
|
+
await stack.defineType(Note);
|
|
60
|
+
|
|
61
|
+
// Typed access to the Note family
|
|
62
|
+
const notes = stack.collection(Note);
|
|
56
63
|
|
|
57
64
|
// Create a record
|
|
58
|
-
const note = await
|
|
65
|
+
const note = await notes.create({
|
|
59
66
|
text: 'Hello, Haverstack!',
|
|
60
67
|
title: 'My first note',
|
|
61
68
|
});
|
|
62
69
|
|
|
63
70
|
// Update its content (partial merge — only changed fields needed)
|
|
64
|
-
await
|
|
71
|
+
await notes.patchContent(note.id, { title: 'Updated title' });
|
|
65
72
|
|
|
66
73
|
// Read it back, typed: `content.text` is a string
|
|
67
|
-
const same = await
|
|
74
|
+
const same = await notes.get(note.id);
|
|
68
75
|
|
|
69
76
|
// Tag it
|
|
70
|
-
await
|
|
77
|
+
await notes.associate(note.id, [{ kind: 'tag', label: 'favourite' }]);
|
|
71
78
|
|
|
72
79
|
// Or change several things at once — one version, one atomic write
|
|
73
|
-
await
|
|
80
|
+
await notes.mutate(note.id, {
|
|
74
81
|
contentPatch: { title: 'Final title' },
|
|
75
82
|
permissions: [{ kind: 'anyone', label: 'read' }],
|
|
76
83
|
unlisted: false,
|
|
77
84
|
});
|
|
78
85
|
|
|
79
|
-
// Query
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
filter: { typeId: 'com.example.myapp/note@1', tags: ['favourite'] },
|
|
86
|
+
// Query the family. stack.query() reads across every type you can see instead.
|
|
87
|
+
const { records: favourites } = await notes.query({
|
|
88
|
+
filter: { tags: ['favourite'] },
|
|
83
89
|
sort: { field: 'createdAt', direction: 'desc' },
|
|
84
90
|
});
|
|
85
91
|
|
|
@@ -92,60 +98,117 @@ await stack.close();
|
|
|
92
98
|
An app has two layers, because the stack draws a line between them:
|
|
93
99
|
|
|
94
100
|
- **A data layer that takes a `StackClient`** — the record API `Stack` and `ScopedStack` both implement. The same code then runs embedded as the owner, or behind a server as a requester who reaches only what they were granted.
|
|
95
|
-
- **An install
|
|
101
|
+
- **An install that the owner approves.** Defining types, `migrateAll()` and `grantType()` change the whole stack rather than one record, so they live on `Stack` alone and are absent from `StackClient`.
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
Migrations belong to neither. They are passed to `Stack.open()` on **every startup**, so the chain is complete before the first read.
|
|
98
104
|
|
|
99
105
|
```ts
|
|
100
|
-
import { Stack,
|
|
106
|
+
import { Stack, map, migration, typeHandle } from '@haverstack/core';
|
|
107
|
+
import type { AppManifest, Collection, ContentOf, StackClient } from '@haverstack/core';
|
|
101
108
|
|
|
102
|
-
const
|
|
103
|
-
|
|
109
|
+
export const Shelf = typeHandle({
|
|
110
|
+
id: 'com.example.reading/shelf@1',
|
|
111
|
+
name: 'Shelf',
|
|
112
|
+
schema: { name: { kind: 'string', required: true } },
|
|
104
113
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
const BookV1 = typeHandle({
|
|
115
|
+
id: 'com.example.reading/book@1',
|
|
116
|
+
name: 'Book',
|
|
117
|
+
schema: {
|
|
118
|
+
title: { kind: 'string', required: true },
|
|
119
|
+
status: { kind: 'enum', values: ['reading', 'done'], required: true },
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
export const Book = typeHandle({
|
|
123
|
+
id: 'com.example.reading/book@2',
|
|
124
|
+
name: 'Book',
|
|
125
|
+
migratesFrom: BookV1,
|
|
126
|
+
schema: {
|
|
127
|
+
title: { kind: 'string', required: true },
|
|
128
|
+
status: { kind: 'enum', values: ['reading', 'finished'], required: true },
|
|
129
|
+
shelfId: { kind: 'record-ref' },
|
|
130
|
+
},
|
|
108
131
|
});
|
|
132
|
+
export const Review = typeHandle({
|
|
133
|
+
id: 'com.example.reading/review@1',
|
|
134
|
+
name: 'Review',
|
|
135
|
+
schema: {
|
|
136
|
+
bookId: { kind: 'record-ref', required: true },
|
|
137
|
+
rating: { kind: 'number', required: true },
|
|
138
|
+
text: { kind: 'text' },
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export interface ShelfContent extends ContentOf<typeof Shelf.schema> {}
|
|
143
|
+
export interface BookContent extends ContentOf<typeof Book.schema> {}
|
|
144
|
+
export interface ReviewContent extends ContentOf<typeof Review.schema> {}
|
|
145
|
+
|
|
146
|
+
// Startup: pass to every Stack.open(). Each step is checked against both
|
|
147
|
+
// handles' schemas when migration() runs.
|
|
148
|
+
export const migrations = [migration(BookV1, Book, [map('status', { done: 'finished' })])];
|
|
149
|
+
|
|
150
|
+
// Data layer: one collection per type, and typed records from every verb.
|
|
151
|
+
export class Library {
|
|
152
|
+
readonly shelves: Collection<ShelfContent>;
|
|
153
|
+
readonly books: Collection<BookContent>;
|
|
154
|
+
readonly reviews: Collection<ReviewContent>;
|
|
109
155
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return this.client.create(Note, { text, pinned: false });
|
|
156
|
+
constructor(client: StackClient) {
|
|
157
|
+
this.shelves = client.collection(Shelf);
|
|
158
|
+
this.books = client.collection(Book);
|
|
159
|
+
this.reviews = client.collection(Review);
|
|
115
160
|
}
|
|
116
|
-
|
|
117
|
-
|
|
161
|
+
|
|
162
|
+
addBook(title: string, shelfId: string) {
|
|
163
|
+
return this.books.create({ title, status: 'reading', shelfId });
|
|
118
164
|
}
|
|
119
|
-
|
|
120
|
-
|
|
165
|
+
|
|
166
|
+
finish(id: string) {
|
|
167
|
+
return this.books.patchContent(id, { status: 'finished' });
|
|
121
168
|
}
|
|
122
|
-
}
|
|
123
169
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
from: NoteV1.id,
|
|
128
|
-
to: Note.id,
|
|
129
|
-
migrate: (content) => ({ ...content, pinned: false }),
|
|
130
|
-
});
|
|
131
|
-
}
|
|
170
|
+
review(bookId: string, rating: number, text?: string) {
|
|
171
|
+
return this.reviews.create({ bookId, rating, ...(text && { text }) });
|
|
172
|
+
}
|
|
132
173
|
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (appDid) {
|
|
140
|
-
await stack.grantType(Note.baseId, {
|
|
141
|
-
actions: ['create', 'read-own', 'update-own', 'delete-own'],
|
|
142
|
-
grantee: { kind: 'entity', entityId: appDid },
|
|
174
|
+
// Records at @1 come back migrated to @2. One written by a newer release
|
|
175
|
+
// of the app can't be migrated down, so it is reported, not thrown.
|
|
176
|
+
async onShelf(shelfId: string) {
|
|
177
|
+
const page = await this.books.query({
|
|
178
|
+
filter: { content: { shelfId } },
|
|
179
|
+
sort: { contentField: 'title' },
|
|
143
180
|
});
|
|
181
|
+
if (page.misfits.length > 0) {
|
|
182
|
+
console.warn(`${page.misfits.length} books need a newer version of this app`);
|
|
183
|
+
}
|
|
184
|
+
return page.records;
|
|
144
185
|
}
|
|
145
186
|
}
|
|
187
|
+
|
|
188
|
+
// Install, when the owner runs the app. defineType() is a no-op for a
|
|
189
|
+
// schema already stored, so re-running after an upgrade is safe.
|
|
190
|
+
export async function install(stack: Stack) {
|
|
191
|
+
for (const handle of [Shelf, BookV1, Book, Review]) await stack.defineType(handle);
|
|
192
|
+
await stack.migrateAll(Book.baseId);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Install, when the owner doesn't: the same handles, as a request.
|
|
196
|
+
export const manifest: AppManifest = {
|
|
197
|
+
appId: 'com.example.reading',
|
|
198
|
+
name: 'Reading',
|
|
199
|
+
types: [Shelf, BookV1, Book, Review],
|
|
200
|
+
requests: [Shelf, Book, Review].map((handle) => ({
|
|
201
|
+
baseId: handle.baseId,
|
|
202
|
+
actions: ['create', 'read-own', 'update-own', 'delete-own'],
|
|
203
|
+
})),
|
|
204
|
+
};
|
|
146
205
|
```
|
|
147
206
|
|
|
148
|
-
|
|
207
|
+
**When the owner runs the app**, it opens with `Stack.open(adapter, { migrations })`, calls `install(stack)`, then `new Library(stack)`. A server hands each requester `new Library(stack.asActor(session))`.
|
|
208
|
+
|
|
209
|
+
**When the app runs elsewhere** — holding its own key and reaching the stack through `APIAdapter` — it can't define types, so it asks: `adapter.requestInstall(manifest)` resolves to `pending` until the owner approves. The owner reviews `stack.planInstall(manifest, { did })` and applies it with `stack.installApp(plan)`, which defines the types, registers the app's key and grants it the requests. The app still opens with its `migrations`, then builds `new Library(stack)` over its own connection. See [App installs](https://github.com/haverstack/core/blob/main/docs/spec/apps.md).
|
|
210
|
+
|
|
211
|
+
**Name content with an interface**, as above. `ContentOf` flattens its result, so a `type` alias shows the expanded object in editor hovers, while an interface keeps its name. typescript-eslint's recommended `no-empty-object-type` rule rejects an empty interface; its `allowInterfaces: 'with-single-extends'` option admits this one.
|
|
149
212
|
|
|
150
213
|
## Core concepts
|
|
151
214
|
|
|
@@ -186,24 +249,23 @@ Tags, attachments, and relationships are unified under a single model:
|
|
|
186
249
|
|
|
187
250
|
### Migrations
|
|
188
251
|
|
|
189
|
-
Types can evolve over time.
|
|
252
|
+
Types can evolve over time. A new version names the one it migrates from, and `migration()` builds the step between two handles from declared steps — `rename`, `map`, `add`, `drop` and `derive` — checked against both schemas when it runs. Pass every step to `Stack.open()`; the library composes adjacent steps into chains:
|
|
190
253
|
|
|
191
254
|
```ts
|
|
192
|
-
|
|
255
|
+
const NoteV2 = typeHandle({
|
|
193
256
|
id: 'com.example.myapp/note@2',
|
|
194
257
|
name: 'Note',
|
|
258
|
+
migratesFrom: NoteV1,
|
|
195
259
|
schema: { text: { kind: 'text', required: true }, title: { kind: 'string' } },
|
|
196
|
-
migratesFrom: 'com.example.myapp/note@1',
|
|
197
260
|
});
|
|
198
261
|
|
|
199
|
-
stack.
|
|
200
|
-
|
|
201
|
-
to: 'com.example.myapp/note@2',
|
|
202
|
-
migrate: (content) => ({ ...content, title: '' }),
|
|
262
|
+
const stack = await Stack.open(adapter, {
|
|
263
|
+
migrations: [migration(NoteV1, NoteV2, [add('title', '')])],
|
|
203
264
|
});
|
|
265
|
+
await stack.defineType(NoteV2);
|
|
204
266
|
```
|
|
205
267
|
|
|
206
|
-
Records stay at the version they were written at: `get()` and `query()` return them as stored, `presentAt: 'latest'` migrates them in memory for one read, and `stack.migrateAll()` commits a family to disk.
|
|
268
|
+
Records stay at the version they were written at: `get()` and `query()` return them as stored, `presentAt: 'latest'` migrates them in memory for one read, and `stack.migrateAll()` commits a family to disk. Because the steps are declared rather than a function, a collection carries a content filter or sort across the versions they translate to — a filter on `title` reaches records still at `@1`. Pass migrations at every startup — see [Writing an app](#writing-an-app).
|
|
207
269
|
|
|
208
270
|
## License
|
|
209
271
|
|
package/dist/access.d.ts
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
* -------------------------------------------------------
|
|
4
4
|
* Implements the Permissions model from the spec: a pure predicate over a
|
|
5
5
|
* Record's `permissions` field, with no dependency on a transport layer or
|
|
6
|
-
* storage backend. Used by ScopedStack (see scoped-stack.ts) to enforce
|
|
6
|
+
* storage backend. Used by ScopedStack (see scoped-stack/scoped-stack.ts) to enforce
|
|
7
7
|
* access control; exported standalone for callers that want the raw
|
|
8
|
-
* predicate.
|
|
8
|
+
* predicate. Also the one home of the `_group` roster rules that predicate
|
|
9
|
+
* and the grant path resolve against, and of the owner-acting-alone tier.
|
|
9
10
|
*
|
|
10
11
|
* A permission set is read as data throughout, never as the type: it can
|
|
11
12
|
* arrive from an import or a foreign server, where no compiler has seen
|
|
12
13
|
* it. Every tier is affirmative, so an element read past its own kind —
|
|
13
14
|
* or carrying no kind at all — confers nothing.
|
|
14
15
|
*/
|
|
15
|
-
import type {
|
|
16
|
+
import type { AuthorityAssociation, DataAssociation, EntityId, GroupRole, RecordId, StackRecord } from './types/index.js';
|
|
16
17
|
import type { ValidationError } from './validate.js';
|
|
17
18
|
export type AccessMode = 'read' | 'write';
|
|
18
19
|
/**
|
|
@@ -21,13 +22,10 @@ export type AccessMode = 'read' | 'write';
|
|
|
21
22
|
*/
|
|
22
23
|
export type RecordResolver = (id: RecordId) => Promise<StackRecord | null>;
|
|
23
24
|
/**
|
|
24
|
-
* Whether an entity
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* A delegated app's own authority is a separate question, asked of the
|
|
29
|
-
* principal. See docs/spec/access-control.md § Record-level permissions and
|
|
30
|
-
* § Delegation: principal and subject.
|
|
25
|
+
* Whether an entity may read or write a Record. The entity is the
|
|
26
|
+
* **subject**: record-level permissions are written about who data is for,
|
|
27
|
+
* never the software carrying the request, whose authority is asked of the
|
|
28
|
+
* principal. See docs/spec/access-control.md § Delegation: principal and subject.
|
|
31
29
|
*/
|
|
32
30
|
export declare function checkAccess(record: StackRecord, subjectEntityId: EntityId | null, ownerEntityId: EntityId | null, mode: AccessMode, resolveRecord: RecordResolver): Promise<boolean>;
|
|
33
31
|
/**
|
|
@@ -39,40 +37,40 @@ export declare function checkAccess(record: StackRecord, subjectEntityId: Entity
|
|
|
39
37
|
*/
|
|
40
38
|
export declare function validatePermissions(permissions: AuthorityAssociation[] | undefined, path?: string): ValidationError[];
|
|
41
39
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
/** Whether a held role meets a grantee's: an admin is also a member. */
|
|
46
|
+
export declare function roleSatisfies(held: GroupRole | null, required: GroupRole): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether a Record is a `_group`, in any of its type versions — the family
|
|
49
|
+
* whose roster rules apply.
|
|
50
|
+
*/
|
|
51
|
+
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.
|
|
48
57
|
*/
|
|
49
58
|
export declare function carriesRoster(record: StackRecord): boolean;
|
|
50
59
|
/**
|
|
51
|
-
* An entity's role
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* Reads associations alone and cannot tell whose they are: callers pass
|
|
55
|
-
* carriesRoster() first, or an app's own `member` relationships become a
|
|
56
|
-
* roster.
|
|
60
|
+
* An entity's role on a `_group` roster; a matching `admin` wins regardless
|
|
61
|
+
* of order. Reads associations alone and cannot tell whose they are, so
|
|
62
|
+
* callers pass carriesRoster() first.
|
|
57
63
|
*/
|
|
58
64
|
export declare function groupRoleFromAssociations(associations: DataAssociation[] | undefined, entityId: EntityId): GroupRole | null;
|
|
59
65
|
/**
|
|
60
|
-
* Whether a roster carries at least one `admin` — the invariant every
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* removal when they are the last, without either case naming who is going.
|
|
65
|
-
* Reads associations alone, on the same terms as
|
|
66
|
-
* groupRoleFromAssociations() above.
|
|
66
|
+
* Whether a roster carries at least one `admin` — the invariant every
|
|
67
|
+
* `_group` write must leave standing. Asked of the roster the write would
|
|
68
|
+
* *produce*, which lets an admin leave while another remains and refuses
|
|
69
|
+
* the last. Reads associations alone, like groupRoleFromAssociations().
|
|
67
70
|
*/
|
|
68
71
|
export declare function hasGroupAdmin(associations: DataAssociation[] | undefined): boolean;
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
* what makes it one — a `record` target carrying the same label confers
|
|
72
|
-
* nothing, so it must not count toward the invariant either.
|
|
73
|
-
* See docs/spec/data-model.md § Relationship targets.
|
|
74
|
-
*/
|
|
75
|
-
export declare function isGroupAdminAssociation(association: Association): boolean;
|
|
72
|
+
/** Bootstraps a `_group` record's first admin at create time. */
|
|
73
|
+
export declare function stampGroupAdmin(associations: DataAssociation[] | undefined, creator: EntityId): DataAssociation[];
|
|
76
74
|
/**
|
|
77
75
|
* Whether a request is the stack owner acting alone — the tier behind every
|
|
78
76
|
* unconditional owner authority. Being the owner is never sufficient on its
|
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;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAEV,oBAAoB,EACpB,eAAe,EACf,QAAQ,EAER,SAAS,EAET,QAAQ,EAER,WAAW,EACZ,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;AAE3E;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,QAAQ,GAAG,IAAI,EAChC,aAAa,EAAE,QAAQ,GAAG,IAAI,EAC9B,IAAI,EAAE,UAAU,EAChB,aAAa,EAAE,cAAc,GAC5B,OAAO,CAAC,OAAO,CAAC,CA4BlB;AA0CD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,oBAAoB,EAAE,GAAG,SAAS,EAC/C,IAAI,SAAgB,GACnB,eAAe,EAAE,CAanB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,QAAQ,EACjB,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,cAAc,EAC7B,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC,GACnC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAS3B;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;;;;;GAKG;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
|
@@ -3,26 +3,24 @@
|
|
|
3
3
|
* -------------------------------------------------------
|
|
4
4
|
* Implements the Permissions model from the spec: a pure predicate over a
|
|
5
5
|
* Record's `permissions` field, with no dependency on a transport layer or
|
|
6
|
-
* storage backend. Used by ScopedStack (see scoped-stack.ts) to enforce
|
|
6
|
+
* storage backend. Used by ScopedStack (see scoped-stack/scoped-stack.ts) to enforce
|
|
7
7
|
* access control; exported standalone for callers that want the raw
|
|
8
|
-
* predicate.
|
|
8
|
+
* predicate. Also the one home of the `_group` roster rules that predicate
|
|
9
|
+
* and the grant path resolve against, and of the owner-acting-alone tier.
|
|
9
10
|
*
|
|
10
11
|
* A permission set is read as data throughout, never as the type: it can
|
|
11
12
|
* arrive from an import or a foreign server, where no compiler has seen
|
|
12
13
|
* it. Every tier is affirmative, so an element read past its own kind —
|
|
13
14
|
* or carrying no kind at all — confers nothing.
|
|
14
15
|
*/
|
|
15
|
-
import { SYSTEM_TYPES } from './types.js';
|
|
16
|
-
import { granteeEqual } from './
|
|
16
|
+
import { SYSTEM_TYPES } from './types/index.js';
|
|
17
|
+
import { granteeEqual } from './associations/identity.js';
|
|
17
18
|
import { baseIdOf } from './schema.js';
|
|
18
19
|
/**
|
|
19
|
-
* Whether an entity
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* A delegated app's own authority is a separate question, asked of the
|
|
24
|
-
* principal. See docs/spec/access-control.md § Record-level permissions and
|
|
25
|
-
* § Delegation: principal and subject.
|
|
20
|
+
* Whether an entity may read or write a Record. The entity is the
|
|
21
|
+
* **subject**: record-level permissions are written about who data is for,
|
|
22
|
+
* never the software carrying the request, whose authority is asked of the
|
|
23
|
+
* principal. See docs/spec/access-control.md § Delegation: principal and subject.
|
|
26
24
|
*/
|
|
27
25
|
export async function checkAccess(record, subjectEntityId, ownerEntityId, mode, resolveRecord) {
|
|
28
26
|
if (subjectEntityId && subjectEntityId === ownerEntityId)
|
|
@@ -31,6 +29,9 @@ export async function checkAccess(record, subjectEntityId, ownerEntityId, mode,
|
|
|
31
29
|
// No permissions = private.
|
|
32
30
|
if (!perms || perms.length === 0)
|
|
33
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();
|
|
34
35
|
for (const p of perms) {
|
|
35
36
|
if (p?.kind === 'anyone') {
|
|
36
37
|
if (mode === 'read' && isWorldRead(p))
|
|
@@ -48,7 +49,7 @@ export async function checkAccess(record, subjectEntityId, ownerEntityId, mode,
|
|
|
48
49
|
// See docs/spec/access-control.md § Write implies read.
|
|
49
50
|
if (mode === 'write' && !holdsRead(perms, p.grantee))
|
|
50
51
|
continue;
|
|
51
|
-
if (await granteeCovers(p.grantee, subjectEntityId, resolveRecord))
|
|
52
|
+
if (await granteeCovers(p.grantee, subjectEntityId, resolveRecord, roles))
|
|
52
53
|
return true;
|
|
53
54
|
}
|
|
54
55
|
return false;
|
|
@@ -77,13 +78,13 @@ function isWorldRead(association) {
|
|
|
77
78
|
* Whether a grantee reaches this subject. `role: 'member'` is the wider
|
|
78
79
|
* set — an admin satisfies it — and `role: 'admin'` the narrower.
|
|
79
80
|
*/
|
|
80
|
-
async function granteeCovers(grantee, subjectEntityId, resolveRecord) {
|
|
81
|
+
async function granteeCovers(grantee, subjectEntityId, resolveRecord, roles) {
|
|
81
82
|
if (!subjectEntityId)
|
|
82
83
|
return false;
|
|
83
84
|
if (grantee.kind === 'entity')
|
|
84
85
|
return grantee.entityId === subjectEntityId;
|
|
85
|
-
const role = await resolveGroupRole(grantee.groupId, subjectEntityId, resolveRecord);
|
|
86
|
-
return grantee.role
|
|
86
|
+
const role = await resolveGroupRole(grantee.groupId, subjectEntityId, resolveRecord, roles);
|
|
87
|
+
return roleSatisfies(role, grantee.role);
|
|
87
88
|
}
|
|
88
89
|
/**
|
|
89
90
|
* Rejects a permission set where some grantee holds `write` with no `read`
|
|
@@ -107,31 +108,45 @@ export function validatePermissions(permissions, path = 'permissions') {
|
|
|
107
108
|
});
|
|
108
109
|
return errors;
|
|
109
110
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
/** Whether a held role meets a grantee's: an admin is also a member. */
|
|
127
|
+
export function roleSatisfies(held, required) {
|
|
128
|
+
return required === 'admin' ? held === 'admin' : held !== null;
|
|
116
129
|
}
|
|
117
130
|
/**
|
|
118
|
-
* Whether a Record is a
|
|
119
|
-
*
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
* Whether a Record is a `_group`, in any of its type versions — the family
|
|
132
|
+
* whose roster rules apply.
|
|
133
|
+
*/
|
|
134
|
+
export function isGroupRecord(record) {
|
|
135
|
+
return baseIdOf(record.typeId) === SYSTEM_TYPES.GROUP;
|
|
136
|
+
}
|
|
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.
|
|
124
142
|
*/
|
|
125
143
|
export function carriesRoster(record) {
|
|
126
|
-
return
|
|
144
|
+
return isGroupRecord(record) && !record.deletedAt;
|
|
127
145
|
}
|
|
128
146
|
/**
|
|
129
|
-
* An entity's role
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* Reads associations alone and cannot tell whose they are: callers pass
|
|
133
|
-
* carriesRoster() first, or an app's own `member` relationships become a
|
|
134
|
-
* roster.
|
|
147
|
+
* An entity's role on a `_group` roster; a matching `admin` wins regardless
|
|
148
|
+
* of order. Reads associations alone and cannot tell whose they are, so
|
|
149
|
+
* callers pass carriesRoster() first.
|
|
135
150
|
*/
|
|
136
151
|
export function groupRoleFromAssociations(associations, entityId) {
|
|
137
152
|
let role = null;
|
|
@@ -146,13 +161,10 @@ export function groupRoleFromAssociations(associations, entityId) {
|
|
|
146
161
|
return role;
|
|
147
162
|
}
|
|
148
163
|
/**
|
|
149
|
-
* Whether a roster carries at least one `admin` — the invariant every
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* removal when they are the last, without either case naming who is going.
|
|
154
|
-
* Reads associations alone, on the same terms as
|
|
155
|
-
* groupRoleFromAssociations() above.
|
|
164
|
+
* Whether a roster carries at least one `admin` — the invariant every
|
|
165
|
+
* `_group` write must leave standing. Asked of the roster the write would
|
|
166
|
+
* *produce*, which lets an admin leave while another remains and refuses
|
|
167
|
+
* the last. Reads associations alone, like groupRoleFromAssociations().
|
|
156
168
|
*/
|
|
157
169
|
export function hasGroupAdmin(associations) {
|
|
158
170
|
return (associations ?? []).some(isGroupAdminAssociation);
|
|
@@ -163,11 +175,22 @@ export function hasGroupAdmin(associations) {
|
|
|
163
175
|
* nothing, so it must not count toward the invariant either.
|
|
164
176
|
* See docs/spec/data-model.md § Relationship targets.
|
|
165
177
|
*/
|
|
166
|
-
|
|
178
|
+
function isGroupAdminAssociation(association) {
|
|
167
179
|
return (association.kind === 'relationship' &&
|
|
168
180
|
association.label === 'admin' &&
|
|
169
181
|
association.target.kind === 'entity');
|
|
170
182
|
}
|
|
183
|
+
/** Bootstraps a `_group` record's first admin at create time. */
|
|
184
|
+
export function stampGroupAdmin(associations, creator) {
|
|
185
|
+
const list = associations ?? [];
|
|
186
|
+
const alreadyAdmin = list.some((a) => isGroupAdminAssociation(a) && a.target.entityId === creator);
|
|
187
|
+
if (alreadyAdmin)
|
|
188
|
+
return list;
|
|
189
|
+
return [
|
|
190
|
+
...list,
|
|
191
|
+
{ kind: 'relationship', label: 'admin', target: { kind: 'entity', entityId: creator } },
|
|
192
|
+
];
|
|
193
|
+
}
|
|
171
194
|
/**
|
|
172
195
|
* Whether a request is the stack owner acting alone — the tier behind every
|
|
173
196
|
* unconditional owner authority. Being the owner is never sufficient on its
|
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,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Combining adapters
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* A full StackAdapter assembled from a record half and a blob half, so a
|
|
5
|
+
* deployment can keep records and bytes in different backends without
|
|
6
|
+
* either adapter knowing about the other.
|
|
7
|
+
*/
|
|
8
|
+
import type { StackAdapter, StackRecordAdapter, StackBlobAdapter } from '../types/index.js';
|
|
2
9
|
/**
|
|
3
10
|
* Compose a StackRecordAdapter and a StackBlobAdapter into a single StackAdapter.
|
|
4
11
|
* Use this when you want different backends for records and blobs, e.g.:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combine.d.ts","sourceRoot":"","sources":["../../src/adapter/combine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAEV,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,YAAY,CAsEf"}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combining adapters
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* A full StackAdapter assembled from a record half and a blob half, so a
|
|
5
|
+
* deployment can keep records and bytes in different backends without
|
|
6
|
+
* either adapter knowing about the other.
|
|
7
|
+
*/
|
|
1
8
|
/**
|
|
2
9
|
* Compose a StackRecordAdapter and a StackBlobAdapter into a single StackAdapter.
|
|
3
10
|
* Use this when you want different backends for records and blobs, e.g.:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combine.js","sourceRoot":"","sources":["../../src/adapter/combine.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,10 +7,11 @@
|
|
|
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, BumpVersionOptions, SnapshotOptions, JournalEntryInput, JournalOptions,
|
|
11
|
-
export { OwnerMismatchError } from '
|
|
10
|
+
export type { StackAdapter, StackRecordAdapter, StackBlobAdapter, BlobInfo, BumpVersionOptions, SnapshotOptions, JournalEntryInput, JournalOptions, MutateOptions, } from '../types/index.js';
|
|
11
|
+
export { OwnerMismatchError } from '../errors.js';
|
|
12
12
|
export { combineAdapters } from './combine.js';
|
|
13
|
-
export { assertQueryCapabilities, assertSortCapability, assertValidSort, assertValidAssociationFilters, assertValidJournalQuery, assertValidVersionsQuery, filtersContent, parseContentFilterKey, } from '
|
|
14
|
-
export { contentSortEntry, contentSortKey, compareSortEntries } from './sort.js';
|
|
13
|
+
export { assertQueryCapabilities, assertSortCapability, assertValidSort, assertValidAssociationFilters, assertValidJournalQuery, assertValidVersionsQuery, filtersContent, parseContentFilterKey, } from '../query-validation.js';
|
|
14
|
+
export { contentSortEntry, contentSortKey, compareSortEntries, constantSortEntry, sortConstant, } from './sort.js';
|
|
15
15
|
export type { SortEntry } from './sort.js';
|
|
16
|
-
|
|
16
|
+
export { AUTHORITY_ASSOCIATION_KINDS, partitionAssociations } from '../associations/identity.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|