@haverstack/core 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -15
- package/dist/access.d.ts +4 -6
- package/dist/access.d.ts.map +1 -1
- package/dist/access.js +7 -8
- package/dist/access.js.map +1 -1
- package/dist/adapter-entry.d.ts +7 -6
- package/dist/adapter-entry.d.ts.map +1 -1
- package/dist/adapter-entry.js +6 -5
- package/dist/adapter-entry.js.map +1 -1
- package/dist/attachment-download.d.ts +3 -1
- package/dist/attachment-download.d.ts.map +1 -1
- package/dist/attachment-download.js +1 -1
- package/dist/attachment-download.js.map +1 -1
- package/dist/changes.d.ts +22 -16
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +43 -32
- package/dist/changes.js.map +1 -1
- package/dist/combine.d.ts +1 -1
- package/dist/combine.js +6 -6
- package/dist/combine.js.map +1 -1
- package/dist/errors.d.ts +24 -12
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +32 -17
- package/dist/errors.js.map +1 -1
- package/dist/grants.d.ts +17 -25
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +27 -14
- package/dist/grants.js.map +1 -1
- package/dist/identity-bindings.js +2 -2
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/query-validation.d.ts +28 -11
- package/dist/query-validation.d.ts.map +1 -1
- package/dist/query-validation.js +95 -36
- package/dist/query-validation.js.map +1 -1
- package/dist/record-changes.d.ts +11 -10
- package/dist/record-changes.d.ts.map +1 -1
- package/dist/record-changes.js +33 -25
- package/dist/record-changes.js.map +1 -1
- package/dist/record-id.d.ts +3 -3
- package/dist/record-id.js +8 -8
- package/dist/record-id.js.map +1 -1
- package/dist/scoped-stack.d.ts +71 -34
- package/dist/scoped-stack.d.ts.map +1 -1
- package/dist/scoped-stack.js +107 -88
- package/dist/scoped-stack.js.map +1 -1
- package/dist/stack-reads.d.ts +2 -2
- package/dist/stack-reads.js +5 -5
- package/dist/stack-reads.js.map +1 -1
- package/dist/stack.d.ts +132 -161
- package/dist/stack.d.ts.map +1 -1
- package/dist/stack.js +287 -288
- package/dist/stack.js.map +1 -1
- package/dist/testing.d.ts +17 -17
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +46 -42
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +169 -143
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/wire-body.d.ts +71 -0
- package/dist/wire-body.d.ts.map +1 -0
- package/dist/wire-body.js +138 -0
- package/dist/wire-body.js.map +1 -0
- package/dist/wire-entry.d.ts +9 -8
- package/dist/wire-entry.d.ts.map +1 -1
- package/dist/wire-entry.js +13 -14
- package/dist/wire-entry.js.map +1 -1
- package/dist/wire-record.d.ts +4 -4
- package/dist/wire-record.d.ts.map +1 -1
- package/dist/wire-record.js +35 -22
- package/dist/wire-record.js.map +1 -1
- package/dist/wire-request.d.ts +37 -4
- package/dist/wire-request.d.ts.map +1 -1
- package/dist/wire-request.js +288 -79
- package/dist/wire-request.js.map +1 -1
- package/package.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
export type RecordId = string;
|
|
14
14
|
/** Namespaced, versioned type identifier e.g. "com.example.myapp/note@2" */
|
|
15
15
|
export type TypeId = string;
|
|
16
|
-
/**
|
|
16
|
+
/** A type family: a TypeId without its version, e.g. "com.example.myapp/note" */
|
|
17
|
+
export type BaseId = string;
|
|
18
|
+
/** Opaque file identifier returned by putBlob() */
|
|
17
19
|
export type FileId = string;
|
|
18
20
|
/**
|
|
19
21
|
* Reverse-DNS identifier for the software that wrote a Record, e.g.
|
|
20
22
|
* "com.example.myapp" — not a RecordId. Self-reported and never a
|
|
21
|
-
* permission input; `StackRecord.principalId` is the verified
|
|
23
|
+
* permission input; `StackRecord.createdBy.principalId` is the verified
|
|
24
|
+
* counterpart.
|
|
22
25
|
* See docs/spec/identity.md § App.
|
|
23
26
|
*/
|
|
24
27
|
export type AppId = string;
|
|
@@ -29,6 +32,17 @@ export type AppId = string;
|
|
|
29
32
|
* See docs/spec/identity.md.
|
|
30
33
|
*/
|
|
31
34
|
export type EntityId = string;
|
|
35
|
+
/**
|
|
36
|
+
* Who did something, and through which principal. `subjectId` is who the
|
|
37
|
+
* act is attributed to; `principalId` is who authenticated, present only
|
|
38
|
+
* when it differs — a delegated app acting for its user. `Stack` stores
|
|
39
|
+
* a `principalId` equal to `subjectId` as absent, so the two spellings of
|
|
40
|
+
* "acted as itself" never diverge. See docs/spec/data-model.md § Actor.
|
|
41
|
+
*/
|
|
42
|
+
export type Actor = {
|
|
43
|
+
subjectId: EntityId;
|
|
44
|
+
principalId?: EntityId;
|
|
45
|
+
};
|
|
32
46
|
export type TagAssociation = {
|
|
33
47
|
kind: 'tag';
|
|
34
48
|
label: string;
|
|
@@ -36,7 +50,7 @@ export type TagAssociation = {
|
|
|
36
50
|
/**
|
|
37
51
|
* `attachmentRecordId` names the `_attachment` record whose upload
|
|
38
52
|
* established this reference — annotation, not identity, since `fileId`
|
|
39
|
-
* alone is what GC, the `
|
|
53
|
+
* alone is what GC, the `referencesFileId` filter and file access ask
|
|
40
54
|
* about. See docs/spec/attachments.md § Naming the upload a reference came from.
|
|
41
55
|
*/
|
|
42
56
|
export type AttachmentAssociation = {
|
|
@@ -51,7 +65,7 @@ export type AttachmentAssociation = {
|
|
|
51
65
|
* stack. See docs/spec/data-model.md § Associations.
|
|
52
66
|
*/
|
|
53
67
|
export type RecordTarget = {
|
|
54
|
-
|
|
68
|
+
kind: 'record';
|
|
55
69
|
recordId: RecordId;
|
|
56
70
|
stackUrl?: string;
|
|
57
71
|
};
|
|
@@ -61,7 +75,7 @@ export type RecordTarget = {
|
|
|
61
75
|
* See docs/spec/identity.md § Group.
|
|
62
76
|
*/
|
|
63
77
|
export type EntityTarget = {
|
|
64
|
-
|
|
78
|
+
kind: 'entity';
|
|
65
79
|
entityId: EntityId;
|
|
66
80
|
};
|
|
67
81
|
/**
|
|
@@ -71,7 +85,7 @@ export type EntityTarget = {
|
|
|
71
85
|
* See docs/spec/data-model.md § Relationship targets.
|
|
72
86
|
*/
|
|
73
87
|
export type ExternalTarget = {
|
|
74
|
-
|
|
88
|
+
kind: 'external';
|
|
75
89
|
ns: string;
|
|
76
90
|
id: string;
|
|
77
91
|
};
|
|
@@ -87,25 +101,28 @@ export type RelationshipAssociation = {
|
|
|
87
101
|
* granting and revoking are a plain add and remove and the element's
|
|
88
102
|
* identity carries its whole meaning. Its grantee shape is its own rather
|
|
89
103
|
* than a `RelationshipTarget`: a target names no role and has no group
|
|
90
|
-
*
|
|
104
|
+
* arm. See docs/spec/access-control.md § Record-level permissions.
|
|
91
105
|
*/
|
|
92
106
|
export type PermissionAssociation = {
|
|
93
107
|
kind: 'permission';
|
|
94
108
|
label: 'read' | 'write';
|
|
95
|
-
grantee:
|
|
109
|
+
grantee: Grantee;
|
|
96
110
|
};
|
|
111
|
+
/** An entity's standing within a `_group` Record's roster. `admin` implies `member` for ACL purposes. */
|
|
112
|
+
export type GroupRole = 'member' | 'admin';
|
|
97
113
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
114
|
+
* The grantee arms both permission layers share, spelled identically so a
|
|
115
|
+
* value built for one is valid for the other. `role` is required — `member`
|
|
116
|
+
* is the wider set, `admin` the narrower, matching the roster labels where
|
|
117
|
+
* admin implies member. See docs/spec/access-control.md § Who a grant reaches.
|
|
101
118
|
*/
|
|
102
|
-
export type
|
|
103
|
-
|
|
119
|
+
export type Grantee = {
|
|
120
|
+
kind: 'entity';
|
|
104
121
|
entityId: EntityId;
|
|
105
122
|
} | {
|
|
106
|
-
|
|
123
|
+
kind: 'group';
|
|
107
124
|
groupId: RecordId;
|
|
108
|
-
role:
|
|
125
|
+
role: GroupRole;
|
|
109
126
|
};
|
|
110
127
|
/**
|
|
111
128
|
* Reach to the world, spelled affirmatively: it names no grantee and
|
|
@@ -166,46 +183,43 @@ export type AssociationChange = {
|
|
|
166
183
|
* record, so they stay their own verbs rather than folding in here.
|
|
167
184
|
* See docs/spec/data-model.md § Mutations.
|
|
168
185
|
*/
|
|
169
|
-
export type
|
|
186
|
+
export type RecordChangeSet = {
|
|
170
187
|
contentPatch?: Record<string, unknown | null>;
|
|
171
|
-
parentId?:
|
|
188
|
+
parentId?: RecordId | null;
|
|
172
189
|
permissions?: AuthorityAssociation[];
|
|
173
190
|
associations?: DataAssociation[];
|
|
174
191
|
unlisted?: boolean;
|
|
175
192
|
};
|
|
176
193
|
/** The keys of a change set, for presence checks that must not miss one. */
|
|
177
|
-
export declare const
|
|
194
|
+
export declare const RECORD_CHANGE_SET_KEYS: readonly ["contentPatch", "parentId", "permissions", "associations", "unlisted"];
|
|
178
195
|
export type StackRecord = {
|
|
179
196
|
id: RecordId;
|
|
180
197
|
typeId: TypeId;
|
|
181
198
|
createdAt: Date;
|
|
182
199
|
updatedAt: Date;
|
|
183
200
|
content: Record<string, unknown>;
|
|
201
|
+
/**
|
|
202
|
+
* Ordinal of the snapshot history, not a count of changes: associations,
|
|
203
|
+
* permissions, parent and listing moves leave it alone. See
|
|
204
|
+
* docs/spec/versioning.md § Version history.
|
|
205
|
+
*/
|
|
184
206
|
version: number;
|
|
185
207
|
parentId?: RecordId;
|
|
186
|
-
entityId?: EntityId;
|
|
187
208
|
appId?: AppId;
|
|
188
209
|
/**
|
|
189
|
-
* The
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* present means `appId` can be checked against the `_app` record naming
|
|
193
|
-
* this DID. See docs/spec/identity.md § App.
|
|
194
|
-
*/
|
|
195
|
-
principalId?: EntityId;
|
|
196
|
-
/**
|
|
197
|
-
* The subject that performed the mutation this version records — unlike
|
|
198
|
-
* `entityId`, it moves with every write. Absent means an unscoped `Stack`
|
|
199
|
-
* wrote it, which names no requester.
|
|
210
|
+
* The record's author, stamped once by the create. A `principalId` here
|
|
211
|
+
* is what lets `appId` be checked against the `_app` record naming that
|
|
212
|
+
* DID; without one, `appId` is an unverifiable self-report.
|
|
200
213
|
* See docs/spec/data-model.md § Authorship and attribution.
|
|
201
214
|
*/
|
|
202
|
-
|
|
215
|
+
createdBy?: Actor;
|
|
203
216
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
217
|
+
* Who performed the mutation this version records — unlike `createdBy`,
|
|
218
|
+
* it moves with every write. Absent means an unscoped `Stack` wrote it,
|
|
219
|
+
* which names no requester.
|
|
206
220
|
* See docs/spec/data-model.md § Authorship and attribution.
|
|
207
221
|
*/
|
|
208
|
-
|
|
222
|
+
updatedBy?: Actor;
|
|
209
223
|
deletedAt?: Date;
|
|
210
224
|
/**
|
|
211
225
|
* Present when the record is withheld from enumeration — absent from
|
|
@@ -232,15 +246,13 @@ export type RecordVersion = {
|
|
|
232
246
|
content: Record<string, unknown>;
|
|
233
247
|
updatedAt: Date;
|
|
234
248
|
/**
|
|
235
|
-
* The record's author, carried through from `record.
|
|
236
|
-
*
|
|
249
|
+
* The record's author, carried through from `record.createdBy` — not the
|
|
250
|
+
* actor of the change this version records.
|
|
237
251
|
* See docs/spec/versioning.md § Version history.
|
|
238
252
|
*/
|
|
239
|
-
|
|
253
|
+
createdBy?: Actor;
|
|
240
254
|
/** Who performed the mutation that produced this version. */
|
|
241
|
-
updatedBy?:
|
|
242
|
-
/** The principal behind that mutation, when it isn't `updatedBy`. */
|
|
243
|
-
updatedVia?: EntityId;
|
|
255
|
+
updatedBy?: Actor;
|
|
244
256
|
};
|
|
245
257
|
export type ScalarFieldKind = 'string' | 'number' | 'boolean' | 'date' | 'text' | 'record-ref' | 'file-ref';
|
|
246
258
|
export type ScalarFieldDef = {
|
|
@@ -294,7 +306,7 @@ export type TypeSchema = {
|
|
|
294
306
|
};
|
|
295
307
|
export type StackType = {
|
|
296
308
|
id: TypeId;
|
|
297
|
-
baseId:
|
|
309
|
+
baseId: BaseId;
|
|
298
310
|
version: number;
|
|
299
311
|
name: string;
|
|
300
312
|
schema: TypeSchema;
|
|
@@ -311,7 +323,7 @@ export type StackType = {
|
|
|
311
323
|
export type EntityContent = {
|
|
312
324
|
/**
|
|
313
325
|
* The identity this profile is about. e.g. "did:key:z6Mk...". A binding,
|
|
314
|
-
* not a value —
|
|
326
|
+
* not a value — an Actor's `subjectId` resolves through it to the name
|
|
315
327
|
* this card carries, so it is unique per stack and immutable once set.
|
|
316
328
|
* See docs/spec/identity.md § DID bindings.
|
|
317
329
|
*/
|
|
@@ -341,7 +353,7 @@ export type AppContent = {
|
|
|
341
353
|
version?: string;
|
|
342
354
|
/**
|
|
343
355
|
* The DID this app authenticates with, when it holds a key of its own.
|
|
344
|
-
* Lets an attribution UI resolve
|
|
356
|
+
* Lets an attribution UI resolve an Actor's `principalId` to this card,
|
|
345
357
|
* and a server check a self-reported `appId` against the principal that
|
|
346
358
|
* wrote it. Absent for apps that ride their user's identity. Unique per
|
|
347
359
|
* stack and immutable once set, like `appId` above — see
|
|
@@ -366,7 +378,7 @@ export type GroupContent = {
|
|
|
366
378
|
/**
|
|
367
379
|
* Actions that can be granted via a _grant record. The array is the source
|
|
368
380
|
* of truth — GrantAction is derived from it so runtime validation (see
|
|
369
|
-
* Stack.
|
|
381
|
+
* Stack.grantType()) can't drift from the type.
|
|
370
382
|
*/
|
|
371
383
|
export declare const GRANT_ACTIONS: readonly ["create", "read-own", "read-any", "update-own", "update-any", "delete-own", "delete-any"];
|
|
372
384
|
export type GrantAction = (typeof GRANT_ACTIONS)[number];
|
|
@@ -385,16 +397,18 @@ export type GrantAction = (typeof GRANT_ACTIONS)[number];
|
|
|
385
397
|
* principal half of a delegated request.
|
|
386
398
|
* See docs/spec/access-control.md § Type-level grants.
|
|
387
399
|
*/
|
|
388
|
-
export type GrantGrantee = {
|
|
389
|
-
kind: 'entity';
|
|
390
|
-
entityId: EntityId;
|
|
391
|
-
} | {
|
|
392
|
-
kind: 'group';
|
|
393
|
-
groupId: RecordId;
|
|
394
|
-
role: 'member' | 'admin';
|
|
395
|
-
} | {
|
|
400
|
+
export type GrantGrantee = Grantee | {
|
|
396
401
|
kind: 'authenticated';
|
|
397
402
|
};
|
|
403
|
+
/**
|
|
404
|
+
* What `Stack.grantType()` and `Stack.revokeType()` take beside the type they act
|
|
405
|
+
* on — the type-level counterpart of the `AuthorityAssociation`
|
|
406
|
+
* `grantAccess()` takes beside a record id.
|
|
407
|
+
*/
|
|
408
|
+
export type TypeGrant = {
|
|
409
|
+
actions: GrantAction[];
|
|
410
|
+
grantee: GrantGrantee;
|
|
411
|
+
};
|
|
398
412
|
/** Content for _grant records */
|
|
399
413
|
export type GrantContent = {
|
|
400
414
|
/** Which record type the grant applies to. */
|
|
@@ -404,6 +418,13 @@ export type GrantContent = {
|
|
|
404
418
|
/** Who the grant reaches. Required — see GrantGrantee. */
|
|
405
419
|
grantee: GrantGrantee;
|
|
406
420
|
};
|
|
421
|
+
/** The metadata an upload's `_attachment@1` record carries beside its bytes. */
|
|
422
|
+
export type PutAttachmentOptions = {
|
|
423
|
+
mimeType: string;
|
|
424
|
+
filename?: string;
|
|
425
|
+
/** Attribution for the metadata record, as create()'s `appId`. */
|
|
426
|
+
appId?: AppId;
|
|
427
|
+
};
|
|
407
428
|
/** Content for _attachment records — one per upload, tracks file metadata. */
|
|
408
429
|
export type AttachmentContent = {
|
|
409
430
|
/** Content-addressed file identifier (SHA-256 hex). */
|
|
@@ -447,21 +468,21 @@ export declare const SYSTEM_TYPES: {
|
|
|
447
468
|
* matches its whole namespace.
|
|
448
469
|
*/
|
|
449
470
|
export type RelationshipTargetPattern = {
|
|
450
|
-
|
|
471
|
+
kind: 'record';
|
|
451
472
|
recordId: RecordId;
|
|
452
473
|
stackUrl?: string;
|
|
453
474
|
} | {
|
|
454
|
-
|
|
475
|
+
kind: 'entity';
|
|
455
476
|
entityId: EntityId;
|
|
456
477
|
} | {
|
|
457
|
-
|
|
478
|
+
kind: 'external';
|
|
458
479
|
ns: string;
|
|
459
480
|
id?: string;
|
|
460
481
|
};
|
|
461
482
|
/**
|
|
462
483
|
* A relationship query names a label, a target, or both — never neither.
|
|
463
484
|
* "Carries any relationship at all" is deliberately not expressible, in
|
|
464
|
-
* line with `tags` and `
|
|
485
|
+
* line with `tags` and `attachment`, which likewise have no match-any
|
|
465
486
|
* form. See docs/spec/data-model.md § Filter.
|
|
466
487
|
*/
|
|
467
488
|
export type RelatedToFilter = {
|
|
@@ -471,6 +492,18 @@ export type RelatedToFilter = {
|
|
|
471
492
|
label?: string;
|
|
472
493
|
target: RelationshipTargetPattern;
|
|
473
494
|
};
|
|
495
|
+
/**
|
|
496
|
+
* An attachment query names a label, a file, or both — never neither —
|
|
497
|
+
* and both halves together match one association. See
|
|
498
|
+
* docs/spec/data-model.md § Filter.
|
|
499
|
+
*/
|
|
500
|
+
export type AttachmentFilter = {
|
|
501
|
+
label: string;
|
|
502
|
+
fileId?: FileId;
|
|
503
|
+
} | {
|
|
504
|
+
label?: string;
|
|
505
|
+
fileId: FileId;
|
|
506
|
+
};
|
|
474
507
|
export type DateRange = {
|
|
475
508
|
before?: Date;
|
|
476
509
|
after?: Date;
|
|
@@ -484,15 +517,19 @@ export type RecordFilter = {
|
|
|
484
517
|
* it works regardless of which versions happen to exist. Combined with
|
|
485
518
|
* typeId (if both given) as an intersection.
|
|
486
519
|
*/
|
|
487
|
-
baseId?:
|
|
520
|
+
baseId?: BaseId | BaseId[];
|
|
488
521
|
parentId?: RecordId | null;
|
|
489
522
|
appId?: AppId | AppId[];
|
|
490
|
-
|
|
491
|
-
|
|
523
|
+
/** Matches the record's author — never the latest actor, which isn't filterable. */
|
|
524
|
+
createdBy?: {
|
|
525
|
+
subjectId?: EntityId | EntityId[];
|
|
526
|
+
principalId?: EntityId | EntityId[];
|
|
527
|
+
};
|
|
492
528
|
createdAt?: DateRange;
|
|
493
529
|
updatedAt?: DateRange;
|
|
494
530
|
tags?: string[];
|
|
495
|
-
|
|
531
|
+
/** Records carrying a matching attachment association. See docs/spec/data-model.md § Filter. */
|
|
532
|
+
attachment?: AttachmentFilter;
|
|
496
533
|
/**
|
|
497
534
|
* Records carrying a matching relationship association. Either half may
|
|
498
535
|
* be given alone and each is a pattern: a bare `label` matches every
|
|
@@ -501,13 +538,18 @@ export type RecordFilter = {
|
|
|
501
538
|
* only local targets. See docs/spec/data-model.md § Filter.
|
|
502
539
|
*/
|
|
503
540
|
relatedTo?: RelatedToFilter;
|
|
504
|
-
|
|
541
|
+
/**
|
|
542
|
+
* Records that reference this file, through an attachment association
|
|
543
|
+
* or a top-level `file-ref` content field — the question GC and
|
|
544
|
+
* `deleteAttachment()` ask. See docs/spec/attachments.md § Deleting attachments.
|
|
545
|
+
*/
|
|
546
|
+
referencesFileId?: FileId;
|
|
505
547
|
/**
|
|
506
548
|
* Exact match on content fields, keyed by a dot-separated path
|
|
507
549
|
* (`'emails.value'`). An array anywhere along the path is matched
|
|
508
550
|
* element-wise, so the motivating question — which contact holds this
|
|
509
551
|
* email address — is one filter. A multi-segment key needs the
|
|
510
|
-
* `filter.content: 'path'` capability. POST /query only.
|
|
552
|
+
* `filter.content: 'path'` capability. POST /records/query only.
|
|
511
553
|
* See docs/spec/data-model.md § Filter.
|
|
512
554
|
*/
|
|
513
555
|
content?: Record<string, unknown>;
|
|
@@ -516,7 +558,7 @@ export type RecordFilter = {
|
|
|
516
558
|
* since a filter value matches what is there rather than whether
|
|
517
559
|
* anything is. Element-wise like `content`, so a path holds a value
|
|
518
560
|
* when at least one non-null value is reachable at it. Needs the
|
|
519
|
-
* filter.contentPresent capability. POST /query only.
|
|
561
|
+
* filter.contentPresent capability. POST /records/query only.
|
|
520
562
|
* See docs/spec/data-model.md § Filter.
|
|
521
563
|
*/
|
|
522
564
|
contentPresent?: string[];
|
|
@@ -600,19 +642,19 @@ export type ContentFilterReach =
|
|
|
600
642
|
/** Multi-segment paths too (`'emails.value'`). */
|
|
601
643
|
| 'path';
|
|
602
644
|
/**
|
|
603
|
-
* What
|
|
645
|
+
* What a Stack — and the adapter under it — honors, grouped by the query surface each entry gates:
|
|
604
646
|
* every flag under `filter`/`sort` is named for the query key it answers
|
|
605
647
|
* for, so the capability a query needs is derivable from the query rather
|
|
606
648
|
* than memorized. `limits` sits apart because a byte ceiling is not a
|
|
607
649
|
* feature to gate on but a number to pre-check against.
|
|
608
650
|
* See docs/spec/adapters.md § Adapter capabilities.
|
|
609
651
|
*/
|
|
610
|
-
export type
|
|
652
|
+
export type StackCapabilities = {
|
|
611
653
|
filter: {
|
|
612
654
|
/**
|
|
613
655
|
* Required `'path'` for local/in-process adapters; a wire adapter may
|
|
614
656
|
* report a shallower rung, driven by the server's discovery response.
|
|
615
|
-
* Stack.query() throws
|
|
657
|
+
* Stack.query() throws StackBadRequestError rather than silently widening.
|
|
616
658
|
*/
|
|
617
659
|
content: ContentFilterReach;
|
|
618
660
|
/**
|
|
@@ -666,24 +708,23 @@ export type AdapterCapabilities = {
|
|
|
666
708
|
contentBytes: number | null;
|
|
667
709
|
};
|
|
668
710
|
};
|
|
669
|
-
/** What a Stack can do, as seen by app and plugin code. */
|
|
670
|
-
export type StackFeatures = AdapterCapabilities;
|
|
671
711
|
/**
|
|
672
712
|
* A capability a query can be refused for, as its path into
|
|
673
|
-
*
|
|
713
|
+
* StackCapabilities — the same name the spec and a discovery response
|
|
674
714
|
* use, so an error says which key to look at. `limits` never appears: a
|
|
675
715
|
* ceiling is not something a query can lack.
|
|
676
716
|
*/
|
|
677
717
|
export type MissingCapability = 'filter.content' | 'filter.contentPresent' | 'filter.search' | 'sort.fields' | 'sort.contentField';
|
|
678
718
|
/**
|
|
679
719
|
* Opt-in optimistic-concurrency precondition, accepted by every mutation
|
|
680
|
-
* that bumps a record's version. On mismatch the
|
|
681
|
-
* StackVersionConflictError
|
|
682
|
-
*
|
|
683
|
-
* docs/spec/versioning.md § Optimistic
|
|
720
|
+
* that bumps a record's version. On mismatch the mutation throws
|
|
721
|
+
* StackVersionConflictError and changes nothing; omit to keep
|
|
722
|
+
* last-writer-wins. An adapter checks it atomically inside its write, never
|
|
723
|
+
* as a read-then-write. See docs/spec/versioning.md § Optimistic
|
|
724
|
+
* concurrency (`ifVersion`).
|
|
684
725
|
*/
|
|
685
|
-
export type
|
|
686
|
-
|
|
726
|
+
export type IfVersionOptions = {
|
|
727
|
+
ifVersion?: number;
|
|
687
728
|
};
|
|
688
729
|
/**
|
|
689
730
|
* Accepted by every mutating StackRecordAdapter method. The adapter
|
|
@@ -713,8 +754,7 @@ export type BumpVersionOptions = {
|
|
|
713
754
|
* See docs/spec/data-model.md § Authorship and attribution.
|
|
714
755
|
*/
|
|
715
756
|
export type ActorOptions = {
|
|
716
|
-
|
|
717
|
-
updatedVia?: EntityId;
|
|
757
|
+
actor?: Actor;
|
|
718
758
|
};
|
|
719
759
|
/**
|
|
720
760
|
* The part of a change that the record cannot report once the write has
|
|
@@ -780,24 +820,24 @@ export type JournalOptions = {
|
|
|
780
820
|
/** Window into a record's journal. Omitting both reads the whole log, oldest first. */
|
|
781
821
|
export type JournalQuery = {
|
|
782
822
|
/** Entries after this seq, exclusive. */
|
|
783
|
-
|
|
823
|
+
afterSeq?: number;
|
|
784
824
|
limit?: number;
|
|
785
825
|
};
|
|
786
826
|
/**
|
|
787
827
|
* The coarse branch every subscriber makes, closed at four values. A
|
|
788
828
|
* handler covering exactly these is complete, not merely adequate:
|
|
789
|
-
* `changed` is an upsert signal carrying
|
|
829
|
+
* `changed` is an upsert signal carrying nine distinct verbs.
|
|
790
830
|
* See docs/spec/events.md § The event shape.
|
|
791
831
|
*/
|
|
792
832
|
export type ChangeKind = 'created' | 'changed' | 'deleted' | 'purged';
|
|
793
833
|
/**
|
|
794
834
|
* The precise verb behind a ChangeKind, for consumers that distinguish a
|
|
795
|
-
* reshare from an edit.
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
* § Version history.
|
|
835
|
+
* reshare from an edit. Each is named after the call that produced it; see
|
|
836
|
+
* docs/spec/events.md § The event shape for the three that aren't a method
|
|
837
|
+
* name. `associate`, `dissociate`, `reshare`, `reparent`, `unlist` and
|
|
838
|
+
* `list` don't bump; see docs/spec/versioning.md § Version history.
|
|
799
839
|
*/
|
|
800
|
-
export type ChangeOp = 'create' | 'patch' | 'associate' | 'dissociate' | '
|
|
840
|
+
export type ChangeOp = 'create' | 'patch' | 'associate' | 'dissociate' | 'reshare' | 'migrate' | 'restore' | 'delete' | 'undelete' | 'purge'
|
|
801
841
|
/**
|
|
802
842
|
* Emitted even though the record's post-change state (`unlistedAt` now
|
|
803
843
|
* set) would otherwise be excluded by the same filter it announces —
|
|
@@ -821,11 +861,7 @@ export type ChangeOp = 'create' | 'patch' | 'associate' | 'dissociate' | 'permis
|
|
|
821
861
|
* `Stack`: absence is a fact of its own and never stands in for the
|
|
822
862
|
* author. See docs/spec/events.md § Attribution.
|
|
823
863
|
*/
|
|
824
|
-
export type ChangeActor = {
|
|
825
|
-
/** The subject the change is attributed to. */
|
|
826
|
-
entityId: EntityId;
|
|
827
|
-
/** The principal behind it, when a delegated app acted for the subject. */
|
|
828
|
-
principalId?: EntityId;
|
|
864
|
+
export type ChangeActor = Actor & {
|
|
829
865
|
/** Self-reported at create, never a trust input. See AppId. */
|
|
830
866
|
appId?: AppId;
|
|
831
867
|
};
|
|
@@ -851,14 +887,14 @@ export type RecordChange = {
|
|
|
851
887
|
typeId: TypeId;
|
|
852
888
|
/**
|
|
853
889
|
* The version this change produced; on `purged`, the version destroyed.
|
|
854
|
-
* Unchanged from the record's prior version on
|
|
855
|
-
*
|
|
890
|
+
* Unchanged from the record's prior version on every op that doesn't
|
|
891
|
+
* bump — see ChangeOp.
|
|
856
892
|
*/
|
|
857
893
|
version: number;
|
|
858
894
|
/**
|
|
859
895
|
* As persisted by this change; on `purged`, when the delete ran.
|
|
860
|
-
* Unchanged from the record's prior `updatedAt` on
|
|
861
|
-
*
|
|
896
|
+
* Unchanged from the record's prior `updatedAt` on every op that
|
|
897
|
+
* doesn't bump.
|
|
862
898
|
*/
|
|
863
899
|
updatedAt: Date;
|
|
864
900
|
parentId?: RecordId;
|
|
@@ -869,14 +905,14 @@ export type RecordChange = {
|
|
|
869
905
|
* `attachmentRecordId`. Present whenever `ops` includes `associate`, on
|
|
870
906
|
* the same "as of this change" convention as every other field here.
|
|
871
907
|
*/
|
|
872
|
-
associationsAdded?:
|
|
908
|
+
associationsAdded?: DataAssociation[];
|
|
873
909
|
/**
|
|
874
910
|
* Associations no longer present, identity only — kind and label, plus
|
|
875
911
|
* `fileId` for an attachment — present whenever `ops` includes
|
|
876
912
|
* `dissociate`. An attachment's `attachmentRecordId` is never repeated
|
|
877
913
|
* here, the same way a `purged` frame never carries what it destroyed.
|
|
878
914
|
*/
|
|
879
|
-
associationsRemoved?:
|
|
915
|
+
associationsRemoved?: DataAssociation[];
|
|
880
916
|
/**
|
|
881
917
|
* The record as of this change — present only when asked for and
|
|
882
918
|
* available, never on `purged`. Shared with every other subscriber on
|
|
@@ -888,19 +924,16 @@ export type RecordChange = {
|
|
|
888
924
|
* stack's own writes included — so a consumer holding a cursor keeps
|
|
889
925
|
* the last one that was present rather than the last change's.
|
|
890
926
|
*/
|
|
891
|
-
|
|
927
|
+
cursor?: string;
|
|
892
928
|
};
|
|
893
929
|
/**
|
|
894
930
|
* Applied by the emitter, exactly: a filtered subscription never receives
|
|
895
931
|
* an event outside its filter, so filtering again is redundant rather than
|
|
896
|
-
* defensive.
|
|
932
|
+
* defensive. Every key means what it means on `RecordFilter`, so one value
|
|
933
|
+
* can drive both `query()` and `subscribe()`. See docs/spec/events.md
|
|
934
|
+
* § Subscribing.
|
|
897
935
|
*/
|
|
898
|
-
export type ChangeFilter = {
|
|
899
|
-
/** Matched by baseId, as grants are, so a version bump orphans nothing. */
|
|
900
|
-
typeId?: TypeId | TypeId[];
|
|
901
|
-
parentId?: RecordId | null;
|
|
902
|
-
/** The record's author. Not the actor — see ChangeActor. */
|
|
903
|
-
entityId?: EntityId;
|
|
936
|
+
export type ChangeFilter = Pick<RecordFilter, 'typeId' | 'baseId' | 'parentId' | 'createdBy'> & {
|
|
904
937
|
kinds?: ChangeKind[];
|
|
905
938
|
};
|
|
906
939
|
/** Ends a subscription. Safe to call more than once. */
|
|
@@ -918,16 +951,16 @@ export type SubscribeOptions = {
|
|
|
918
951
|
*/
|
|
919
952
|
includeUnlisted?: boolean;
|
|
920
953
|
/**
|
|
921
|
-
* Resume from this cursor rather than the present — the last `
|
|
922
|
-
* was present on a delivered `RecordChange`. A relaying stack delivers
|
|
954
|
+
* Resume from this cursor rather than the present — the last `cursor`
|
|
955
|
+
* that was present on a delivered `RecordChange`. A relaying stack delivers
|
|
923
956
|
* its own local writes through the same handler and those carry none, so
|
|
924
|
-
* a consumer that stores every change's `
|
|
957
|
+
* a consumer that stores every change's `cursor` unconditionally erases its
|
|
925
958
|
* own cursor on its next write.
|
|
926
959
|
*
|
|
927
960
|
* Forwarded to the adapter as `SubscribeChangesOptions.since`, so it
|
|
928
961
|
* only means something where a relay exists: a stack with none has no
|
|
929
962
|
* third-party writes to have missed, and therefore no cursor it could
|
|
930
|
-
* ever have minted. Passing `since` there throws `
|
|
963
|
+
* ever have minted. Passing `since` there throws `StackBadRequestError`
|
|
931
964
|
* rather than silently starting from the present, which would let the
|
|
932
965
|
* caller believe it resumed when it did not. A cursor outside the
|
|
933
966
|
* framable charset is refused the same way, so a malformed one reports
|
|
@@ -973,7 +1006,7 @@ export type SubscribeChangesOptions = {
|
|
|
973
1006
|
* associations, versioning, type definitions, and stack identity.
|
|
974
1007
|
*/
|
|
975
1008
|
export interface StackRecordAdapter {
|
|
976
|
-
readonly capabilities:
|
|
1009
|
+
readonly capabilities: StackCapabilities;
|
|
977
1010
|
/** DID of the stack owner. Set during adapter initialization. */
|
|
978
1011
|
readonly ownerEntityId: EntityId;
|
|
979
1012
|
/**
|
|
@@ -1013,19 +1046,19 @@ export interface StackRecordAdapter {
|
|
|
1013
1046
|
* See docs/spec/data-model.md § Mutations and docs/spec/versioning.md
|
|
1014
1047
|
* § Version history.
|
|
1015
1048
|
*/
|
|
1016
|
-
mutateRecord(id: RecordId, changes:
|
|
1049
|
+
mutateRecord(id: RecordId, changes: RecordChangeSet, opts?: IfVersionOptions & SnapshotOptions & BumpVersionOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
|
|
1017
1050
|
/**
|
|
1018
1051
|
* Returns the record this call acted on: as it now stands after a soft
|
|
1019
|
-
* delete, and as it stood immediately before destruction after a
|
|
1020
|
-
*
|
|
1052
|
+
* delete, and as it stood immediately before destruction after a
|
|
1053
|
+
* purge — captured inside the same write, so nothing can observe or alter
|
|
1021
1054
|
* it in between. Null when there was no record to delete, which is the
|
|
1022
1055
|
* only case that mutates nothing.
|
|
1023
1056
|
*/
|
|
1024
1057
|
deleteRecord(id: RecordId, opts?: {
|
|
1025
|
-
|
|
1026
|
-
} &
|
|
1058
|
+
purge?: boolean;
|
|
1059
|
+
} & IfVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord | null>;
|
|
1027
1060
|
/** Reverse a soft delete. Returns the record as it now stands. */
|
|
1028
|
-
undeleteRecord(id: RecordId, opts?:
|
|
1061
|
+
undeleteRecord(id: RecordId, opts?: IfVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
|
|
1029
1062
|
queryRecords(query: StackQuery): Promise<QueryResult>;
|
|
1030
1063
|
/**
|
|
1031
1064
|
* Add an association. Never bumps `version`/`updatedAt` and never
|
|
@@ -1058,20 +1091,20 @@ export interface StackRecordAdapter {
|
|
|
1058
1091
|
* Bumps version internally; throws StackNotFoundError if the version
|
|
1059
1092
|
* doesn't exist.
|
|
1060
1093
|
*/
|
|
1061
|
-
restoreVersion(id: RecordId, version: number, opts?:
|
|
1094
|
+
restoreVersion(id: RecordId, version: number, opts?: IfVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
|
|
1062
1095
|
/**
|
|
1063
1096
|
* Commit a migration: write new content under a new typeId in one step.
|
|
1064
1097
|
* This is the only way a record's typeId changes after creation — used by
|
|
1065
1098
|
* Stack.commitMigration() and Stack.migrateAll(); Stack.mutate() never
|
|
1066
1099
|
* changes typeId as a side effect. Bumps version internally.
|
|
1067
1100
|
*/
|
|
1068
|
-
commitMigration(id: RecordId, toTypeId: TypeId, content: Record<string, unknown>, opts?:
|
|
1101
|
+
commitMigration(id: RecordId, toTypeId: TypeId, content: Record<string, unknown>, opts?: IfVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
|
|
1069
1102
|
saveType(type: StackType): Promise<void>;
|
|
1070
1103
|
getType(id: TypeId): Promise<StackType | null>;
|
|
1071
1104
|
listTypes(): Promise<StackType[]>;
|
|
1072
1105
|
/**
|
|
1073
|
-
* Atomically verify fileId is unreferenced, then
|
|
1074
|
-
* metadata records in the same adapter call, returning
|
|
1106
|
+
* Atomically verify fileId is unreferenced, then purge its
|
|
1107
|
+
* metadata records in the same adapter call, returning the deleted records.
|
|
1075
1108
|
* Throws StackConflictError if still referenced. Optional —
|
|
1076
1109
|
* Stack.deleteAttachment() has a non-atomic fallback. See
|
|
1077
1110
|
* docs/spec/attachments.md § Deleting attachments.
|
|
@@ -1088,12 +1121,12 @@ export interface StackRecordAdapter {
|
|
|
1088
1121
|
* storage, so locally there is no third party whose writes could have
|
|
1089
1122
|
* been missed. See docs/spec/events.md § Where events come from.
|
|
1090
1123
|
*/
|
|
1091
|
-
subscribeChanges?(opts: SubscribeChangesOptions, handler: (change: RecordChange) => void): Promise<
|
|
1124
|
+
subscribeChanges?(opts: SubscribeChangesOptions, handler: (change: RecordChange) => void): Promise<Unsubscribe>;
|
|
1092
1125
|
flush?(): Promise<void>;
|
|
1093
1126
|
close?(): Promise<void>;
|
|
1094
1127
|
}
|
|
1095
|
-
/** One stored blob, as reported by StackBlobAdapter.
|
|
1096
|
-
export type
|
|
1128
|
+
/** One stored blob, as reported by StackBlobAdapter.listBlobs(). */
|
|
1129
|
+
export type BlobInfo = {
|
|
1097
1130
|
fileId: FileId;
|
|
1098
1131
|
size: number;
|
|
1099
1132
|
/** When the blob was written. Used to apply a GC grace period to fresh, not-yet-associated uploads. */
|
|
@@ -1104,22 +1137,22 @@ export type BlobFileInfo = {
|
|
|
1104
1137
|
* attachment metadata lives on _attachment@1 records in the record adapter.
|
|
1105
1138
|
*/
|
|
1106
1139
|
export interface StackBlobAdapter {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1140
|
+
putBlob(data: Uint8Array): Promise<FileId>;
|
|
1141
|
+
getBlob(fileId: FileId): Promise<Uint8Array>;
|
|
1142
|
+
deleteBlob(fileId: FileId): Promise<void>;
|
|
1110
1143
|
/**
|
|
1111
1144
|
* Enumerate every blob currently in storage. Optional — without it,
|
|
1112
1145
|
* garbage collection can't find bare-bytes orphans (bytes with no
|
|
1113
1146
|
* metadata record at all). See docs/spec/attachments.md § Garbage
|
|
1114
1147
|
* collection.
|
|
1115
1148
|
*/
|
|
1116
|
-
|
|
1149
|
+
listBlobs?(): Promise<BlobInfo[]>;
|
|
1117
1150
|
flush?(): Promise<void>;
|
|
1118
1151
|
close?(): Promise<void>;
|
|
1119
1152
|
}
|
|
1120
1153
|
/**
|
|
1121
1154
|
* A complete adapter: record storage and blob storage combined.
|
|
1122
|
-
* Pass this to Stack.
|
|
1155
|
+
* Pass this to Stack.open(). Build one with combineAdapters() when you
|
|
1123
1156
|
* want different backends for records and blobs (e.g. SQLite + S3).
|
|
1124
1157
|
*/
|
|
1125
1158
|
export type StackAdapter = StackRecordAdapter & StackBlobAdapter & {
|
|
@@ -1130,22 +1163,16 @@ export type StackAdapter = StackRecordAdapter & StackBlobAdapter & {
|
|
|
1130
1163
|
* synthesized by combineAdapters(). A security boundary, not an
|
|
1131
1164
|
* efficiency shortcut — see docs/spec/adapters.md § Interface split.
|
|
1132
1165
|
*/
|
|
1133
|
-
putAttachmentWithMetadata?(data: Uint8Array,
|
|
1166
|
+
putAttachmentWithMetadata?(data: Uint8Array, opts: PutAttachmentOptions): Promise<StackRecord>;
|
|
1134
1167
|
};
|
|
1135
1168
|
/**
|
|
1136
|
-
* The two identities a token establishes
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
* stack.asEntity(session.principalId, { onBehalfOf: session.subjectId })
|
|
1141
|
-
*
|
|
1142
|
-
* See docs/spec/access-control.md § Delegation: principal and subject.
|
|
1169
|
+
* The two identities a token establishes — an Actor whose `principalId` is
|
|
1170
|
+
* always populated, equal to `subjectId` on an undelegated token, so a
|
|
1171
|
+
* server reading one never has to default it. Passes to `Stack.asActor()`
|
|
1172
|
+
* as is. See docs/spec/access-control.md § Delegation: principal and subject.
|
|
1143
1173
|
*/
|
|
1144
|
-
export type TokenSession = {
|
|
1145
|
-
/** Who authenticated — the DID that proved key possession. Governs authority. */
|
|
1174
|
+
export type TokenSession = Actor & {
|
|
1146
1175
|
principalId: EntityId;
|
|
1147
|
-
/** Who the principal acts for. Governs attribution. Equal to principalId unless delegated. */
|
|
1148
|
-
subjectId: EntityId;
|
|
1149
1176
|
};
|
|
1150
1177
|
export type TokenInfo = TokenSession & {
|
|
1151
1178
|
id: string;
|
|
@@ -1160,14 +1187,13 @@ export type TokenInfo = TokenSession & {
|
|
|
1160
1187
|
* handshake. Tokens SHOULD be stored outside the portable stack file.
|
|
1161
1188
|
* See docs/spec/wire-format.md § Authentication.
|
|
1162
1189
|
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
* it may act for. It is asserted by the owner out of band, which is safe
|
|
1190
|
+
* The actor's `subjectId`, where it differs from `principalId`, is the
|
|
1191
|
+
* delegation binding, and no handshake can establish it — proving key
|
|
1192
|
+
* possession proves the principal and nothing about whom it may act for. It is asserted by the owner out of band, which is safe
|
|
1166
1193
|
* because effective authority is the intersection of both parties' grants.
|
|
1167
1194
|
*/
|
|
1168
1195
|
export interface StackTokenStore {
|
|
1169
|
-
createToken(
|
|
1170
|
-
onBehalfOf?: EntityId;
|
|
1196
|
+
createToken(actor: Actor, opts?: {
|
|
1171
1197
|
label?: string;
|
|
1172
1198
|
expiresAt?: Date;
|
|
1173
1199
|
}): Promise<{
|