@haverstack/core 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/adapter-entry.d.ts +1 -1
  2. package/dist/adapter-entry.d.ts.map +1 -1
  3. package/dist/adapter-entry.js +1 -1
  4. package/dist/adapter-entry.js.map +1 -1
  5. package/dist/changes.d.ts +14 -0
  6. package/dist/changes.d.ts.map +1 -1
  7. package/dist/changes.js +35 -0
  8. package/dist/changes.js.map +1 -1
  9. package/dist/errors.d.ts +173 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +208 -0
  12. package/dist/errors.js.map +1 -0
  13. package/dist/grants.d.ts +84 -0
  14. package/dist/grants.d.ts.map +1 -0
  15. package/dist/grants.js +157 -0
  16. package/dist/grants.js.map +1 -0
  17. package/dist/id.d.ts +7 -5
  18. package/dist/id.d.ts.map +1 -1
  19. package/dist/id.js +17 -15
  20. package/dist/id.js.map +1 -1
  21. package/dist/identity-bindings.d.ts +13 -0
  22. package/dist/identity-bindings.d.ts.map +1 -0
  23. package/dist/identity-bindings.js +42 -0
  24. package/dist/identity-bindings.js.map +1 -0
  25. package/dist/index.d.ts +6 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/limits.d.ts +23 -0
  30. package/dist/limits.d.ts.map +1 -0
  31. package/dist/limits.js +35 -0
  32. package/dist/limits.js.map +1 -0
  33. package/dist/query-validation.d.ts +80 -0
  34. package/dist/query-validation.d.ts.map +1 -0
  35. package/dist/query-validation.js +223 -0
  36. package/dist/query-validation.js.map +1 -0
  37. package/dist/record-changes.d.ts +89 -0
  38. package/dist/record-changes.d.ts.map +1 -0
  39. package/dist/record-changes.js +201 -0
  40. package/dist/record-changes.js.map +1 -0
  41. package/dist/record-id.d.ts +70 -0
  42. package/dist/record-id.d.ts.map +1 -0
  43. package/dist/record-id.js +127 -0
  44. package/dist/record-id.js.map +1 -0
  45. package/dist/scoped-stack.d.ts +460 -0
  46. package/dist/scoped-stack.d.ts.map +1 -0
  47. package/dist/scoped-stack.js +1163 -0
  48. package/dist/scoped-stack.js.map +1 -0
  49. package/dist/stack-reads.d.ts +38 -0
  50. package/dist/stack-reads.d.ts.map +1 -0
  51. package/dist/stack-reads.js +79 -0
  52. package/dist/stack-reads.js.map +1 -0
  53. package/dist/stack.d.ts +37 -643
  54. package/dist/stack.d.ts.map +1 -1
  55. package/dist/stack.js +102 -2254
  56. package/dist/stack.js.map +1 -1
  57. package/dist/testing.d.ts.map +1 -1
  58. package/dist/testing.js +3 -1
  59. package/dist/testing.js.map +1 -1
  60. package/dist/types.d.ts +9 -2
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js +8 -0
  63. package/dist/types.js.map +1 -1
  64. package/dist/wire-entry.d.ts +2 -2
  65. package/dist/wire-entry.d.ts.map +1 -1
  66. package/dist/wire-entry.js +2 -2
  67. package/dist/wire-entry.js.map +1 -1
  68. package/dist/wire-record.js +1 -1
  69. package/dist/wire-record.js.map +1 -1
  70. package/dist/wire-request.d.ts.map +1 -1
  71. package/dist/wire-request.js +3 -2
  72. package/dist/wire-request.js.map +1 -1
  73. package/package.json +1 -1
package/dist/stack.d.ts CHANGED
@@ -13,19 +13,9 @@
13
13
  *
14
14
  * Apps should never talk to a StackAdapter directly.
15
15
  */
16
- import type { SchemaDriftViolation } from './schema.js';
17
- import { ChangeEmitter } from './changes.js';
18
- import type { ValidationError } from './validate.js';
19
- import type { StackRecord, StackType, TypeSchema, TypeId, StackAdapter, StackQuery, QuerySort, RecordFilter, QueryResult, Association, RelationshipTarget, Permission, Migration, RecordVersion, StackFeatures, GrantAction, AttachmentContent, FileId, EntityId, AppId, RecordId, TokenSession, ActorOptions, RecordChange, RecordChanges, SubscribeOptions, Unsubscribe } from './types.js';
20
- /**
21
- * Who a grant() / revoke() / listGrants() call targets: a specific entity
22
- * (DID), a `_group` Record's roster (by ID), or `null` for the default
23
- * grant / default-only listing. See docs/spec/access-control.md § Type-level
24
- * grants.
25
- */
26
- export type GrantTarget = EntityId | {
27
- groupId: RecordId;
28
- } | null;
16
+ import type { StackRecord, StackType, TypeSchema, TypeId, StackAdapter, StackQuery, QueryResult, Association, Permission, Migration, RecordVersion, StackFeatures, GrantAction, AttachmentContent, FileId, EntityId, AppId, TokenSession, ActorOptions, RecordChange, RecordChanges, SubscribeOptions, Unsubscribe } from './types.js';
17
+ import type { GrantTarget } from './grants.js';
18
+ import { ScopedStack } from './scoped-stack.js';
29
19
  export type CreateRecordOptions = {
30
20
  /**
31
21
  * Client-minted record ID. Must be 12 lowercase Crockford base-32
@@ -151,204 +141,6 @@ export type DeleteRecordOptions = IfVersionOptions & {
151
141
  export type DefineTypeOptions = {
152
142
  migratesFrom?: TypeId;
153
143
  };
154
- /**
155
- * The wire-protocol discriminator vocabulary, one code per Stack-domain
156
- * error class. Lives here rather than in @haverstack/wire-types because the
157
- * classes that carry these codes are defined here; wire-types re-exports it
158
- * as WireErrorCode. See docs/spec/wire-format.md § Wire error body.
159
- */
160
- export type StackErrorCode = 'bad_request' | 'permission' | 'not_found' | 'conflict' | 'version_conflict' | 'validation' | 'migration' | 'schema_drift' | 'payload_too_large' | 'timeout';
161
- /**
162
- * Root of the Stack error taxonomy. A single `instanceof StackError` answers
163
- * "is this a Stack-domain error or a bug?" — the question a server's error
164
- * middleware asks before serializing a wire body, and one a nine-arm
165
- * instanceof ladder answers only by exhaustion. Every subclass carries its
166
- * discriminator as an instance `code`, so serialization is a lookup rather
167
- * than a chain of class tests.
168
- *
169
- * Membership implies a wire mapping: every code has an entry in
170
- * WIRE_ERROR_STATUS. Errors with no wire representation (IdGenerationError,
171
- * InvalidDidError) deliberately stay outside this hierarchy.
172
- *
173
- * Subclassing adds no hierarchy beyond this root — notably
174
- * StackVersionConflictError is a sibling of StackConflictError, not a
175
- * subtype. See docs/spec/wire-format.md § Error responses.
176
- */
177
- export declare abstract class StackError extends Error {
178
- abstract readonly code: StackErrorCode;
179
- }
180
- export declare class StackValidationError extends StackError {
181
- readonly errors: ValidationError[];
182
- static readonly code: "validation";
183
- readonly code: "validation";
184
- constructor(errors: ValidationError[]);
185
- }
186
- export declare class StackMigrationError extends StackError {
187
- static readonly code: "migration";
188
- readonly code: "migration";
189
- constructor(message: string);
190
- }
191
- /** Thrown by ScopedStack when a requester lacks permission for the operation. */
192
- export declare class StackPermissionError extends StackError {
193
- static readonly code: "permission";
194
- readonly code: "permission";
195
- constructor(message?: string);
196
- }
197
- /** Thrown when a record (or specific version) does not exist. */
198
- export declare class StackNotFoundError extends StackError {
199
- static readonly code: "not_found";
200
- readonly code: "not_found";
201
- constructor(message: string);
202
- }
203
- /** Thrown when an operation cannot proceed due to a constraint violation (e.g. deleting an attachment that is still referenced). */
204
- export declare class StackConflictError extends StackError {
205
- static readonly code: "conflict";
206
- readonly code: "conflict";
207
- constructor(message: string);
208
- }
209
- /**
210
- * Thrown when an `ifVersion` precondition doesn't match a record's current
211
- * version. Deliberately not a StackConflictError subtype — the two have
212
- * different recovery stories and HTTP statuses (409 vs. 412). See
213
- * docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).
214
- */
215
- export declare class StackVersionConflictError extends StackError {
216
- readonly recordId: string;
217
- readonly expectedVersion: number;
218
- readonly actualVersion: number;
219
- static readonly code: "version_conflict";
220
- readonly code: "version_conflict";
221
- constructor(message: string, recordId: string, expectedVersion: number, actualVersion: number);
222
- }
223
- /**
224
- * Thrown when a request is structurally malformed — not a content-validation
225
- * failure, but input the adapter/server can't even interpret (e.g. an
226
- * undecodable pagination cursor, a malformed TypeId, search text the engine
227
- * cannot parse, or a typeId no definition exists for). Distinct from
228
- * StackValidationError, which means the request was well-formed but content
229
- * failed schema validation.
230
- *
231
- * Naming something absent belongs here rather than under `not_found`: a
232
- * request whose *type* is undefined never addressed a record, so answering
233
- * 404 would say a record was missing when none was asked for.
234
- */
235
- export declare class StackQueryError extends StackError {
236
- static readonly code: "bad_request";
237
- readonly code: "bad_request";
238
- constructor(message: string);
239
- }
240
- /**
241
- * Fail loud rather than silently widen: a filter the adapter can't honor
242
- * would otherwise be dropped, returning an unfiltered superset presented
243
- * as the filtered result. Shared by Stack.query() and
244
- * APIAdapter.queryRecords(). See docs/spec/data-model.md
245
- * § Capability-gated filters.
246
- */
247
- export declare function assertQueryCapabilities(filter: RecordFilter | undefined, capabilities: Pick<StackFeatures, 'filter'>): void;
248
- /**
249
- * Split a content filter key into path segments. Write-time validation
250
- * keeps stored field names free of the path metacharacters, but a filter
251
- * arrives from a request body and has made no such promise, so the same
252
- * rule is enforced here. See docs/spec/data-model.md § Content field names.
253
- */
254
- export declare function parseContentFilterKey(key: string): string[];
255
- /**
256
- * Reject a sort whose field or direction is outside the closed set the
257
- * types promise. `QuerySort` is typed `'asc' | 'desc'`, but a type is not a
258
- * runtime guard: a server mapping `?direction=` onto a query, or a
259
- * delegated app calling query(), supplies a raw string. A SQLite record
260
- * adapter interpolates the direction straight into `ORDER BY`, so an
261
- * unvalidated value there is a SQL-injection sink reachable from every
262
- * untrusted caller. Validating in the invariant layer — the same reason
263
- * emission and _config protection live here — means no adapter can forget
264
- * it. See docs/spec/data-model.md § Sorting and pagination.
265
- */
266
- export declare function assertValidSort(sort: QuerySort | undefined): void;
267
- /**
268
- * Fail loud rather than silently reorder: an adapter that can't honor the
269
- * requested sort would otherwise answer in some other order, which a
270
- * caller paging a bounded window has no way to notice. The companion to
271
- * assertQueryCapabilities(), split from it because a sort is not a filter
272
- * — see docs/spec/data-model.md § Capability-gated filters.
273
- */
274
- export declare function assertSortCapability(sort: QuerySort | undefined, capabilities: Pick<StackFeatures, 'sort'>): void;
275
- /**
276
- * Reject a relationship filter that names neither a label nor a target,
277
- * or whose target is malformed. `RelatedToFilter` promises one half is
278
- * always present; without the runtime check a filter decoded from a
279
- * request could arrive empty and match every record carrying any
280
- * relationship. See docs/spec/data-model.md § Filter.
281
- */
282
- export declare function assertValidRelatedTo(relatedTo: RecordFilter['relatedTo']): void;
283
- /**
284
- * Thrown when an attachment upload exceeds the adapter's declared
285
- * `limits.attachmentBytes` ceiling — checked client-side before any bytes
286
- * are sent; a server still enforces 413 authoritatively regardless. See
287
- * docs/spec/wire-format.md § Attachments.
288
- */
289
- export declare class StackPayloadTooLargeError extends StackError {
290
- static readonly code: "payload_too_large";
291
- readonly code: "payload_too_large";
292
- constructor(message: string);
293
- }
294
- /**
295
- * Thrown when a server abandons an operation for taking too long — in
296
- * practice a full-text search, the one query whose cost the sanitizers
297
- * bound the *grammar* of but not the execution of (see
298
- * docs/spec/data-model.md § Capability-gated filters).
299
- *
300
- * Never produced in-process: both SQLite engines run synchronously, so
301
- * there is nothing to interrupt from inside the call. It exists so a
302
- * server that bounds query time has a class to serialize, and so the app
303
- * catching it can tell "too expensive, narrow it and retry" from
304
- * StackQueryError's "malformed, don't bother retrying" — the distinction
305
- * that would be lost if a timeout reused `bad_request`.
306
- */
307
- export declare class StackTimeoutError extends StackError {
308
- static readonly code: "timeout";
309
- readonly code: "timeout";
310
- constructor(message: string);
311
- }
312
- /**
313
- * Thrown by defineType() when redefining an existing typeId with a schema
314
- * change beyond additive evolution. The remedy is a new version, never an
315
- * in-place redefinition. See docs/spec/data-model.md § Schema drift
316
- * detection.
317
- */
318
- export declare class StackSchemaDriftError extends StackError {
319
- readonly typeId: TypeId;
320
- readonly violations: SchemaDriftViolation[];
321
- static readonly code: "schema_drift";
322
- readonly code: "schema_drift";
323
- constructor(typeId: TypeId, violations: SchemaDriftViolation[]);
324
- }
325
- /**
326
- * Thrown when a Stack or ScopedStack is used after close(). Deliberately
327
- * outside the StackError taxonomy, alongside IdGenerationError and
328
- * InvalidDidError: a caller holding a closed client is a local programming
329
- * error with no wire representation — no server ever responds with it.
330
- * See docs/spec/adapters.md § Lifecycle.
331
- */
332
- export declare class StackClosedError extends Error {
333
- constructor(message?: string);
334
- }
335
- /**
336
- * Thrown when a scoped view is asked to observe a stack whose adapter
337
- * relays a remote feed. Outside the StackError taxonomy for the same
338
- * reason StackClosedError is: it reports a topology the caller assembled,
339
- * not a state a request can be in.
340
- *
341
- * A relayed frame is scoped by the authority that opened the feed, and a
342
- * narrower scope cannot re-derive that decision — a purge leaves no record
343
- * to check `canRead` against. Delivering anyway would break the promise
344
- * that a subscriber never sees what it may not read; delivering only local
345
- * writes would silently drop every change made elsewhere, which is the
346
- * failure that looks fine in testing. So it refuses.
347
- * See docs/spec/events.md § Permission scoping.
348
- */
349
- export declare class StackRelayScopeError extends Error {
350
- constructor(message: string);
351
- }
352
144
  /**
353
145
  * The app-facing record API, implemented by both Stack and ScopedStack.
354
146
  * Accept this type in plugin or extension code to remain adapter-agnostic
@@ -632,6 +424,18 @@ export declare class Stack implements StackClient {
632
424
  * Returns the record as it now stands — unchanged on a no-op.
633
425
  */
634
426
  dissociate(id: string, association: Association, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
427
+ /**
428
+ * The checks a *caller-named* destination owes, before the cycle walk:
429
+ * `parentId` is a real, well-formed record id. Format first, so a
430
+ * malformed one is a 400 naming the problem rather than a read that
431
+ * cannot match. Existence closes the gap between the owner path and a
432
+ * non-owner's, where canReadReferent() already refuses a parent that
433
+ * isn't there.
434
+ *
435
+ * restoreVersion() deliberately does not call this — see its own comment.
436
+ * See docs/spec/data-model.md § Reparenting.
437
+ */
438
+ private assertParentExists;
635
439
  /**
636
440
  * Refuse an edge that would make a record its own ancestor — asked at
637
441
  * every site that adds one: a change set naming `parentId`, a restore
@@ -653,18 +457,6 @@ export declare class Stack implements StackClient {
653
457
  * the same posture applied here.
654
458
  * See docs/spec/data-model.md § Reparenting.
655
459
  */
656
- /**
657
- * The checks a *caller-named* destination owes, before the cycle walk:
658
- * `parentId` is a real, well-formed record id. Format first, so a
659
- * malformed one is a 400 naming the problem rather than a read that
660
- * cannot match. Existence closes the gap between the owner path and a
661
- * non-owner's, where canReadReferent() already refuses a parent that
662
- * isn't there.
663
- *
664
- * restoreVersion() deliberately does not call this — see its own comment.
665
- * See docs/spec/data-model.md § Reparenting.
666
- */
667
- private assertParentExists;
668
460
  private assertNoParentCycle;
669
461
  /**
670
462
  * Soft-delete a record (default) or hard-delete it permanently, removing
@@ -811,25 +603,19 @@ export declare class Stack implements StackClient {
811
603
  */
812
604
  private checkAttachmentMimeTypeOnCreate;
813
605
  /**
814
- * filename is the only mutable field on an _attachment@1 record; fileId,
815
- * size, and mimeType are immutable (even a same-value mimeType rewrite
816
- * is refused). The correction flow is delete + re-upload. See
817
- * docs/spec/attachments.md § The `_attachment` record type.
818
- */
819
- private checkAttachmentImmutableFields;
820
- /**
821
- * The same immutability checkAttachmentImmutableFields() enforces, asked
822
- * value-wise instead of presence-wise: a migration replaces content
823
- * wholesale, so it necessarily re-sends `mimeType`, `fileId` and `size`
824
- * (all required) and a presence check would refuse every migration. Only
825
- * an actual change is a violation.
606
+ * filename is the only mutable field on an `_attachment@1` record; fileId,
607
+ * size and mimeType are immutable, and the correction flow is delete +
608
+ * re-upload. `violates` decides what counts as touching one, because the
609
+ * two write shapes disagree: a patch names only what it changes, while a
610
+ * migration replaces content wholesale and necessarily re-sends all three.
826
611
  *
827
612
  * Repointing `fileId` is the one that matters most: an `_attachment@1`
828
613
  * record naming a fileId is what canAccessFile()'s uploader clause reads,
829
614
  * so moving an existing record onto another file's hash is a route to
830
615
  * bytes the record's author never uploaded.
616
+ * See docs/spec/attachments.md § The `_attachment` record type.
831
617
  */
832
- private checkAttachmentImmutableOnMigrate;
618
+ private assertAttachmentImmutable;
833
619
  /**
834
620
  * `_config.entityId` defines stack ownership; neither patchContent() nor
835
621
  * restoreVersion() may change it. A conflict with stack integrity, not a
@@ -890,12 +676,6 @@ export declare class Stack implements StackClient {
890
676
  * not a failure. See docs/spec/attachments.md § Garbage collection.
891
677
  */
892
678
  collectAttachmentGarbage(opts?: CollectAttachmentGarbageOptions & ActorOptions): Promise<CollectAttachmentGarbageResult>;
893
- /**
894
- * Flush pending writes to the underlying storage. A no-op for adapters
895
- * that commit on every call (SQLite, the API adapter); meaningful for
896
- * ones that buffer, and for checkpointing a stack that stays open —
897
- * close() covers the teardown case on its own.
898
- */
899
679
  /**
900
680
  * Observe every change made through this Stack. Unscoped, so no
901
681
  * permission filter applies — a caller holding a `Stack` already reaches
@@ -924,6 +704,12 @@ export declare class Stack implements StackClient {
924
704
  * ScopedStack can refuse a scope it cannot honor; not an app-facing API.
925
705
  */
926
706
  get relaysChanges(): boolean;
707
+ /**
708
+ * Flush pending writes to the underlying storage. A no-op for adapters
709
+ * that commit on every call (SQLite, the API adapter); meaningful for
710
+ * ones that buffer, and for checkpointing a stack that stays open —
711
+ * close() covers the teardown case on its own.
712
+ */
927
713
  flush(): Promise<void>;
928
714
  /**
929
715
  * Flush, then release any resources the adapter holds (connections, file
@@ -993,6 +779,15 @@ export declare class Stack implements StackClient {
993
779
  * snapshotting work when the mismatch is already visible.
994
780
  */
995
781
  private checkIfVersion;
782
+ /**
783
+ * The options every version-bumping adapter write carries: the `ifVersion`
784
+ * precondition, the prior-state snapshot that has to land in the same
785
+ * atomic write, and who to attribute the change to. Taken together so a
786
+ * new mutating verb cannot quietly omit one — `updatedVia` most of all,
787
+ * whose absence reads as an undelegated write.
788
+ * See docs/spec/versioning.md § Version history.
789
+ */
790
+ private writeOptions;
996
791
  /**
997
792
  * Snapshot of a record's prior state, passed with the mutating adapter
998
793
  * call so snapshot and mutation land in one atomic write. `associations`
@@ -1003,405 +798,4 @@ export declare class Stack implements StackClient {
1003
798
  */
1004
799
  private buildVersionSnapshot;
1005
800
  }
1006
- /** Structural equality per target arm — what dissociate() matches on. */
1007
- export declare function targetEqual(a: RelationshipTarget, b: RelationshipTarget): boolean;
1008
- export declare class ScopedStack implements StackClient {
1009
- private readonly stack;
1010
- private readonly principalEntityId;
1011
- private readonly subjectEntityId;
1012
- private readonly idTimestampSkewMs;
1013
- private readonly adapter;
1014
- private readonly changes;
1015
- constructor(stack: Stack, principalEntityId: EntityId | null, subjectEntityId: EntityId | null, idTimestampSkewMs: number | null, adapter: StackAdapter, changes: ChangeEmitter);
1016
- get features(): StackFeatures;
1017
- private resolveRecord;
1018
- /** Whether a delegated app is acting for someone other than itself. */
1019
- private get delegated();
1020
- /**
1021
- * Who this request is, for stamping onto whatever it mutates. Attribution
1022
- * follows record-level authorship: the subject is the actor, and the
1023
- * principal is named beside it only when the two differ.
1024
- * See docs/spec/data-model.md § Authorship and attribution.
1025
- */
1026
- private get actor();
1027
- /**
1028
- * Whether unconditional owner authority applies — the owner acting as
1029
- * itself. The verbs that rest on it are irreversible or disclose the
1030
- * sharing graph, so delegation never carries one to a subject, whichever
1031
- * side the owner is on. Shared with the predicate a server applies to a
1032
- * session, which decides the same tier one step earlier. See
1033
- * docs/spec/access-control.md § Delegation: principal and subject.
1034
- */
1035
- private get ownerActingAlone();
1036
- private checkRead;
1037
- private checkWrite;
1038
- /**
1039
- * Whether `grantee` holds a _grant covering one of `actions` for the
1040
- * type's family (grants match by baseId, so a version bump never orphans
1041
- * one). -own actions additionally require record.entityId === grantee,
1042
- * unless `matchOwn` is false — on the principal side of a delegated
1043
- * request the suffix is read as the bare verb, since which records are
1044
- * reachable is the subject's business. `allowDefault` decides whether a
1045
- * grant naming nobody counts. Anonymous grantees always return false.
1046
- *
1047
- * Reached only through subjectAllows()/principalAllows(), which fix those
1048
- * two flags per side of the intersection. Call one of those instead.
1049
- * See docs/spec/access-control.md § Type-level grants.
1050
- */
1051
- private hasGrant;
1052
- /**
1053
- * The principal half of a delegated request's authority: does the app
1054
- * hold any grant permitting these verbs on this type at all. Bounds what
1055
- * the subject's own authority can reach through it, so a powerful app
1056
- * can never lend its reach to a weaker subject — nor the reverse.
1057
- * Vacuously true when there's no delegation, where the principal and
1058
- * subject checks would be the same question asked twice.
1059
- *
1060
- * Default grants don't count here. "Any authenticated entity" is about
1061
- * people who turn up, not software the owner installed — an app reaches
1062
- * only the types named to it, which is the whole of what containment
1063
- * promises.
1064
- *
1065
- * Group-targeted grants don't count here either, one step removed: a
1066
- * roster is editable by any of the group's admins, so authority reaching
1067
- * a principal through one would let someone other than the owner name an
1068
- * app to a type. See docs/spec/access-control.md § Type-level grants.
1069
- */
1070
- private principalAllows;
1071
- /**
1072
- * The subject half: which records are reachable, answered with `-own`
1073
- * matching and default grants both in force — the ordinary reading of a
1074
- * grant, since the subject is the entity a grant is written about.
1075
- *
1076
- * Paired with principalAllows() so that the two halves of the
1077
- * intersection are the only callers of hasGrant(): its flags differ per
1078
- * side and mean nothing on their own, so no call site sets them by hand.
1079
- */
1080
- private subjectAllows;
1081
- /**
1082
- * How to refuse a record this request addressed by ID. A requester who
1083
- * can read the record is told it exists and the verb was refused;
1084
- * everyone else is told what a missing ID is told, so no one learns an ID
1085
- * is live who could not have learned it by reading. `message` therefore
1086
- * only ever reaches someone holding the record already.
1087
- * See docs/spec/access-control.md § Errors and information exposure.
1088
- */
1089
- private denialFor;
1090
- private canRead;
1091
- private checkCreateGrant;
1092
- /**
1093
- * `_group` records are managed, not merely written: only the owner or an
1094
- * `admin` roster holder may mutate them — ordinary write permissions and
1095
- * grants don't apply. Asked of both identities under delegation, like
1096
- * a reshare. See docs/spec/identity.md § Group.
1097
- */
1098
- private isGroupManager;
1099
- /** Whether one identity, on its own, manages `record` — see isGroupManager(). */
1100
- private managesGroup;
1101
- /**
1102
- * Fetch a record the subject can reach and the principal holds `update` on
1103
- * (via permissions or an update grant), or throw. `mutating: false` marks
1104
- * the history readers, which borrow this gate without changing anything —
1105
- * the one way through it a `_grant` Record stays open to.
1106
- */
1107
- private requireUpdatable;
1108
- /**
1109
- * Refuse a mutation aimed at a soft-deleted Record. Asked only of a
1110
- * mutating caller, so the history readers borrowing this gate still work,
1111
- * and only after the authority decision, so "exists but deleted" is never
1112
- * a probe a stranger can run. See docs/spec/versioning.md § Mutations are
1113
- * refused, not applied to a tombstone.
1114
- */
1115
- private refuseIfDeleted;
1116
- /**
1117
- * Fetch a record the subject can reach and the principal holds `delete` on
1118
- * (via permissions or a delete grant), or throw.
1119
- */
1120
- private requireDeletable;
1121
- /**
1122
- * Whether this request may reference `recordId` (as a parentId or
1123
- * relationship target). Missing and unreadable both return false —
1124
- * indistinguishable, so this can't probe for a record's existence.
1125
- */
1126
- private canReadReferent;
1127
- /**
1128
- * Whether this request can read some record referencing `fileId` —
1129
- * shared by canAccessFile() and the non-owner _attachment@1 create()
1130
- * carve-out, which deliberately excludes the uploader clause. Walks
1131
- * every referencing record, short-circuiting on the first readable one.
1132
- * `_attachment@1` records are excluded from matching outright: the
1133
- * carve-out must be satisfied by some *other* record referencing the
1134
- * file, never by the requester's own prior metadata record for it —
1135
- * allowing that would let one successful guess unlock unlimited further
1136
- * metadata records for the same fileId, reintroducing the circularity
1137
- * the carve-out's uploader-clause exclusion closes.
1138
- * See docs/spec/attachments.md § Creating `_attachment@1` records directly.
1139
- */
1140
- private hasReadableReference;
1141
- /**
1142
- * Whether this request may reference or download `fileId` — the dual of
1143
- * getAttachment()'s access rule. Nonexistent and inaccessible are
1144
- * indistinguishable (both false), so no confirmation oracle for guessed
1145
- * hashes. See docs/spec/access-control.md § Reference-creation gating.
1146
- */
1147
- private canAccessFile;
1148
- /** Names of the type's top-level file-ref fields — the content-reference half of attachmentFileId matching. */
1149
- private fileRefFieldNames;
1150
- /**
1151
- * Gates file-ref content fields on file access, mirroring the
1152
- * attachment-association gate — a file-ref field conveys attachment
1153
- * access exactly like an `attachment` association. Only fields present
1154
- * in `content` are checked (a content patch is a merge patch; untouched fields
1155
- * carry no new reference).
1156
- */
1157
- private requireFileRefAccess;
1158
- /**
1159
- * Reference-creation gate for one association: `attachment` requires
1160
- * file access, and a `relationship` naming a record in this stack
1161
- * requires read access to it. `tag` is unchecked, and `_group` roster
1162
- * associations are gated by the stricter isGroupManager() instead.
1163
- *
1164
- * The other target arms are ungated because the gate's purpose —
1165
- * refusing a reference that would convey access to, or confirm the
1166
- * existence of, an unreadable record — has nothing to bite on: core
1167
- * never resolves them, so no access flows through one.
1168
- * See docs/spec/access-control.md § Reference-creation gating.
1169
- */
1170
- private requireAssociationAccess;
1171
- /**
1172
- * Create a record on behalf of the subject: create grant required,
1173
- * anonymous denied, entityId set to the subject, client IDs skew-checked,
1174
- * reference-creating options gated, and non-owner `_attachment@1`
1175
- * creation refused save one carve-out. A scoped create always stamps
1176
- * authorship — an absent entityId means an unscoped `Stack` wrote it.
1177
- * `createdAt`/`updatedAt` are refused to everyone but the owner acting
1178
- * alone — see the guard below and docs/spec/data-model.md § Record IDs.
1179
- * See also docs/spec/access-control.md and docs/spec/attachments.md.
1180
- */
1181
- create<T extends Record<string, unknown> = Record<string, unknown>>(typeId: TypeId, content: T, opts?: BackdatableCreateRecordOptions): Promise<StackRecord & {
1182
- content: T;
1183
- }>;
1184
- /**
1185
- * Whether this request may decide who else reaches a record — the rule
1186
- * a reshare enforces, asked at create time too so the reach it
1187
- * withholds can't be taken one step earlier while authoring. A delegated
1188
- * app is denied it: widening access is the one thing containment most
1189
- * needs to hold. Refused rather than silently ignored, so an app never
1190
- * believes it published something it didn't. Not `ownerActingAlone`:
1191
- * the record is the subject's own, so an owner principal grants it no
1192
- * reach the subject lacks.
1193
- * See docs/spec/access-control.md § Delegation: principal and subject.
1194
- */
1195
- private mayGrantAccess;
1196
- /**
1197
- * Whether one identity, on its own, may decide who else reaches `record`
1198
- * — the owner-or-creator rule a reshare enforces, asked of one
1199
- * side at a time. See
1200
- * docs/spec/access-control.md § Delegation: principal and subject.
1201
- */
1202
- private mayReshare;
1203
- /**
1204
- * A record this request may read, or null. Never throws
1205
- * StackPermissionError: an unreadable record answers exactly as a missing
1206
- * one does, so a caller learns only what it may read.
1207
- * See docs/spec/access-control.md § Errors and information exposure.
1208
- */
1209
- get(id: string, opts?: GetRecordOptions): Promise<StackRecord | null>;
1210
- /**
1211
- * Runs through this.query(), so canRead() still applies — this answers a
1212
- * question the caller asks on its own behalf. includeUnlisted is passed
1213
- * only when the request is the owner acting alone, per
1214
- * docs/spec/identity.md § DID bindings.
1215
- */
1216
- getEntityByDid(did: EntityId): Promise<StackRecord | null>;
1217
- getOwnerEntity(): Promise<StackRecord | null>;
1218
- /**
1219
- * Query records, filtered to those this request can read. Pages are
1220
- * filtered then refilled, so a page may slightly overshoot `limit` but
1221
- * never skips a record. Grants are prefetched once, cursor-walked to
1222
- * exhaustion.
1223
- */
1224
- query(query?: StackQuery): Promise<QueryResult>;
1225
- /**
1226
- * Apply a change set on behalf of the subject. Authority is resolved
1227
- * **per key** and every gate reads the record as it stands, never as the
1228
- * change set would leave it: a widened `permissions` never satisfies the
1229
- * read check on a `parentId` named in the same call, and a `_group`
1230
- * roster never satisfies the admin check that same call has to pass.
1231
- * One refused key refuses the whole call, so nothing is partially
1232
- * applied and no key is silently dropped.
1233
- * See docs/spec/access-control.md § Composing a change set.
1234
- */
1235
- mutate(id: string, changes: RecordChanges, opts?: IfVersionOptions): Promise<StackRecord>;
1236
- patchContent(id: string, patch: Record<string, unknown | null>, opts?: IfVersionOptions): Promise<StackRecord>;
1237
- /**
1238
- * The record, having established that this request may decide who else
1239
- * reaches it — the owner-or-creator rule, asked of both sides of a
1240
- * delegation, or a Group's admin rule for a `_group`. The gate the
1241
- * `permissions` and `unlisted` keys carry, which the write bit
1242
- * deliberately does not confer.
1243
- */
1244
- private requireReshareable;
1245
- /** The reshare decision alone, for a record already read and write-gated. */
1246
- private requireReshareOf;
1247
- /**
1248
- * Naming the software behind a key is the trust decision the `_app`
1249
- * registry exists to record, so both halves of that binding — `did` and
1250
- * `appId` — belong to the owner alone. Same reasoning that makes `_app`
1251
- * ungrantable, applied to the fields a lookup reads. Registering a card
1252
- * is already owner-only; without this, record-level `write` shared on a
1253
- * card would be a second way in: a card carrying no DID yet could be
1254
- * pointed at a write-holder's own key while keeping the name the owner
1255
- * gave it, or relabelled to claim another app's `appId`. `name` and
1256
- * `version` stay writable — they are display, not lookup.
1257
- *
1258
- * Owner *acting alone*, in both directions: a delegated app never holds
1259
- * it, and an owner principal doesn't lend it to a subject holding
1260
- * record-level `write` on a card — which would reopen the same route
1261
- * from the other side.
1262
- *
1263
- * `_entity` deliberately does not get this rule: naming people is what a
1264
- * contacts app does, so its cards stay writable by grant. Uniqueness and
1265
- * immutability still bind them. See docs/spec/identity.md § DID bindings.
1266
- */
1267
- private requireOwnerForAppIdentity;
1268
- /**
1269
- * A self-reported `appId` must agree with the `_app` card naming the
1270
- * principal's DID, where the owner registered one. `principalId` is
1271
- * verified, so letting the pair disagree would leave a verified principal
1272
- * claiming a name the owner gave different software — the cross-check the
1273
- * registry exists for, refused at the write instead of left to each reader.
1274
- * A principal with no card keeps `appId` as the bare self-report it is for
1275
- * every undelegated writer.
1276
- * See docs/spec/identity.md § Attribution and what can be trusted.
1277
- */
1278
- private requireAppIdMatchesPrincipal;
1279
- /**
1280
- * The owner's own DID is the one `_entity` binding a grantee may not claim.
1281
- * `ownerProfile` adopts whichever card holds it, so a card minted by
1282
- * someone else becomes the stack's own profile, and uniqueness then makes
1283
- * that permanent. Every other DID stays open to a contacts app, which is
1284
- * the reach `_entity` is grantable for.
1285
- * See docs/spec/identity.md § DID bindings.
1286
- */
1287
- private requireOwnerForOwnerDid;
1288
- /**
1289
- * A `_grant` Record *is* authority, so rewriting one is the escalation
1290
- * UNGRANTABLE_SYSTEM_TYPES refuses at evaluation, reached by editing an
1291
- * existing grant rather than minting a fresh one. `grant()` and `revoke()`
1292
- * live on `Stack`, never `StackClient`, so no scoped write is lost. Writes
1293
- * only: reading a grant Record and its history stays on the ordinary gate.
1294
- * See docs/spec/access-control.md § Type-level grants.
1295
- */
1296
- private requireOwnerForGrantRecord;
1297
- associate(id: string, association: Association, opts?: IfVersionOptions): Promise<StackRecord>;
1298
- dissociate(id: string, association: Association, opts?: IfVersionOptions): Promise<StackRecord>;
1299
- /**
1300
- * Hard delete is owner-only: it is irreversible and destroys version
1301
- * history, so neither the write bit nor delete-own/delete-any grants
1302
- * reach it, and delegation doesn't carry it either. Everyone else is
1303
- * limited to soft delete.
1304
- */
1305
- delete(id: string, opts?: DeleteRecordOptions): Promise<void>;
1306
- /**
1307
- * Reverse a soft delete. Gated the same as delete() — undelete is the
1308
- * inverse of soft delete, so granting one direction without the other
1309
- * would be backwards. Idempotent, per Stack.undelete().
1310
- */
1311
- undelete(id: string, opts?: IfVersionOptions): Promise<StackRecord>;
1312
- /**
1313
- * History is the mutation/recovery surface, not a read surface — gated
1314
- * like patchContent(), with snapshot `permissions` stripped for everyone but
1315
- * the owner acting alone — a snapshot's permissions are the stack's
1316
- * sharing graph, which delegation is not a route to. Reading history
1317
- * changes nothing, so it is the one path the `_grant` write fence leaves
1318
- * alone: seeing how a Record you can already read got that way is not
1319
- * the escalation that fence exists to stop, and losing it would leave a
1320
- * write-holder unable to audit the Record they hold.
1321
- * See docs/spec/versioning.md § History access.
1322
- */
1323
- getVersions(id: string): Promise<RecordVersion[]>;
1324
- /** See getVersions() — same mutate-surface gate, same permissions stripping. */
1325
- getVersion(id: string, version: number): Promise<RecordVersion | null>;
1326
- /**
1327
- * Re-runs the reference-creation checks against the snapshot, so a
1328
- * restore can't re-convey access to a file or record the subject can no
1329
- * longer reach today — the snapshot's `parentId` among them, gated
1330
- * exactly as a change set's `parentId` gates a destination named directly. Only the
1331
- * owner acting alone is exempt: under delegation the checks resolve
1332
- * against the subject, which is whose reach the restore would widen.
1333
- * See docs/spec/versioning.md § Restore semantics.
1334
- */
1335
- restoreVersion(id: string, version: number, opts?: IfVersionOptions): Promise<StackRecord>;
1336
- /**
1337
- * Commit a per-record migration — **the owner acting alone, only**.
1338
- *
1339
- * Migration is owner-driven by design: `migrateAll()`, the bulk path,
1340
- * lives on `Stack` and is deliberately absent from `StackClient`, the
1341
- * same way `grant()`/`revoke()` are. This is its per-record counterpart
1342
- * and carries the same restriction, rather than inventing a grant model
1343
- * that the bulk path deliberately doesn't have.
1344
- *
1345
- * The restriction is what makes the verb safe to expose at all. Migrate
1346
- * replaces `content` and `typeId` wholesale, so a grant-based version
1347
- * would have to re-derive every gate `create()` applies at the
1348
- * destination *and* every gate `mutate()` applies over the existing
1349
- * content, and would reopen each one it missed. The sharpest is the
1350
- * non-owner `_attachment@1` refusal create() carries: without it, a
1351
- * requester holding a create grant on `_attachment@1` and write access
1352
- * to any record they authored could migrate that record into the family
1353
- * naming any `fileId`, then read the bytes through canAccessFile()'s
1354
- * uploader clause — the exact escalation that carve-out exists to refuse
1355
- * (see docs/spec/attachments.md § Creating `_attachment@1` records
1356
- * directly). Ordinary write access to a record is not consent to move it
1357
- * between families.
1358
- *
1359
- * A server implementing `POST /records/:id/migrate` therefore serves it
1360
- * to the stack owner and answers 403 otherwise. See
1361
- * docs/spec/data-model.md § Type migrations.
1362
- */
1363
- commitMigration(id: string, toTypeId: TypeId, content: Record<string, unknown>, opts?: IfVersionOptions): Promise<StackRecord>;
1364
- /**
1365
- * Store bytes and create an _attachment@1 metadata record (create grant
1366
- * on `_attachment@1` required; anonymous denied), returning that record.
1367
- * Authorship and principal are stamped exactly as create() does.
1368
- */
1369
- putAttachment(data: Uint8Array, mimeType: string, filename?: string, appId?: AppId): Promise<StackRecord & {
1370
- content: AttachmentContent;
1371
- }>;
1372
- /**
1373
- * Download attachment bytes. Accessible to the owner, a reader of any
1374
- * referencing record, or the uploader pre-association — the same
1375
- * predicate as the reference-creation gate (canAccessFile).
1376
- */
1377
- getAttachment(fileId: string): Promise<Uint8Array>;
1378
- /**
1379
- * Delete an attachment. Only the stack owner may delete attachments.
1380
- * Delegates to Stack.deleteAttachment(), which enforces the "not referenced" check.
1381
- */
1382
- deleteAttachment(fileId: string): Promise<void>;
1383
- /**
1384
- * Sweep for unreferenced attachment bytes and delete them. Only the stack
1385
- * owner may run this. Delegates to Stack.collectAttachmentGarbage().
1386
- */
1387
- collectAttachmentGarbage(opts?: CollectAttachmentGarbageOptions): Promise<CollectAttachmentGarbageResult>;
1388
- /**
1389
- * Observe the changes this request may read. The predicate is canRead
1390
- * applied per event — the same one get() and query() answer with, so a
1391
- * feed can't disagree with them about what this session sees.
1392
- *
1393
- * A record the subscriber cannot read produces no event at all, rather
1394
- * than an empty or redacted one: the existence of a change is itself a
1395
- * disclosure, the same reasoning that keeps a count of the whole match
1396
- * off a query result. See docs/spec/events.md § Permission scoping.
1397
- */
1398
- subscribe(handler: (change: RecordChange) => void, opts?: SubscribeOptions): Promise<Unsubscribe>;
1399
- /**
1400
- * canRead for one event, through the subscription's own cache. Grants
1401
- * are prefetched once and refilled after an invalidation rather than
1402
- * re-queried per event, which would be a `_grant` scan for every change
1403
- * the stack makes.
1404
- */
1405
- private canReadCached;
1406
- }
1407
801
  //# sourceMappingURL=stack.d.ts.map