@haverstack/core 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/README.md +124 -64
  2. package/dist/access.d.ts +43 -47
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +66 -101
  5. package/dist/access.js.map +1 -1
  6. package/dist/{combine.d.ts → adapter/combine.d.ts} +8 -1
  7. package/dist/adapter/combine.d.ts.map +1 -0
  8. package/dist/{combine.js → adapter/combine.js} +18 -3
  9. package/dist/adapter/combine.js.map +1 -0
  10. package/dist/adapter/index.d.ts +19 -0
  11. package/dist/adapter/index.d.ts.map +1 -0
  12. package/dist/adapter/index.js +16 -0
  13. package/dist/adapter/index.js.map +1 -0
  14. package/dist/adapter/reach.d.ts +14 -0
  15. package/dist/adapter/reach.d.ts.map +1 -0
  16. package/dist/adapter/reach.js +31 -0
  17. package/dist/adapter/reach.js.map +1 -0
  18. package/dist/{sort.d.ts → adapter/sort.d.ts} +20 -20
  19. package/dist/adapter/sort.d.ts.map +1 -0
  20. package/dist/{sort.js → adapter/sort.js} +37 -27
  21. package/dist/adapter/sort.js.map +1 -0
  22. package/dist/associations/identity.d.ts +69 -0
  23. package/dist/associations/identity.d.ts.map +1 -0
  24. package/dist/associations/identity.js +174 -0
  25. package/dist/associations/identity.js.map +1 -0
  26. package/dist/associations/validation.d.ts +83 -0
  27. package/dist/associations/validation.d.ts.map +1 -0
  28. package/dist/associations/validation.js +312 -0
  29. package/dist/associations/validation.js.map +1 -0
  30. package/dist/attachment-records.d.ts +48 -0
  31. package/dist/attachment-records.d.ts.map +1 -0
  32. package/dist/attachment-records.js +52 -0
  33. package/dist/attachment-records.js.map +1 -0
  34. package/dist/collection.d.ts +178 -0
  35. package/dist/collection.d.ts.map +1 -0
  36. package/dist/collection.js +397 -0
  37. package/dist/collection.js.map +1 -0
  38. package/dist/content-match.d.ts +25 -0
  39. package/dist/content-match.d.ts.map +1 -0
  40. package/dist/content-match.js +51 -0
  41. package/dist/content-match.js.map +1 -0
  42. package/dist/content-translation.d.ts +42 -0
  43. package/dist/content-translation.d.ts.map +1 -0
  44. package/dist/content-translation.js +108 -0
  45. package/dist/content-translation.js.map +1 -0
  46. package/dist/did/did.d.ts.map +1 -0
  47. package/dist/{did.js → did/did.js} +3 -4
  48. package/dist/did/did.js.map +1 -0
  49. package/dist/{did-entry.d.ts → did/index.d.ts} +1 -1
  50. package/dist/did/index.d.ts.map +1 -0
  51. package/dist/{did-entry.js → did/index.js} +3 -3
  52. package/dist/did/index.js.map +1 -0
  53. package/dist/errors.d.ts +54 -66
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +58 -62
  56. package/dist/errors.js.map +1 -1
  57. package/dist/grants.d.ts +20 -44
  58. package/dist/grants.d.ts.map +1 -1
  59. package/dist/grants.js +69 -151
  60. package/dist/grants.js.map +1 -1
  61. package/dist/id.d.ts +13 -31
  62. package/dist/id.d.ts.map +1 -1
  63. package/dist/id.js +13 -31
  64. package/dist/id.js.map +1 -1
  65. package/dist/index.d.ts +13 -9
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +9 -6
  68. package/dist/index.js.map +1 -1
  69. package/dist/install.d.ts +3 -6
  70. package/dist/install.d.ts.map +1 -1
  71. package/dist/install.js +6 -10
  72. package/dist/install.js.map +1 -1
  73. package/dist/lazy.d.ts +13 -0
  74. package/dist/lazy.d.ts.map +1 -0
  75. package/dist/lazy.js +27 -0
  76. package/dist/lazy.js.map +1 -0
  77. package/dist/merge.d.ts +1 -1
  78. package/dist/merge.d.ts.map +1 -1
  79. package/dist/merge.js +6 -12
  80. package/dist/merge.js.map +1 -1
  81. package/dist/migration-steps.d.ts +102 -0
  82. package/dist/migration-steps.d.ts.map +1 -0
  83. package/dist/migration-steps.js +293 -0
  84. package/dist/migration-steps.js.map +1 -0
  85. package/dist/query-validation.d.ts +68 -121
  86. package/dist/query-validation.d.ts.map +1 -1
  87. package/dist/query-validation.js +214 -380
  88. package/dist/query-validation.js.map +1 -1
  89. package/dist/record-changes.d.ts +11 -150
  90. package/dist/record-changes.d.ts.map +1 -1
  91. package/dist/record-changes.js +14 -305
  92. package/dist/record-changes.js.map +1 -1
  93. package/dist/schema.d.ts +18 -7
  94. package/dist/schema.d.ts.map +1 -1
  95. package/dist/schema.js +59 -30
  96. package/dist/schema.js.map +1 -1
  97. package/dist/scoped-stack/authority-snapshot.d.ts +37 -0
  98. package/dist/scoped-stack/authority-snapshot.d.ts.map +1 -0
  99. package/dist/scoped-stack/authority-snapshot.js +49 -0
  100. package/dist/scoped-stack/authority-snapshot.js.map +1 -0
  101. package/dist/scoped-stack/authority.d.ts +169 -0
  102. package/dist/scoped-stack/authority.d.ts.map +1 -0
  103. package/dist/scoped-stack/authority.js +408 -0
  104. package/dist/scoped-stack/authority.js.map +1 -0
  105. package/dist/scoped-stack/family-policy.d.ts +59 -0
  106. package/dist/scoped-stack/family-policy.d.ts.map +1 -0
  107. package/dist/scoped-stack/family-policy.js +68 -0
  108. package/dist/scoped-stack/family-policy.js.map +1 -0
  109. package/dist/scoped-stack/scoped-stack.d.ts +318 -0
  110. package/dist/scoped-stack/scoped-stack.d.ts.map +1 -0
  111. package/dist/scoped-stack/scoped-stack.js +695 -0
  112. package/dist/scoped-stack/scoped-stack.js.map +1 -0
  113. package/dist/scoped-stack/subscription.d.ts +36 -0
  114. package/dist/scoped-stack/subscription.d.ts.map +1 -0
  115. package/dist/scoped-stack/subscription.js +69 -0
  116. package/dist/scoped-stack/subscription.js.map +1 -0
  117. package/dist/stack/arguments.d.ts +27 -0
  118. package/dist/stack/arguments.d.ts.map +1 -0
  119. package/dist/stack/arguments.js +53 -0
  120. package/dist/stack/arguments.js.map +1 -0
  121. package/dist/stack/attachments.d.ts +24 -0
  122. package/dist/stack/attachments.d.ts.map +1 -0
  123. package/dist/stack/attachments.js +113 -0
  124. package/dist/stack/attachments.js.map +1 -0
  125. package/dist/{changes.d.ts → stack/changes.d.ts} +30 -72
  126. package/dist/stack/changes.d.ts.map +1 -0
  127. package/dist/{changes.js → stack/changes.js} +40 -74
  128. package/dist/stack/changes.js.map +1 -0
  129. package/dist/stack/client.d.ts +247 -0
  130. package/dist/stack/client.d.ts.map +1 -0
  131. package/dist/stack/client.js +9 -0
  132. package/dist/stack/client.js.map +1 -0
  133. package/dist/stack/identity-bindings.d.ts.map +1 -0
  134. package/dist/{identity-bindings.js → stack/identity-bindings.js} +5 -14
  135. package/dist/stack/identity-bindings.js.map +1 -0
  136. package/dist/stack/install-app.d.ts +22 -0
  137. package/dist/stack/install-app.d.ts.map +1 -0
  138. package/dist/stack/install-app.js +262 -0
  139. package/dist/stack/install-app.js.map +1 -0
  140. package/dist/stack/integrity.d.ts +94 -0
  141. package/dist/stack/integrity.d.ts.map +1 -0
  142. package/dist/stack/integrity.js +268 -0
  143. package/dist/stack/integrity.js.map +1 -0
  144. package/dist/{limits.d.ts → stack/limits.d.ts} +2 -5
  145. package/dist/stack/limits.d.ts.map +1 -0
  146. package/dist/{limits.js → stack/limits.js} +3 -6
  147. package/dist/stack/limits.js.map +1 -0
  148. package/dist/stack/migrations.d.ts +59 -0
  149. package/dist/stack/migrations.d.ts.map +1 -0
  150. package/dist/stack/migrations.js +141 -0
  151. package/dist/stack/migrations.js.map +1 -0
  152. package/dist/stack/reads.d.ts +69 -0
  153. package/dist/stack/reads.d.ts.map +1 -0
  154. package/dist/stack/reads.js +141 -0
  155. package/dist/stack/reads.js.map +1 -0
  156. package/dist/stack/record-id.d.ts +35 -0
  157. package/dist/stack/record-id.d.ts.map +1 -0
  158. package/dist/stack/record-id.js +82 -0
  159. package/dist/stack/record-id.js.map +1 -0
  160. package/dist/stack/stack.d.ts +444 -0
  161. package/dist/stack/stack.d.ts.map +1 -0
  162. package/dist/stack/stack.js +1339 -0
  163. package/dist/stack/stack.js.map +1 -0
  164. package/dist/stack/system-types.d.ts +16 -0
  165. package/dist/stack/system-types.d.ts.map +1 -0
  166. package/dist/stack/system-types.js +111 -0
  167. package/dist/stack/system-types.js.map +1 -0
  168. package/dist/stack/type-grants.d.ts +20 -0
  169. package/dist/stack/type-grants.d.ts.map +1 -0
  170. package/dist/stack/type-grants.js +69 -0
  171. package/dist/stack/type-grants.js.map +1 -0
  172. package/dist/stack/write-expectation.d.ts +79 -0
  173. package/dist/stack/write-expectation.d.ts.map +1 -0
  174. package/dist/stack/write-expectation.js +71 -0
  175. package/dist/stack/write-expectation.js.map +1 -0
  176. package/dist/stack/write-gate.d.ts +23 -0
  177. package/dist/stack/write-gate.d.ts.map +1 -0
  178. package/dist/stack/write-gate.js +17 -0
  179. package/dist/stack/write-gate.js.map +1 -0
  180. package/dist/testing.d.ts +44 -33
  181. package/dist/testing.d.ts.map +1 -1
  182. package/dist/testing.js +180 -176
  183. package/dist/testing.js.map +1 -1
  184. package/dist/type-handle.d.ts +67 -78
  185. package/dist/type-handle.d.ts.map +1 -1
  186. package/dist/type-handle.js +44 -111
  187. package/dist/type-handle.js.map +1 -1
  188. package/dist/types/adapter.d.ts +282 -0
  189. package/dist/types/adapter.d.ts.map +1 -0
  190. package/dist/types/adapter.js +5 -0
  191. package/dist/types/adapter.js.map +1 -0
  192. package/dist/types/associations.d.ts +140 -0
  193. package/dist/types/associations.d.ts.map +1 -0
  194. package/dist/types/associations.js +6 -0
  195. package/dist/types/associations.js.map +1 -0
  196. package/dist/types/capabilities.d.ts +89 -0
  197. package/dist/types/capabilities.d.ts.map +1 -0
  198. package/dist/types/capabilities.js +5 -0
  199. package/dist/types/capabilities.js.map +1 -0
  200. package/dist/types/events.d.ts +150 -0
  201. package/dist/types/events.d.ts.map +1 -0
  202. package/dist/types/events.js +5 -0
  203. package/dist/types/events.js.map +1 -0
  204. package/dist/types/ids.d.ts +43 -0
  205. package/dist/types/ids.d.ts.map +1 -0
  206. package/dist/types/ids.js +5 -0
  207. package/dist/types/ids.js.map +1 -0
  208. package/dist/types/index.d.ts +19 -0
  209. package/dist/types/index.d.ts.map +1 -0
  210. package/dist/types/index.js +19 -0
  211. package/dist/types/index.js.map +1 -0
  212. package/dist/types/journal.d.ts +47 -0
  213. package/dist/types/journal.d.ts.map +1 -0
  214. package/dist/types/journal.js +5 -0
  215. package/dist/types/journal.js.map +1 -0
  216. package/dist/types/query.d.ts +185 -0
  217. package/dist/types/query.d.ts.map +1 -0
  218. package/dist/types/query.js +10 -0
  219. package/dist/types/query.js.map +1 -0
  220. package/dist/types/records.d.ts +76 -0
  221. package/dist/types/records.d.ts.map +1 -0
  222. package/dist/types/records.js +5 -0
  223. package/dist/types/records.js.map +1 -0
  224. package/dist/types/schema.d.ts +74 -0
  225. package/dist/types/schema.d.ts.map +1 -0
  226. package/dist/types/schema.js +5 -0
  227. package/dist/types/schema.js.map +1 -0
  228. package/dist/types/system.d.ts +161 -0
  229. package/dist/types/system.d.ts.map +1 -0
  230. package/dist/types/system.js +32 -0
  231. package/dist/types/system.js.map +1 -0
  232. package/dist/types/tokens.d.ts +39 -0
  233. package/dist/types/tokens.d.ts.map +1 -0
  234. package/dist/types/tokens.js +5 -0
  235. package/dist/types/tokens.js.map +1 -0
  236. package/dist/validate.d.ts +31 -70
  237. package/dist/validate.d.ts.map +1 -1
  238. package/dist/validate.js +52 -92
  239. package/dist/validate.js.map +1 -1
  240. package/dist/{attachment-download.d.ts → wire/attachment-download.d.ts} +0 -57
  241. package/dist/wire/attachment-download.d.ts.map +1 -0
  242. package/dist/{attachment-download.js → wire/attachment-download.js} +0 -61
  243. package/dist/wire/attachment-download.js.map +1 -0
  244. package/dist/{auth.d.ts → wire/auth.d.ts} +5 -8
  245. package/dist/wire/auth.d.ts.map +1 -0
  246. package/dist/{auth.js → wire/auth.js} +5 -8
  247. package/dist/wire/auth.js.map +1 -0
  248. package/dist/{wire-body.d.ts → wire/body.d.ts} +35 -12
  249. package/dist/wire/body.d.ts.map +1 -0
  250. package/dist/{wire-body.js → wire/body.js} +74 -13
  251. package/dist/wire/body.js.map +1 -0
  252. package/dist/{wire-entry.d.ts → wire/index.d.ts} +12 -12
  253. package/dist/wire/index.d.ts.map +1 -0
  254. package/dist/{wire-entry.js → wire/index.js} +13 -17
  255. package/dist/wire/index.js.map +1 -0
  256. package/dist/wire/record.d.ts +31 -0
  257. package/dist/wire/record.d.ts.map +1 -0
  258. package/dist/{wire-record.js → wire/record.js} +7 -67
  259. package/dist/wire/record.js.map +1 -0
  260. package/dist/{wire-request.d.ts → wire/request.d.ts} +32 -49
  261. package/dist/wire/request.d.ts.map +1 -0
  262. package/dist/{wire-request.js → wire/request.js} +113 -83
  263. package/dist/wire/request.js.map +1 -0
  264. package/package.json +7 -7
  265. package/dist/adapter-entry.d.ts +0 -16
  266. package/dist/adapter-entry.d.ts.map +0 -1
  267. package/dist/adapter-entry.js +0 -14
  268. package/dist/adapter-entry.js.map +0 -1
  269. package/dist/attachment-download.d.ts.map +0 -1
  270. package/dist/attachment-download.js.map +0 -1
  271. package/dist/auth.d.ts.map +0 -1
  272. package/dist/auth.js.map +0 -1
  273. package/dist/changes.d.ts.map +0 -1
  274. package/dist/changes.js.map +0 -1
  275. package/dist/combine.d.ts.map +0 -1
  276. package/dist/combine.js.map +0 -1
  277. package/dist/did-entry.d.ts.map +0 -1
  278. package/dist/did-entry.js.map +0 -1
  279. package/dist/did.d.ts.map +0 -1
  280. package/dist/did.js.map +0 -1
  281. package/dist/identity-bindings.d.ts.map +0 -1
  282. package/dist/identity-bindings.js.map +0 -1
  283. package/dist/limits.d.ts.map +0 -1
  284. package/dist/limits.js.map +0 -1
  285. package/dist/record-id.d.ts +0 -70
  286. package/dist/record-id.d.ts.map +0 -1
  287. package/dist/record-id.js +0 -127
  288. package/dist/record-id.js.map +0 -1
  289. package/dist/scoped-stack.d.ts +0 -549
  290. package/dist/scoped-stack.d.ts.map +0 -1
  291. package/dist/scoped-stack.js +0 -1301
  292. package/dist/scoped-stack.js.map +0 -1
  293. package/dist/sort.d.ts.map +0 -1
  294. package/dist/sort.js.map +0 -1
  295. package/dist/stack-reads.d.ts +0 -38
  296. package/dist/stack-reads.d.ts.map +0 -1
  297. package/dist/stack-reads.js +0 -79
  298. package/dist/stack-reads.js.map +0 -1
  299. package/dist/stack.d.ts +0 -1032
  300. package/dist/stack.d.ts.map +0 -1
  301. package/dist/stack.js +0 -2337
  302. package/dist/stack.js.map +0 -1
  303. package/dist/types.d.ts +0 -1278
  304. package/dist/types.d.ts.map +0 -1
  305. package/dist/types.js +0 -51
  306. package/dist/types.js.map +0 -1
  307. package/dist/wire-body.d.ts.map +0 -1
  308. package/dist/wire-body.js.map +0 -1
  309. package/dist/wire-entry.d.ts.map +0 -1
  310. package/dist/wire-entry.js.map +0 -1
  311. package/dist/wire-record.d.ts +0 -50
  312. package/dist/wire-record.d.ts.map +0 -1
  313. package/dist/wire-record.js.map +0 -1
  314. package/dist/wire-request.d.ts.map +0 -1
  315. package/dist/wire-request.js.map +0 -1
  316. /package/dist/{did.d.ts → did/did.d.ts} +0 -0
  317. /package/dist/{identity-bindings.d.ts → stack/identity-bindings.d.ts} +0 -0
package/dist/stack.d.ts DELETED
@@ -1,1032 +0,0 @@
1
- /**
2
- * Stack — Core Stack Class
3
- * -------------------------------------------------------
4
- * The Stack class is the primary interface for apps. It sits
5
- * on top of a StackAdapter and adds:
6
- *
7
- * - ID generation
8
- * - Type definition and schema hashing
9
- * - Content validation on write
10
- * - Migration registry and explicit, owner-driven migrateAll()
11
- * - Version snapshotting on update
12
- * - Soft delete and purge
13
- *
14
- * Apps should never talk to a StackAdapter directly.
15
- */
16
- import type { StackRecord, StackType, TypeSchema, TypeId, BaseId, StackAdapter, StackQuery, QueryResult, AssociationEdit, AuthorityAssociation, DataAssociation, Migration, RecordVersion, StackCapabilities, IfVersionOptions, GrantContent, TypeGrant, PutAttachmentOptions, AttachmentContent, FileId, EntityId, AppId, InstallContent, RecordId, Actor, ActorOptions, RecordChange, RecordChangeSet, SubscribeOptions, Unsubscribe, JournalQuery, VersionsQuery, RecordJournalEntry } from './types.js';
17
- import type { GrantQuery } from './grants.js';
18
- import type { AppManifest, InstallPlan } from './install.js';
19
- import { ScopedStack } from './scoped-stack.js';
20
- import type { ContentOf, PatchOf, ReadonlyTypeSchema, TypedChange, TypedChangeSet, TypedQuery, TypedSubscribeOptions, TypedRecord, TypeHandle } from './type-handle.js';
21
- /**
22
- * An Actor in the one form `Stack` stores: a `principalId` equal to the
23
- * subject is dropped, so "acted as itself" has a single spelling, and an
24
- * empty id is refused rather than stored as a name for nobody.
25
- * See docs/spec/data-model.md § Actor.
26
- */
27
- export declare function normalizeActor(actor: Actor | undefined): Actor | undefined;
28
- export type CreateRecordOptions = {
29
- /**
30
- * Client-minted record ID. Must be 12 lowercase Crockford base-32
31
- * characters and may not use the reserved `_` prefix. Omit to let the
32
- * library generate one. See Stack.create() and ScopedStack.create()
33
- * for the validation each applies.
34
- */
35
- id?: RecordId;
36
- parentId?: RecordId;
37
- /**
38
- * The author. ScopedStack sets this from its own identities; callers of
39
- * plain Stack supply it only when reconstructing an attributed write.
40
- * See StackRecord.createdBy.
41
- */
42
- createdBy?: Actor;
43
- /** Reverse-DNS identifier of the writing software — see AppId. */
44
- appId?: AppId;
45
- permissions?: AuthorityAssociation[];
46
- associations?: DataAssociation[];
47
- /**
48
- * Create the record already unlisted, so the create event itself is
49
- * withheld from the feed — there is no window where the record exists
50
- * and is listed before a mutation catches up. See
51
- * docs/spec/unlisted.md.
52
- */
53
- unlisted?: boolean;
54
- };
55
- /**
56
- * CreateRecordOptions extended with createdAt/updatedAt, for backdating a
57
- * record's clock fields on import. Unscoped Stack.create() accepts them
58
- * unconditionally; ScopedStack.create() only from the stack owner acting
59
- * alone, since a grantee or a delegated app could otherwise forge a sort
60
- * position the same way a raw `id` could.
61
- * See docs/spec/data-model.md § Record IDs.
62
- */
63
- export type BackdatableCreateRecordOptions = CreateRecordOptions & {
64
- /**
65
- * The record's creation time. When `id` is also supplied, its embedded
66
- * timestamp must agree with this within `idTimestampSkewMs` (default 24h;
67
- * see StackOptions.idTimestampSkewMs) or the create throws
68
- * StackValidationError. Omit `id` to have it derived from this timestamp
69
- * instead. Defaults to now.
70
- */
71
- createdAt?: Date;
72
- /**
73
- * The record's last-modified time. Defaults to `createdAt` (or now, if
74
- * `createdAt` is omitted too) — never to the actual current time — so a
75
- * plain import doesn't fabricate a fake edit. Must not precede
76
- * `createdAt`.
77
- */
78
- updatedAt?: Date;
79
- };
80
- export type StackOptions = {
81
- /**
82
- * Ensures the owner's own `_entity` profile record exists, creating it on
83
- * first run. Idempotent — safe to pass on every open. See
84
- * docs/spec/identity.md § Entity.
85
- */
86
- ownerProfile?: {
87
- name: string;
88
- handle?: string;
89
- };
90
- /**
91
- * Clock-skew tolerance (ms) for two timestamp-prefix checks: the one
92
- * ScopedStack.create() runs on a non-backdated create's client-supplied
93
- * `id` against the current time, and the one Stack.create() runs between
94
- * an explicit `id` and an explicit `createdAt` when both are supplied —
95
- * reached directly when unscoped, or via ScopedStack.create() when the
96
- * requester is the owner acting alone. Default: 24 hours; null disables
97
- * both. See docs/spec/data-model.md § Record IDs.
98
- */
99
- idTimestampSkewMs?: number | null;
100
- };
101
- export type CollectAttachmentGarbageOptions = {
102
- /**
103
- * How recent an unreferenced file must be to survive collection, covering
104
- * the upload-then-associate window. Default: 24 hours. Pass 0 to collect
105
- * anything unreferenced right now.
106
- */
107
- graceMs?: number;
108
- /** Compute what would be deleted without deleting anything. Default: false. */
109
- dryRun?: boolean;
110
- };
111
- export type CollectAttachmentGarbageResult = {
112
- /** The files collected — or, on a dry run, the ones that would be. */
113
- deletedFileIds: FileId[];
114
- reclaimedBytes: number;
115
- };
116
- export type GetRecordOptions = {
117
- /**
118
- * Records are returned exactly as stored by default. Pass "latest" to
119
- * apply the registered migration chain in memory — never written back.
120
- * See docs/spec/data-model.md § Type migrations.
121
- */
122
- presentAt?: 'stored' | 'latest';
123
- /**
124
- * Soft-deleted records are hidden by default, as query() hides them: a
125
- * tombstone reads as `null`. Pass true to read it back.
126
- * See docs/spec/versioning.md § Deletion.
127
- */
128
- includeDeleted?: boolean;
129
- };
130
- export type DeleteRecordOptions = IfVersionOptions & {
131
- /** If true, permanently remove the record and all its history. Default: false */
132
- purge?: boolean;
133
- };
134
- /**
135
- * What a delete reports back. Information, not action: a purge never
136
- * touches the blob store, and nothing here is deleted by having been
137
- * named. See docs/spec/attachments.md § A purge strands the bytes it
138
- * referenced.
139
- */
140
- export type DeleteResult = {
141
- /**
142
- * The files the purged record referenced — its attachment associations
143
- * and its `file-ref` content fields, deduplicated. The purge removes the
144
- * only rows naming them, so this is the caller's one chance to hold the
145
- * argument `deleteAttachment()` takes. Empty on a soft delete, which
146
- * strands nothing: a tombstone's references stand.
147
- */
148
- referencedFileIds: FileId[];
149
- };
150
- /**
151
- * What deleteAndReturn() reports back — DeleteResult plus the record itself,
152
- * captured inside the same atomic operation as the destroy rather than a
153
- * read beforehand. See Stack.deleteAndReturn().
154
- */
155
- export type DeleteAndReturnResult = DeleteResult & {
156
- /**
157
- * The record as it stood at the moment of deletion: immediately before
158
- * destruction for a purge, or the resulting tombstone for a soft
159
- * delete. Null only for the purge no-op — there was nothing to
160
- * delete, and nothing to report; every other outcome throws instead of
161
- * returning null (see Stack.delete()'s own no-op cases).
162
- */
163
- record: StackRecord | null;
164
- };
165
- /** Options for Stack.migrateAll(). */
166
- export type MigrateAllOptions = {
167
- /**
168
- * `'all'` (the default) sweeps every record of the family, deleted and
169
- * unlisted included. `'listed'` sweeps only what a non-owner can
170
- * enumerate and read whole. See docs/spec/apps.md § Migrating an
171
- * installed app's types.
172
- */
173
- sweep?: 'all' | 'listed';
174
- };
175
- /** The argument to Stack.defineType(). */
176
- export type DefineTypeOptions = {
177
- id: TypeId;
178
- name: string;
179
- /** A handle's `schema` is accepted as written, readonly members and all. */
180
- schema: ReadonlyTypeSchema;
181
- migratesFrom?: TypeId;
182
- };
183
- /**
184
- * The app-facing record API, implemented by both Stack and ScopedStack.
185
- * Accept this type in plugin or extension code to remain adapter-agnostic
186
- * and work equally well with a full Stack or a permission-scoped view.
187
- */
188
- export interface StackClient {
189
- readonly capabilities: StackCapabilities;
190
- create<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, content: ContentOf<S>, opts?: CreateRecordOptions): Promise<TypedRecord<S>>;
191
- create<T extends Record<string, unknown> = Record<string, unknown>>(typeId: TypeId, content: T, opts?: CreateRecordOptions): Promise<StackRecord & {
192
- content: T;
193
- }>;
194
- /**
195
- * Typed read: the record at `presentAt: 'latest'`, checked to be exactly
196
- * the handle's Type with every enum field holding a value the handle
197
- * lists, else it throws. Live records only — a tombstone reads as `null`,
198
- * and there is no `includeDeleted`.
199
- * See docs/spec/data-model.md § Type handles.
200
- */
201
- get<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, id: RecordId): Promise<TypedRecord<S> | null>;
202
- get(id: RecordId, opts?: GetRecordOptions): Promise<StackRecord | null>;
203
- /** Typed read of the handle's whole family — see the typed `get()`. */
204
- query<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, query?: TypedQuery): Promise<{
205
- records: TypedRecord<S>[];
206
- cursor: string | null;
207
- }>;
208
- query(query?: StackQuery): Promise<QueryResult>;
209
- /**
210
- * Resolve a DID to its `_entity` card — family-wide and soft-deleted
211
- * inclusive, per docs/spec/identity.md § DID bindings. No caching.
212
- *
213
- * Under `ScopedStack`, `null` covers missing, unreadable, and (unless the
214
- * owner is acting alone) unlisted — never evidence the card is absent.
215
- */
216
- getEntityByDid(did: EntityId): Promise<StackRecord | null>;
217
- /** getEntityByDid() for the one DID every stack reserves: its own owner. */
218
- getOwnerEntity(): Promise<StackRecord | null>;
219
- /**
220
- * Apply a change set: any combination of content patch, `parentId`,
221
- * `permissions`, `associations` and `unlisted`, in one atomic write —
222
- * producing one version where it names `contentPatch`, and none where it
223
- * doesn't. Keys are read for presence, so `unlisted: false`
224
- * and `parentId: null` are changes; a change set naming no key at all is
225
- * a StackBadRequestError. Under ScopedStack each key carries its own gate and
226
- * one refused key refuses the call.
227
- * See docs/spec/data-model.md § Mutations.
228
- */
229
- mutate<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, id: RecordId, changes: TypedChangeSet<S>, opts?: IfVersionOptions): Promise<TypedRecord<S>>;
230
- mutate(id: RecordId, changes: RecordChangeSet, opts?: IfVersionOptions): Promise<StackRecord>;
231
- /** mutate() with `contentPatch` alone — the common case, named for it. */
232
- patchContent<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, id: RecordId, patch: PatchOf<S>, opts?: IfVersionOptions): Promise<TypedRecord<S>>;
233
- patchContent(id: RecordId, patch: Record<string, unknown | null>, opts?: IfVersionOptions): Promise<StackRecord>;
234
- /**
235
- * Add associations in one atomic write and one journal entry. Never bumps
236
- * `version`/`updatedAt` and takes no `ifVersion` — a set-add composes
237
- * regardless of write order. See docs/spec/data-model.md § Mutations and
238
- * docs/spec/versioning.md § Version history.
239
- */
240
- associate(id: RecordId, associations: DataAssociation[]): Promise<StackRecord>;
241
- /** Remove associations, matched by identity — see associate(). */
242
- dissociate(id: RecordId, associations: DataAssociation[]): Promise<StackRecord>;
243
- /**
244
- * Apply a list of adds and removes to a record's data associations as one
245
- * atomic write — the cover swap. associate() and dissociate() are this with
246
- * one half each; `mutate({ associations })` is the only whole-set write.
247
- * `repoint` is not a request: an add naming an attachment the record
248
- * already holds re-points it. See docs/spec/data-model.md § Mutations.
249
- */
250
- amendAssociations(id: RecordId, changes: AssociationEdit[]): Promise<StackRecord>;
251
- /**
252
- * Extend who reaches a record — the record-level mirror of the type-level
253
- * `grantType()`, and the amending spelling of the `permissions` key, which
254
- * replaces the whole set. `write` never implies `read`: grant both,
255
- * `grantAccess(id, [read, write])`. No-bump, like associate().
256
- * See docs/spec/access-control.md § Write implies read.
257
- */
258
- grantAccess(id: RecordId, permissions: AuthorityAssociation[]): Promise<StackRecord>;
259
- /** Withdraw elements of who reaches a record — see grantAccess(). */
260
- revokeAccess(id: RecordId, permissions: AuthorityAssociation[]): Promise<StackRecord>;
261
- /**
262
- * amendAssociations() for permissions: adds and removes in one atomic
263
- * write, so a downgrade (remove `write`, keep `read`) is one change.
264
- * See docs/spec/access-control.md § Record-level permissions.
265
- */
266
- amendAccess(id: RecordId, changes: AssociationEdit[]): Promise<StackRecord>;
267
- delete(id: RecordId, opts?: DeleteRecordOptions): Promise<DeleteResult>;
268
- /**
269
- * delete(), plus the record it acted on — read and destroyed as one
270
- * atomic operation, so a caller that needs the record for its own
271
- * response (e.g. a server building a purge's body) never opens a
272
- * gap between reading it and destroying it. See Stack.deleteAndReturn().
273
- */
274
- deleteAndReturn(id: RecordId, opts?: DeleteRecordOptions): Promise<DeleteAndReturnResult>;
275
- undelete(id: RecordId, opts?: IfVersionOptions): Promise<StackRecord>;
276
- getVersions(id: RecordId, query?: VersionsQuery): Promise<RecordVersion[]>;
277
- getVersion(id: RecordId, version: number): Promise<RecordVersion | null>;
278
- restoreVersion(id: RecordId, version: number, opts?: IfVersionOptions): Promise<StackRecord>;
279
- /**
280
- * A record's change journal, oldest first. On the mutate surface, on the
281
- * same footing as getVersions() — a plain reader is refused.
282
- *
283
- * Required of every adapter rather than optional, which is why it sits
284
- * here beside the rest: an empty log has to mean "nothing changed"
285
- * unconditionally, so an adapter with nothing to read refuses and names
286
- * why instead. See docs/spec/journal.md § Reading it.
287
- */
288
- getJournal(id: RecordId, query?: JournalQuery): Promise<RecordJournalEntry[]>;
289
- /**
290
- * Commit a per-record migration: change `typeId` and `content` together,
291
- * validated against `toTypeId`'s schema. The only way a record's typeId
292
- * changes after creation — see docs/spec/wire-format.md § Migration
293
- * commit. Takes `ifVersion` like every other mutation that bumps a
294
- * record's version (see docs/spec/versioning.md § Optimistic
295
- * concurrency); over the wire that is `If-Match`.
296
- */
297
- commitMigration(id: RecordId, toTypeId: TypeId, content: Record<string, unknown>, opts?: IfVersionOptions): Promise<StackRecord>;
298
- getAttachment(fileId: FileId): Promise<Uint8Array>;
299
- putAttachment(data: Uint8Array, opts: PutAttachmentOptions): Promise<StackRecord & {
300
- content: AttachmentContent;
301
- }>;
302
- deleteAttachment(fileId: FileId): Promise<void>;
303
- collectAttachmentGarbage(opts?: CollectAttachmentGarbageOptions): Promise<CollectAttachmentGarbageResult>;
304
- /**
305
- * Typed subscription: delivers only changes to records of exactly the
306
- * handle's Type, each `record` typed as its content. See
307
- * docs/spec/data-model.md § Type handles.
308
- */
309
- subscribe<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, handler: (change: TypedChange<S>) => void, opts?: TypedSubscribeOptions): Promise<Unsubscribe>;
310
- subscribe(handler: (change: RecordChange) => void, opts?: SubscribeOptions): Promise<Unsubscribe>;
311
- }
312
- export declare class Stack implements StackClient {
313
- private readonly adapter;
314
- private readonly idTimestampSkewMsValue;
315
- private readonly migrations;
316
- /**
317
- * Highest version this instance has defineType()'d, per baseId — what
318
- * this app process understands, as distinct from what exists in shared
319
- * storage. Used to detect the stale-writer case in presentAtLatest().
320
- */
321
- private readonly maxDefinedVersion;
322
- /**
323
- * Types this instance has fetched or defined, keyed by versioned id. A
324
- * Type's schema is immutable once defined, so entries are never
325
- * invalidated, only added; listTypes() refreshes wholesale. See
326
- * docs/spec/data-model.md § Type cache.
327
- */
328
- private readonly typeCache;
329
- /** Set by close(). See docs/spec/adapters.md § Lifecycle. */
330
- private closed;
331
- /**
332
- * Every change made through this Stack. `ScopedStack` filters this same
333
- * stream rather than opening its own, so no scoped view can observe a
334
- * change the stack did not emit. See docs/spec/events.md.
335
- */
336
- private readonly changes;
337
- private constructor();
338
- /**
339
- * Announce a mutation that has already been persisted. Called after the
340
- * adapter write resolves and before the mutating method settles, so
341
- * `await stack.mutate(...)` guarantees subscribers have been notified —
342
- * and nothing about work they deferred. A handler cannot fail the write:
343
- * there is nothing left to fail. See docs/spec/events.md § Handlers.
344
- */
345
- private announce;
346
- /**
347
- * The actor for a `create` journal entry, read off the record as just
348
- * built. Create is the one journaled write that stamps the requester
349
- * onto the row itself, so the record in hand is authoritative — and it
350
- * carries `appId`, which ActorOptions has no field for.
351
- */
352
- private static createActor;
353
- private getTypeCached;
354
- /**
355
- * Open a Stack over an adapter. Reads ownerEntityId and timezone from the adapter.
356
- */
357
- static open(adapter: StackAdapter, opts?: StackOptions): Promise<Stack>;
358
- /**
359
- * Idempotent bootstrap for StackOptions.ownerProfile. Filters by family
360
- * only (content filtering is capability-gated) and matches `content.did`
361
- * in memory, cursor-walking so an owner card past page one isn't missed
362
- * and duplicated. The probe must be blind to nothing the binding rules
363
- * see, or it mints a card they then refuse and the stack won't open with
364
- * `ownerProfile`: soft-deleted cards still reserve their `did`, and a card
365
- * migrated to a later version still holds one.
366
- * See docs/spec/identity.md § DID bindings.
367
- */
368
- private ensureOwnerEntity;
369
- get ownerEntityId(): EntityId;
370
- getEntityByDid(did: EntityId): Promise<StackRecord | null>;
371
- getOwnerEntity(): Promise<StackRecord | null>;
372
- get timezone(): string | undefined;
373
- get capabilities(): StackCapabilities;
374
- /**
375
- * Get a permission-scoped view of this Stack, as if requests came from
376
- * the given entity acting as itself (null = anonymous). Plain Stack
377
- * methods are unscoped; use asEntity() when one Stack serves multiple,
378
- * possibly untrusted, entities. See
379
- * docs/spec/access-control.md § Enforcement: Stack.asEntity().
380
- */
381
- asEntity(entityId: EntityId | null): ScopedStack;
382
- /**
383
- * Scope to an Actor — a delegated app acting for its user, or a
384
- * TokenSession exactly as StackTokenStore.lookupToken() returns it, which
385
- * is what a server should reach for at its request boundary. Authority is
386
- * the intersection of both parties' grants, while authorship and `-own`
387
- * resolve against the subject. Taking the pair whole leaves no order to
388
- * swap. See docs/spec/access-control.md § Delegation: principal and subject.
389
- */
390
- asActor(actor: Actor): ScopedStack;
391
- private scope;
392
- /**
393
- * Define and persist a Type; call at app startup before creating records
394
- * of the type. Redefining an existing typeId is checked against the
395
- * stored schema: identical is a no-op, a name-only change persists, and
396
- * anything beyond additive evolution throws StackSchemaDriftError. See
397
- * docs/spec/data-model.md § Schema drift detection.
398
- */
399
- defineType({ id, name, schema: declared, migratesFrom, }: DefineTypeOptions): Promise<StackType>;
400
- getType(id: TypeId): Promise<StackType | null>;
401
- /** Refreshes typeCache wholesale — the explicit way to see a rename made by another writer. */
402
- listTypes(): Promise<StackType[]>;
403
- /**
404
- * Check whether a record's type is compatible with a required schema.
405
- * Useful for duck-typed consumption across types.
406
- */
407
- typeIsCompatible(typeId: TypeId, requiredSchema: TypeSchema): Promise<boolean>;
408
- /**
409
- * Register a migration function between two adjacent Type versions; call
410
- * at app startup after defineType(). Runs in-memory — nothing is written
411
- * until migrateAll(). Adjacent migrations are composed into chains
412
- * automatically.
413
- */
414
- registerMigration(migration: Migration): void;
415
- /**
416
- * Find and compose a migration path from one TypeId to another.
417
- * Returns null if no path exists.
418
- */
419
- private resolveMigrationPath;
420
- /**
421
- * Find the latest registered version of a type family.
422
- * Follows the migration chain from the given typeId to the end.
423
- */
424
- private latestTypeId;
425
- /**
426
- * Eagerly migrate all records of a type family to the latest version —
427
- * the only way disk state changes version. Sweeps soft-deleted records
428
- * too, validates each result before writing, and aborts on the first
429
- * validation failure. See docs/spec/data-model.md § Type migrations.
430
- *
431
- * `sweep: 'listed'` is for a contained app over `APIAdapter`, which can
432
- * neither enumerate unlisted records nor read a deleted one's content:
433
- * it migrates live, listed records and counts the deleted ones it passed
434
- * over in `skipped`. See docs/spec/apps.md § Migrating an installed app's types.
435
- */
436
- migrateAll(baseId: BaseId, opts?: MigrateAllOptions): Promise<{
437
- migrated: number;
438
- skipped: number;
439
- }>;
440
- /**
441
- * Create a new record. Validates content against the type's schema.
442
- * `_group` records get their author stamped as the first `admin` roster
443
- * association here — the single stamping site for both Stack.create()
444
- * and ScopedStack.create(). `opts.createdAt`/`updatedAt` let a caller
445
- * backdate an imported record's clock fields — unconditionally here;
446
- * ScopedStack.create() forwards to this same method, but only reaches
447
- * this far with them when the requester is the stack owner acting alone
448
- * — see BackdatableCreateRecordOptions and docs/spec/data-model.md §
449
- * Record IDs.
450
- */
451
- create<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, content: ContentOf<S>, opts?: BackdatableCreateRecordOptions): Promise<TypedRecord<S>>;
452
- create<T extends Record<string, unknown> = Record<string, unknown>>(typeId: TypeId, content: T, opts?: BackdatableCreateRecordOptions): Promise<StackRecord & {
453
- content: T;
454
- }>;
455
- /**
456
- * Apply the registered migration chain in memory, for presentAt:
457
- * 'latest'. Never writes back. Throws StackMigrationError when the
458
- * record's version can't be reconciled with what this instance has
459
- * registered — the stale-writer case. See docs/spec/data-model.md
460
- * § Type migrations.
461
- */
462
- private presentAtLatest;
463
- /**
464
- * Get a record by ID, exactly as stored — no implicit migration. Pass
465
- * { presentAt: 'latest' } to migrate in memory; only migrateAll()
466
- * commits migrations to disk. A soft-deleted record answers `null` unless
467
- * { includeDeleted: true } is passed.
468
- */
469
- get<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, id: RecordId): Promise<TypedRecord<S> | null>;
470
- get(id: RecordId, opts?: GetRecordOptions): Promise<StackRecord | null>;
471
- /**
472
- * Apply a change set as one atomic write — see the StackClient
473
- * declaration above for what it accepts. Atomicity is what it is for: a
474
- * publish is one act, so nothing it names may half-land.
475
- *
476
- * Every key present is checked against the record as it stands, and a set
477
- * already satisfied in all of them writes nothing. Content is validated
478
- * against the record's *current* stored type; `typeId` never changes here.
479
- * See docs/spec/data-model.md § Mutations.
480
- */
481
- mutate<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, id: RecordId, changes: TypedChangeSet<S>, opts?: IfVersionOptions & ActorOptions): Promise<TypedRecord<S>>;
482
- mutate(id: RecordId, changes: RecordChangeSet, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
483
- /**
484
- * mutate() with `contentPatch` alone. The common case by a wide margin,
485
- * and named for what it does rather than for a symmetry with create()
486
- * that a patch does not have.
487
- */
488
- patchContent<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, id: RecordId, patch: PatchOf<S>, opts?: IfVersionOptions & ActorOptions): Promise<TypedRecord<S>>;
489
- patchContent(id: RecordId, patch: Record<string, unknown | null>, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
490
- /**
491
- * Every check a change set owes before anything is written, in one pass:
492
- * one refused key refuses the call, so none of them may run after a
493
- * partial write. Returns the merged content when the set carries a
494
- * content patch, since the caller needs it to decide whether content
495
- * actually moved.
496
- *
497
- * Parent existence and acyclicity are asked only when the destination
498
- * differs from where the record already sits — a move to where it
499
- * already is names no new edge, and would otherwise cost a read apiece.
500
- */
501
- private validateChangeSet;
502
- /**
503
- * Refuse a write that would leave a `_group` Record with no `admin` on its
504
- * roster. Asked of the roster the write would *produce*, which is the
505
- * whole of the self-removal question: an admin removing themselves passes
506
- * while another remains and is refused when they are the last, without
507
- * either case naming who is going.
508
- *
509
- * An integrity constraint on the Record, not a permission question, so it
510
- * lives here rather than in `ScopedStack` and binds every requester — the
511
- * stack owner included. See docs/spec/identity.md § Group.
512
- */
513
- private assertGroupAdminRemains;
514
- /**
515
- * Add associations to a record — no-bump and no snapshot, per the
516
- * StackClient declaration above.
517
- *
518
- * An association the record already holds, saying the same thing, is a
519
- * no-op; one matching an existing association's identity but naming a
520
- * different `attachmentRecordId` — or none, which clears the one stored —
521
- * re-points it in place rather than adding a second reference to the same
522
- * file. What a re-point overwrote is kept only by the journal, on that
523
- * entry's `repoint`. See docs/spec/journal.md § The entry.
524
- */
525
- associate(id: RecordId, associations: DataAssociation[], opts?: ActorOptions): Promise<StackRecord>;
526
- /**
527
- * Remove associations from a record — see associate(). Matched by kind,
528
- * label and payload; an element not found is a no-op. The emitted event
529
- * names each association by identity only, since an attachment's
530
- * `attachmentRecordId` describes nothing current once it is gone; the
531
- * journal is where it survives.
532
- */
533
- dissociate(id: RecordId, associations: DataAssociation[], opts?: ActorOptions): Promise<StackRecord>;
534
- /**
535
- * The atomic form of associate() and dissociate(): one list of adds and
536
- * removes, one adapter write, one journal entry. The journal records each
537
- * element in full — a remove with its annotation, a re-point with what it
538
- * overwrote — so the entry is as undoable as a single-element one.
539
- * See docs/spec/data-model.md § Mutations.
540
- */
541
- amendAssociations(id: RecordId, changes: AssociationEdit[], opts?: ActorOptions, surface?: string): Promise<StackRecord>;
542
- /**
543
- * Extend who reaches a record. The spelling that survives two admins
544
- * sharing a record at once: a `permissions` key write replaces the whole
545
- * set, so the later of two concurrent ones drops what the earlier
546
- * granted. No-bump, like associate(); an element the record already
547
- * carries is a no-op. `write` never implies `read` — name both.
548
- */
549
- grantAccess(id: RecordId, permissions: AuthorityAssociation[], opts?: ActorOptions): Promise<StackRecord>;
550
- /**
551
- * Withdraw elements of who reaches a record — see grantAccess() for why
552
- * this is a verb rather than a key write. An element the record does not
553
- * carry is a no-op. Returns the record as it now stands.
554
- */
555
- revokeAccess(id: RecordId, permissions: AuthorityAssociation[], opts?: ActorOptions): Promise<StackRecord>;
556
- /**
557
- * The atomic form of grantAccess() and revokeAccess(). The
558
- * write-implies-read invariant is asked once, of the set the whole list
559
- * produces. See docs/spec/access-control.md § Write implies read.
560
- */
561
- amendAccess(id: RecordId, changes: AssociationEdit[], opts?: ActorOptions, surface?: string): Promise<StackRecord>;
562
- /**
563
- * Hold a permission set to the cross-element invariant — see
564
- * validatePermissions() in access.ts for why it reads the post-state.
565
- */
566
- private assertPermissionSet;
567
- /**
568
- * A soft-deleted record has no current state to edit, so the verbs that
569
- * edit one refuse it; undelete() and commitMigration() do not call this.
570
- * Asked after the record is found and, under ScopedStack, after the
571
- * authority decision. See docs/spec/versioning.md § Mutations are refused,
572
- * not applied to a tombstone.
573
- */
574
- private refuseIfDeleted;
575
- /** The record, or the not-found refusal every mutating verb owes. */
576
- private requireRecord;
577
- /**
578
- * The checks a *caller-named* destination owes, before the cycle walk:
579
- * `parentId` is a real, well-formed record id. Format first, so a
580
- * malformed one is a 400 naming the problem rather than a read that
581
- * cannot match. Existence closes the gap between the owner path and a
582
- * non-owner's, where canReadReferent() already refuses a parent that
583
- * isn't there.
584
- *
585
- * restoreVersion() deliberately does not call this — see its own comment.
586
- * See docs/spec/data-model.md § Reparenting.
587
- */
588
- private assertParentExists;
589
- /**
590
- * Refuse an edge that would make a record its own ancestor — asked at
591
- * every site that adds one. Nothing downstream (a generator deriving a
592
- * page path, a folder view) is written to survive a cycle.
593
- *
594
- * Walks with the unscoped adapter: a walk that skipped the links a
595
- * requester cannot read would let a cycle be assembled through them and
596
- * break the invariant for every reader.
597
- *
598
- * Read-then-write, so two moves racing on opposite ends of one chain can
599
- * both pass — the same deferral as checkBindingUnique() above, since
600
- * closing it would put a graph constraint in the storage contract.
601
- * Consumers walking `parentId` should carry a visited set rather than
602
- * trust this alone. See docs/spec/data-model.md § Reparenting.
603
- */
604
- private assertNoParentCycle;
605
- /**
606
- * Soft-delete a record (default) or purge it permanently, removing
607
- * the record and its version history. Soft delete snapshots and bumps
608
- * version; a no-op if already deleted. `_config` is never deletable
609
- * (docs/spec.md § The `_config` record). See docs/spec/versioning.md
610
- * § Deletion.
611
- *
612
- * A purge reports the files the record referenced. It deletes none of
613
- * them — byte lifetime is not the purged record's to decide — but it
614
- * destroys the only rows naming them, so a caller who means to erase the
615
- * bytes too has nowhere else to read the argument from afterwards. See
616
- * docs/spec/attachments.md § A purge strands the bytes it referenced.
617
- *
618
- * deleteAndReturn() is this plus the record itself; reach for that
619
- * instead when the caller needs the record too — building a response
620
- * body from a separate read beforehand is exactly the race and the extra
621
- * round trip it exists to remove.
622
- */
623
- delete(id: RecordId, opts?: DeleteRecordOptions & ActorOptions): Promise<DeleteResult>;
624
- /**
625
- * delete(), reporting the record it acted on alongside the files it
626
- * stranded: as it stood immediately before destruction for a
627
- * purge, or as the resulting tombstone for a soft delete. Both are
628
- * captured inside the same write that destroys or tombstones the record,
629
- * never a read beforehand — a read-then-delete would leave a window
630
- * where a concurrent write can land, get destroyed or overwritten by
631
- * this call, and never appear in the record this returns.
632
- */
633
- deleteAndReturn(id: RecordId, opts?: DeleteRecordOptions & ActorOptions): Promise<DeleteAndReturnResult>;
634
- /**
635
- * The files a record references, by the same definition
636
- * deleteAttachment() and the garbage sweep use: attachment associations
637
- * and top-level `file-ref` content fields. An `_attachment` record's own
638
- * `fileId` is a plain string by design and is not one, which is why
639
- * purging a metadata record reports nothing — see initSystemTypes().
640
- */
641
- private referencedFileIds;
642
- /**
643
- * Reverse a soft delete. Idempotent — undeleting a record that isn't
644
- * deleted returns it unchanged. Purged records are gone, so this
645
- * throws StackNotFoundError for them just like any other missing record.
646
- * Snapshots and bumps version, same as delete().
647
- */
648
- undelete(id: RecordId, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
649
- /**
650
- * Query records. filter.baseId matches every version of a type family,
651
- * resolved against registered Types. Results come back exactly as
652
- * stored; pass presentAt: 'latest' to migrate in memory. See
653
- * docs/spec/data-model.md § Queries.
654
- */
655
- query<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, query?: TypedQuery): Promise<{
656
- records: TypedRecord<S>[];
657
- cursor: string | null;
658
- }>;
659
- query(query?: StackQuery): Promise<QueryResult>;
660
- /**
661
- * Resolve filter.baseId into a concrete typeId set (intersected with
662
- * filter.typeId when both are given), so adapters never need their own
663
- * baseId concept. Returns EMPTY_FAMILY when the resolved set is empty so
664
- * the caller can short-circuit without an adapter round trip.
665
- */
666
- private resolveBaseIdFilter;
667
- getVersions(id: RecordId, query?: VersionsQuery): Promise<RecordVersion[]>;
668
- /**
669
- * A record's change journal, oldest first — what moved, who moved it, and
670
- * the association deltas nothing else retains. `ScopedStack` applies the
671
- * mutate-surface gate; this layer is unscoped and trusted by definition.
672
- *
673
- * A record that isn't there is StackNotFoundError, a purged one included:
674
- * a destroyed log and an empty one are not the same answer.
675
- * See docs/spec/journal.md § Reading it.
676
- */
677
- getJournal(id: RecordId, query?: JournalQuery): Promise<RecordJournalEntry[]>;
678
- getVersion(id: RecordId, version: number): Promise<RecordVersion | null>;
679
- /**
680
- * Restore a record to a previous version by creating a new version —
681
- * never rewrites history. The snapshot is validated against its own
682
- * stored typeId, and puts back `content` and `typeId` alone: a snapshot
683
- * carries nothing else. See docs/spec/versioning.md § Restore semantics.
684
- */
685
- restoreVersion(id: RecordId, version: number, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
686
- /**
687
- * Commit a per-record migration — the single-record counterpart to
688
- * migrateAll(), with `content` supplied by the caller rather than by a
689
- * registered Migration function. See docs/spec/wire-format.md § Migration
690
- * commit.
691
- *
692
- * Because `content` is a full replacement written under a new `typeId`,
693
- * this is create-shaped at the destination *and* update-shaped over the
694
- * record as it stands, so it owes both sets of integrity checks. Missing
695
- * either half would make migrate a second, unguarded write path to a
696
- * state create()/mutate() refuse to reach.
697
- */
698
- commitMigration(id: RecordId, toTypeId: TypeId, content: Record<string, unknown>, opts?: IfVersionOptions & ActorOptions): Promise<StackRecord>;
699
- /**
700
- * The checked migration write, shared by commitMigration() and
701
- * migrateAll(). Takes the record already in hand rather than an id: a
702
- * batch pass holds each record from its own query page, and re-fetching
703
- * per record would cost a read apiece for nothing.
704
- *
705
- * Both callers owe the same checks. migrateAll()'s content comes from a
706
- * registered Migration function rather than a request body, but "app
707
- * code" is not a trust boundary here — the app calling commitMigration()
708
- * is the same app that registered the function, and neither may move a
709
- * DID binding or repoint an attachment. registerMigration() also places
710
- * no constraint on `from` and `to` sharing a baseId, so a migration path
711
- * can cross type families; family-crossing is exactly what the checks
712
- * below care about.
713
- */
714
- private commitMigrationChecked;
715
- /** Uniqueness for every unique binding field a newly created card claims. */
716
- private checkBindingsOnCreate;
717
- /**
718
- * Immutability for every binding field a patch touches, then uniqueness
719
- * for the subset that carries it. Fields absent from the patch carry no
720
- * new claim — a content patch is a merge, so an untouched binding is the one the
721
- * card already holds.
722
- */
723
- private checkBindingsOnUpdate;
724
- /**
725
- * Bindings across a migration. `content` is a full replacement, so every
726
- * binding field either keeps its value, moves to a new one, or is shed by
727
- * omission — and immutability refuses the last two. Asked across the
728
- * union of both families' binding fields, so a card can neither shed its
729
- * DID by migrating out of `_entity`/`_app` nor pick one up on the way in.
730
- *
731
- * Uniqueness is asked only of the destination family, excluding the
732
- * record itself. See docs/spec/identity.md § DID bindings.
733
- */
734
- private checkBindingsOnMigrate;
735
- /**
736
- * A unique binding field is what a lookup resolves *by* — an Actor's
737
- * `principalId` by `_app.did`, its `subjectId` by `_entity.did`. Two cards
738
- * claiming one value would leave that lookup without a single answer, and
739
- * ambiguity is all an impersonating card needs. Enforced here rather than
740
- * by schema, since uniqueness is a property of the set, not of the value.
741
- *
742
- * Read-then-write, so two creates racing on one value can both pass:
743
- * closing that means a unique index over a JSON field, which is a
744
- * decision about where uniqueness lives rather than a local fix.
745
- * See docs/spec/identity.md § DID bindings.
746
- */
747
- private checkBindingUnique;
748
- /**
749
- * A binding is permanent once made: uniqueness stops a second card
750
- * claiming a value, but only immutability stops an existing card being
751
- * moved onto one, which reaches the same impersonation by another route.
752
- * Adopting a value is therefore a one-way step, and a subject whose key
753
- * changes gets a new card — matching identity.md's deferral of key
754
- * rotation, where a new key is a new identity rather than the same one
755
- * relabelled. See docs/spec/identity.md § DID bindings.
756
- */
757
- private checkBindingImmutable;
758
- /**
759
- * mimeType is a property of the fileId, not the uploader's perspective:
760
- * the first metadata record for a fileId fixes it, and a conflicting
761
- * later upload is rejected. See docs/spec/attachments.md § The
762
- * `_attachment` record type.
763
- *
764
- * Best-effort by construction — check-then-create with no storage-level
765
- * uniqueness behind it, so two racing first uploads can both land on a
766
- * concurrent server. What survives that race is the *resolution*:
767
- * getAttachmentRecords() returns the candidates in the same total order
768
- * a server serving Content-Type applies, so both sides name the same
769
- * winner however many records exist.
770
- */
771
- private checkAttachmentMimeTypeOnCreate;
772
- /**
773
- * An attachment association's `attachmentRecordId` names an `_attachment`
774
- * record for the same `fileId` — checked here so a reference can't be
775
- * annotated with an unrelated record's filename.
776
- *
777
- * Best-effort, like the mimeType check above: the named record can be
778
- * deleted afterwards, so every reader of the field falls back rather than
779
- * trusting it. `stored` is what the record already holds, asked so a
780
- * change set restating an association is never refused for a pointer the
781
- * record has carried since before the named record was deleted.
782
- * See docs/spec/attachments.md § Naming the upload a reference came from.
783
- */
784
- private checkAttachmentAssociationPointers;
785
- /**
786
- * filename is the only mutable field on an `_attachment@1` record; fileId,
787
- * size and mimeType are immutable, and the correction flow is delete +
788
- * re-upload. `violates` decides what counts as touching one, because the
789
- * two write shapes disagree: a patch names only what it changes, while a
790
- * migration replaces content wholesale and necessarily re-sends all three.
791
- *
792
- * Repointing `fileId` is the one that matters most: an `_attachment@1`
793
- * record naming a fileId is what canAccessFile()'s uploader clause reads,
794
- * so moving an existing record onto another file's hash is a route to
795
- * bytes the record's author never uploaded.
796
- * See docs/spec/attachments.md § The `_attachment` record type.
797
- */
798
- private assertAttachmentImmutable;
799
- /**
800
- * `_config.entityId` defines stack ownership; neither patchContent() nor
801
- * restoreVersion() may change it. A conflict with stack integrity, not a
802
- * schema violation — hence StackConflictError. See docs/spec.md § The
803
- * `_config` record.
804
- */
805
- private checkConfigEntityIdUnchanged;
806
- /**
807
- * Store bytes and create an _attachment@1 metadata record (owner-
808
- * attributed, no createdBy), returning that record — `content.fileId`
809
- * addresses the bytes, `id` addresses the metadata. Delegates to the
810
- * adapter's atomic putAttachmentWithMetadata() when implemented, trusting
811
- * the returned record as backend-authoritative; otherwise falls back to
812
- * bytes-then-create(). See docs/spec/wire-format.md § Attachments.
813
- */
814
- putAttachment(data: Uint8Array, opts: PutAttachmentOptions): Promise<StackRecord & {
815
- content: AttachmentContent;
816
- }>;
817
- getAttachment(fileId: FileId): Promise<Uint8Array>;
818
- /**
819
- * Every `_attachment` record describing `fileId`, earliest-recorded
820
- * first — so `records[0]` is the one that establishes the file's
821
- * mimeType, and firstRecordedAttachment() is only needed by a caller
822
- * narrowing the set further. See docs/spec/attachments.md § Finding a
823
- * `fileId`'s metadata records.
824
- *
825
- * On `Stack` and not `StackClient`: the lookup answers a presentation
826
- * question about an access decision already made, so a scoped version
827
- * would impose a second, different permission check rather than a
828
- * narrower correct answer.
829
- */
830
- getAttachmentRecords(fileId: FileId): Promise<(StackRecord & {
831
- content: AttachmentContent;
832
- })[]>;
833
- /**
834
- * The `_attachment` family as concrete typeIds. Resolved here rather
835
- * than in the adapter, which has no baseId concept of its own — see
836
- * resolveBaseIdFilter().
837
- */
838
- private attachmentTypeIds;
839
- /**
840
- * Delete an attachment's bytes and every _attachment metadata record for
841
- * it, family-wide. Throws StackConflictError if any record in the stack
842
- * still references the file, StackNotFoundError if neither metadata
843
- * records nor bytes exist.
844
- */
845
- deleteAttachment(fileId: FileId, opts?: ActorOptions): Promise<void>;
846
- /**
847
- * Non-atomic fallback for adapters that don't implement
848
- * deleteUnreferencedAttachmentRecords(): a concurrent associate() can
849
- * race between the reference check below and the deletes it guards.
850
- */
851
- private deleteUnreferencedAttachmentRecordsFallback;
852
- /**
853
- * Sweep for attachment bytes unreachable from any record — live or
854
- * soft-deleted — and delete bytes + metadata. Deletion goes through
855
- * deleteAttachment(), so a file re-referenced by sweep time is skipped,
856
- * not a failure. See docs/spec/attachments.md § Garbage collection.
857
- */
858
- collectAttachmentGarbage(opts?: CollectAttachmentGarbageOptions & ActorOptions): Promise<CollectAttachmentGarbageResult>;
859
- /**
860
- * Observe every change made through this Stack. Unscoped, so no
861
- * permission filter applies — a caller holding a `Stack` already reaches
862
- * every record by other means; `ScopedStack.subscribe()` is the filtered
863
- * view. See docs/spec/events.md.
864
- *
865
- * Async so that a remote stack can resolve once its feed is live, which
866
- * makes subscribe-then-query the gap-free startup order everywhere. A
867
- * local stack is live immediately.
868
- */
869
- subscribe<S extends ReadonlyTypeSchema>(handle: TypeHandle<S>, handler: (change: TypedChange<S>) => void, opts?: TypedSubscribeOptions): Promise<Unsubscribe>;
870
- subscribe(handler: (change: RecordChange) => void, opts?: SubscribeOptions): Promise<Unsubscribe>;
871
- /**
872
- * Ask the adapter to relay changes that originated elsewhere. Absent on
873
- * every local adapter, where one process owns the storage and there is
874
- * no third party whose writes could have been missed.
875
- *
876
- * The subscription's own filter goes to the relay rather than being
877
- * applied on the way back: the emitter at the far end holds the record,
878
- * so it can answer `createdBy` and `parentId`, which the envelope
879
- * deliberately does not carry. That is also why a relay is opened per
880
- * subscription rather than shared.
881
- */
882
- private openRelay;
883
- /** Whether changes reach this stack from elsewhere. */
884
- private get relaysChanges();
885
- /**
886
- * Flush pending writes to the underlying storage. A no-op for adapters
887
- * that commit on every call (SQLite, the API adapter); meaningful for
888
- * ones that buffer, and for checkpointing a stack that stays open —
889
- * close() covers the teardown case on its own.
890
- */
891
- flush(): Promise<void>;
892
- /**
893
- * Flush, then release any resources the adapter holds (connections, file
894
- * handles, lock files). A failed flush still releases them before it
895
- * propagates: an unwritable stack must not also leak a lock file.
896
- * See docs/spec/adapters.md § Lifecycle.
897
- */
898
- close(): Promise<void>;
899
- /** Throws once close() has run. */
900
- private assertOpen;
901
- /**
902
- * Create a _grant record authorizing `grantee` to take `actions` on
903
- * records of `typeId`: `{ kind: 'entity' }` for one DID, `{ kind: 'group' }`
904
- * for a `_group` Record's roster at a role, `{ kind: 'authenticated' }` for
905
- * any authenticated entity. The type-level mirror of grantAccess() —
906
- * subject first, grantee inside the element.
907
- *
908
- * Granting an **app** a `-own` action does not contain it the way the
909
- * suffix suggests: when that app acts for someone, `-own` is read as the
910
- * bare verb and the subject decides which records are in reach, so in a
911
- * personal stack — where nearly every record is owner-authored — a
912
- * delegated `read-own` is close to `read-any`. Grant an app the types it
913
- * needs, not the suffix that looks narrowest. See
914
- * docs/spec/access-control.md § Delegation: principal and subject.
915
- *
916
- * `baseId` names the whole family and is refused when it carries an
917
- * `@version` suffix. See docs/spec/access-control.md § Type-level grants.
918
- */
919
- grantType(baseId: BaseId, grant: TypeGrant): Promise<StackRecord & {
920
- content: GrantContent;
921
- }>;
922
- /**
923
- * List _grant records. Omit `query` for all grants;
924
- * `{ kind: 'authenticated' }` for only default grants;
925
- * `{ kind: 'group' }` for grants naming that exact group and role, or
926
- * `role: 'any'` for every grant naming the group;
927
- * `{ kind: 'entity' }` for the grants that currently apply to that entity
928
- * (ones naming them, ones naming a group they belong to at a role they
929
- * hold, plus every default grant) — the same resolution hasGrant() uses.
930
- *
931
- * Every arm but `entity` answers identity — what `grantType()` would have
932
- * written with the same argument. The `entity` arm answers coverage, so
933
- * its result is not a preview of what `revokeType()` would withdraw.
934
- * See docs/spec/access-control.md § Type-level grants.
935
- */
936
- listTypeGrants(query?: GrantQuery): Promise<(StackRecord & {
937
- content: GrantContent;
938
- })[]>;
939
- /**
940
- * The inverse of grantType(): soft-deletes the _grant records on
941
- * `baseId`'s family matching `grant`, at the same granularity
942
- * grantType() writes — the grantee is matched whole, role included, and
943
- * the actions exactly. A soft delete like any other — the owner can
944
- * undelete a revocation.
945
- *
946
- * Returns the grants it withdrew, as they stood: an array, where
947
- * revokeAccess() returns one Record, because one target can match several
948
- * _grant records. An empty result is not an error — re-running a
949
- * revocation has to stay safe. See
950
- * docs/spec/access-control.md § Listing and revoking.
951
- */
952
- revokeType(baseId: BaseId, grant: TypeGrant): Promise<(StackRecord & {
953
- content: GrantContent;
954
- })[]>;
955
- /**
956
- * What applying `manifest` for the key `did` would change, for the owner
957
- * to review before installApp(). Writes nothing. Refuses outright what no
958
- * approval could make valid: a type outside the app's own namespace and
959
- * the commons, a request the grant rules refuse, or a `did` already
960
- * registered to a different app. See docs/spec/apps.md § Plan, then apply.
961
- */
962
- planInstall(submitted: AppManifest, opts: {
963
- did: EntityId;
964
- }): Promise<InstallPlan>;
965
- /**
966
- * Apply an approved plan: define the manifest's types, register the
967
- * key's `_app` card if it has none, write the `_install` Record, and
968
- * bring every linked key's grants to exactly what the manifest requests.
969
- * Refuses a plan the stack has moved on from with StackConflictError, so
970
- * what is applied is what was approved. Reinstalls a soft-deleted
971
- * install. See docs/spec/apps.md § Plan, then apply.
972
- */
973
- installApp(plan: InstallPlan): Promise<StackRecord & {
974
- content: InstallContent;
975
- }>;
976
- /**
977
- * Withdraw every grant an install produced and soft-delete it. The app's
978
- * records and `_app` cards stay: the data is the owner's, and the cards
979
- * are what its records' attribution resolves through. Returns the
980
- * install's tombstone. See docs/spec/apps.md § Uninstalling.
981
- */
982
- uninstallApp(appId: AppId): Promise<StackRecord & {
983
- content: InstallContent;
984
- }>;
985
- /** A manifest's own shape, and every request held to grantType()'s rules. */
986
- private checkManifest;
987
- /** Every `_install` Record, deleted and unlisted included — a deleted install keeps its claims. */
988
- private loadInstalls;
989
- /** The `_app` card claiming `did`, deleted and unlisted included. */
990
- private findAppCard;
991
- /** The key's `_app` card, created or undeleted as needed. */
992
- private ensureAppCard;
993
- /**
994
- * Make the grants linked to `install` exactly its `requests`, once per
995
- * live linked key: withdraw any that no longer match, write any missing,
996
- * and drop links to grants that are gone.
997
- */
998
- private reconcileInstallGrants;
999
- /**
1000
- * Actions must be known GrantAction values; the target is held to the
1001
- * same rule every `_grant` write meets — see validateGrantBaseId().
1002
- */
1003
- private checkGrantValid;
1004
- private seedSystemTypes;
1005
- /**
1006
- * Fast-fail for the ifVersion precondition using the already-fetched
1007
- * record. The adapter re-checks atomically at write time (the source of
1008
- * truth for concurrent writers) — this just skips validation and
1009
- * snapshotting work when the mismatch is already visible.
1010
- */
1011
- private checkIfVersion;
1012
- /**
1013
- * The options every version-bumping adapter write carries: the `ifVersion`
1014
- * precondition, the prior-state snapshot that has to land in the same
1015
- * atomic write, and who to attribute the change to. Taken together so a
1016
- * new mutating verb cannot quietly omit one — the actor's `principalId`
1017
- * most of all, whose absence reads as an undelegated write.
1018
- * See docs/spec/versioning.md § Version history.
1019
- */
1020
- private writeOptions;
1021
- /**
1022
- * Snapshot of a record's prior state, passed with the mutating adapter
1023
- * call so snapshot and mutation land in one atomic write. Carries what
1024
- * only a snapshot preserves: `content` and the `typeId` it is read
1025
- * under. Containment, listing and associations — the authority ones
1026
- * among them — are all kept by the journal instead, so no version ever
1027
- * snapshots them and there is nothing for a restore to roll them back
1028
- * to. See docs/spec/versioning.md § Version history.
1029
- */
1030
- private buildVersionSnapshot;
1031
- }
1032
- //# sourceMappingURL=stack.d.ts.map