@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.
Files changed (80) hide show
  1. package/README.md +19 -15
  2. package/dist/access.d.ts +4 -6
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +7 -8
  5. package/dist/access.js.map +1 -1
  6. package/dist/adapter-entry.d.ts +7 -6
  7. package/dist/adapter-entry.d.ts.map +1 -1
  8. package/dist/adapter-entry.js +6 -5
  9. package/dist/adapter-entry.js.map +1 -1
  10. package/dist/attachment-download.d.ts +3 -1
  11. package/dist/attachment-download.d.ts.map +1 -1
  12. package/dist/attachment-download.js +1 -1
  13. package/dist/attachment-download.js.map +1 -1
  14. package/dist/changes.d.ts +22 -16
  15. package/dist/changes.d.ts.map +1 -1
  16. package/dist/changes.js +43 -32
  17. package/dist/changes.js.map +1 -1
  18. package/dist/combine.d.ts +1 -1
  19. package/dist/combine.js +6 -6
  20. package/dist/combine.js.map +1 -1
  21. package/dist/errors.d.ts +24 -12
  22. package/dist/errors.d.ts.map +1 -1
  23. package/dist/errors.js +32 -17
  24. package/dist/errors.js.map +1 -1
  25. package/dist/grants.d.ts +17 -25
  26. package/dist/grants.d.ts.map +1 -1
  27. package/dist/grants.js +27 -14
  28. package/dist/grants.js.map +1 -1
  29. package/dist/identity-bindings.js +2 -2
  30. package/dist/index.d.ts +12 -8
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +12 -6
  33. package/dist/index.js.map +1 -1
  34. package/dist/query-validation.d.ts +28 -11
  35. package/dist/query-validation.d.ts.map +1 -1
  36. package/dist/query-validation.js +95 -36
  37. package/dist/query-validation.js.map +1 -1
  38. package/dist/record-changes.d.ts +11 -10
  39. package/dist/record-changes.d.ts.map +1 -1
  40. package/dist/record-changes.js +33 -25
  41. package/dist/record-changes.js.map +1 -1
  42. package/dist/record-id.d.ts +3 -3
  43. package/dist/record-id.js +8 -8
  44. package/dist/record-id.js.map +1 -1
  45. package/dist/scoped-stack.d.ts +71 -34
  46. package/dist/scoped-stack.d.ts.map +1 -1
  47. package/dist/scoped-stack.js +107 -88
  48. package/dist/scoped-stack.js.map +1 -1
  49. package/dist/stack-reads.d.ts +2 -2
  50. package/dist/stack-reads.js +5 -5
  51. package/dist/stack-reads.js.map +1 -1
  52. package/dist/stack.d.ts +132 -161
  53. package/dist/stack.d.ts.map +1 -1
  54. package/dist/stack.js +287 -288
  55. package/dist/stack.js.map +1 -1
  56. package/dist/testing.d.ts +17 -17
  57. package/dist/testing.d.ts.map +1 -1
  58. package/dist/testing.js +46 -42
  59. package/dist/testing.js.map +1 -1
  60. package/dist/types.d.ts +169 -143
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js +2 -2
  63. package/dist/types.js.map +1 -1
  64. package/dist/wire-body.d.ts +71 -0
  65. package/dist/wire-body.d.ts.map +1 -0
  66. package/dist/wire-body.js +138 -0
  67. package/dist/wire-body.js.map +1 -0
  68. package/dist/wire-entry.d.ts +9 -8
  69. package/dist/wire-entry.d.ts.map +1 -1
  70. package/dist/wire-entry.js +13 -14
  71. package/dist/wire-entry.js.map +1 -1
  72. package/dist/wire-record.d.ts +4 -4
  73. package/dist/wire-record.d.ts.map +1 -1
  74. package/dist/wire-record.js +35 -22
  75. package/dist/wire-record.js.map +1 -1
  76. package/dist/wire-request.d.ts +37 -4
  77. package/dist/wire-request.d.ts.map +1 -1
  78. package/dist/wire-request.js +288 -79
  79. package/dist/wire-request.js.map +1 -1
  80. 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
- /** Opaque file identifier returned by putAttachment */
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 counterpart.
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 `attachmentFileId` filter and file access ask
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
- scope: 'record';
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
- scope: 'entity';
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
- scope: 'external';
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
- * scope. See docs/spec/access-control.md § Record-level permissions.
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: PermissionGrantee;
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
- * Who a permission reaches. `role` is required — `member` is the wider
99
- * set, `admin` the narrower, matching the roster labels where admin
100
- * implies member. See docs/spec/identity.md § Group.
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 PermissionGrantee = {
103
- scope: 'entity';
119
+ export type Grantee = {
120
+ kind: 'entity';
104
121
  entityId: EntityId;
105
122
  } | {
106
- scope: 'group';
123
+ kind: 'group';
107
124
  groupId: RecordId;
108
- role: 'member' | 'admin';
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 RecordChanges = {
186
+ export type RecordChangeSet = {
170
187
  contentPatch?: Record<string, unknown | null>;
171
- parentId?: string | null;
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 RECORD_CHANGE_KEYS: readonly ["contentPatch", "parentId", "permissions", "associations", "unlisted"];
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 authenticated principal behind the write, when it isn't the author
190
- * itself — a delegated app's own DID. Absent means the writer
191
- * authenticated as the author, so `appId` is an unverifiable self-report;
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
- updatedBy?: EntityId;
215
+ createdBy?: Actor;
203
216
  /**
204
- * The authenticated principal behind that mutation, when it isn't the
205
- * subject — `principalId` is the same fact about the create.
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
- updatedVia?: EntityId;
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.entityId` — not the
236
- * entity that performed the change this version records.
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
- entityId?: EntityId;
253
+ createdBy?: Actor;
240
254
  /** Who performed the mutation that produced this version. */
241
- updatedBy?: EntityId;
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: string;
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 — a Record's `entityId` resolves through it to the name
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 a record's `principalId` to this card,
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.grant()) can't drift from the type.
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
- scope: 'record';
471
+ kind: 'record';
451
472
  recordId: RecordId;
452
473
  stackUrl?: string;
453
474
  } | {
454
- scope: 'entity';
475
+ kind: 'entity';
455
476
  entityId: EntityId;
456
477
  } | {
457
- scope: 'external';
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 `hasAttachment`, which likewise have no match-any
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?: string | string[];
520
+ baseId?: BaseId | BaseId[];
488
521
  parentId?: RecordId | null;
489
522
  appId?: AppId | AppId[];
490
- entityId?: EntityId | EntityId[];
491
- principalId?: EntityId | EntityId[];
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
- hasAttachment?: string;
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
- attachmentFileId?: FileId;
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 an adapter honors, grouped by the query surface each entry gates:
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 AdapterCapabilities = {
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 StackQueryError rather than silently widening.
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
- * AdapterCapabilities — the same name the spec and a discovery response
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 adapter throws
681
- * StackVersionConflictError without applying anything. The check is atomic
682
- * inside the adapter's write, never a read-then-write. See
683
- * docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).
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 ExpectedVersionOptions = {
686
- expectedVersion?: number;
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
- updatedBy?: EntityId;
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
- sinceSeq?: number;
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 seven distinct verbs.
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. `associate`, `dissociate`, `reparent`, `unlist`
796
- * and `list` carry the record's version/updatedAt exactly as they stood
797
- * before the call; every other entry bumps. See docs/spec/versioning.md
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' | 'permissions' | 'migrate' | 'restore' | 'delete' | 'undelete' | 'hard-delete'
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 `associate`/`dissociate`,
855
- * which don't bump.
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 `associate`/
861
- * `dissociate`, which don't touch it.
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?: Association[];
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?: Association[];
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
- seq?: string;
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 `seq` that
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 `seq` unconditionally erases its
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 `StackQueryError`
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: AdapterCapabilities;
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: RecordChanges, opts?: ExpectedVersionOptions & SnapshotOptions & BumpVersionOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
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 hard
1020
- * one — captured inside the same write, so nothing can observe or alter
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
- hard?: boolean;
1026
- } & ExpectedVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord | null>;
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?: ExpectedVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
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?: ExpectedVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
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?: ExpectedVersionOptions & SnapshotOptions & ActorOptions & JournalOptions): Promise<StackRecord>;
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 hard-delete its
1074
- * metadata records in the same adapter call, returning their ids.
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<() => void>;
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.listFiles(). */
1096
- export type BlobFileInfo = {
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
- putAttachment(data: Uint8Array): Promise<FileId>;
1108
- getAttachment(fileId: FileId): Promise<Uint8Array>;
1109
- deleteAttachment(fileId: FileId): Promise<void>;
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
- listFiles?(): Promise<BlobFileInfo[]>;
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.create(). Build one with combineAdapters() when you
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, mimeType: string, filename?: string, appId?: AppId): Promise<StackRecord>;
1166
+ putAttachmentWithMetadata?(data: Uint8Array, opts: PutAttachmentOptions): Promise<StackRecord>;
1134
1167
  };
1135
1168
  /**
1136
- * The two identities a token establishes. Both are always populated: on an
1137
- * undelegated token they are the same DID, and spelling that out is what
1138
- * keeps the mapping onto asEntity() mechanical.
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
- * `onBehalfOf` is the delegation binding, and no handshake can establish
1164
- * it — proving key possession proves the principal and nothing about whom
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(principalId: EntityId, opts?: {
1170
- onBehalfOf?: EntityId;
1196
+ createToken(actor: Actor, opts?: {
1171
1197
  label?: string;
1172
1198
  expiresAt?: Date;
1173
1199
  }): Promise<{