@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/errors.js CHANGED
@@ -7,39 +7,39 @@
7
7
  * subclass carries its wire discriminator as an instance `code`.
8
8
  *
9
9
  * Several errors sit deliberately outside that root (UseAfterCloseError,
10
- * InvalidAdapterError, OwnerMismatchError, RelayScopeError, and — in their own
11
- * modules — IdGenerationError and InvalidDidError): they report a local
12
- * programming error or an assembled topology, not a state a request can be
13
- * in, so no server ever responds with one.
10
+ * InvalidAdapterError, OwnerMismatchError, ElementSetMovedError, ScopeUnavailableError, and — in their own
11
+ * modules — IdGenerationError, InvalidDidError, InvalidAuthChallengeError,
12
+ * StoredVersionError and MisfitError): they report a local programming error, malformed
13
+ * local input or an assembled topology, not a state a request can be in, so
14
+ * no server ever responds with one.
14
15
  *
15
16
  * The `Stack` prefix is reserved for the taxonomy: a class named
16
17
  * `Stack…Error` extends StackError, and nothing else carries the prefix.
17
18
  */
18
19
  import { baseIdOf, parseTypeId } from './schema.js';
19
20
  /**
20
- * Root of the Stack error taxonomy. A single `instanceof StackError` answers
21
- * "is this a Stack-domain error or a bug?" — the question a server's error
22
- * middleware asks before serializing a wire body, and one an instanceof
23
- * ladder answers only by exhaustion. Every subclass carries its
24
- * discriminator as an instance `code`, so serialization is a lookup rather
25
- * than a chain of class tests.
26
- *
27
- * Membership implies a wire mapping: every code has an entry in
28
- * WIRE_ERROR_STATUS. Errors with no wire representation (IdGenerationError,
29
- * InvalidDidError) deliberately stay outside this hierarchy.
30
- *
31
- * Subclassing adds no hierarchy beyond this root — notably
32
- * StackVersionConflictError is a sibling of StackConflictError, not a
33
- * subtype. See docs/spec/wire-format.md § Error responses.
21
+ * Root of the taxonomy: `instanceof StackError` is the one question a
22
+ * server's error middleware asks, and each subclass's `code` makes
23
+ * serialization a lookup. Every code has a WIRE_ERROR_STATUS entry.
24
+ * Subclasses add no hierarchy — StackVersionConflictError is a sibling of
25
+ * StackConflictError. See docs/spec/wire-format.md § Error responses.
34
26
  */
35
27
  export class StackError extends Error {
36
28
  }
29
+ const CONTENT_INVALID = 'Content validation failed';
30
+ export const ARGUMENTS_INVALID = 'Invalid arguments';
31
+ export const SCHEMA_INVALID = 'Schema validation failed';
32
+ /**
33
+ * `header` names what was validated — record content, or the arguments a
34
+ * call was given — so a refusal of `editAssociations(id, [])` doesn't read as a
35
+ * content failure. Only the message varies: `code` is the contract.
36
+ */
37
37
  export class StackValidationError extends StackError {
38
38
  errors;
39
39
  static code = 'validation';
40
40
  code = StackValidationError.code;
41
- constructor(errors) {
42
- super(`Content validation failed:\n` + errors.map((e) => ` ${e.path}: ${e.message}`).join('\n'));
41
+ constructor(errors, header = CONTENT_INVALID) {
42
+ super(`${header}:\n` + errors.map((e) => ` ${e.path}: ${e.message}`).join('\n'));
43
43
  this.errors = errors;
44
44
  this.name = 'StackValidationError';
45
45
  }
@@ -79,6 +79,12 @@ export class StackConflictError extends StackError {
79
79
  this.name = 'StackConflictError';
80
80
  }
81
81
  }
82
+ /**
83
+ * The refusal a mutation of a soft-deleted record earns, from `Stack` or
84
+ * from inside an adapter's aspect write: one message, wherever it is asked.
85
+ * See docs/spec/versioning.md § Mutations are refused, not applied to a tombstone.
86
+ */
87
+ export const softDeletedRefusal = (id) => new StackConflictError(`Record "${id}" is soft-deleted; undelete it before mutating it.`);
82
88
  /**
83
89
  * Thrown when an `ifVersion` precondition doesn't match a record's current
84
90
  * version. Deliberately not a StackConflictError subtype — the two have
@@ -100,16 +106,10 @@ export class StackVersionConflictError extends StackError {
100
106
  }
101
107
  }
102
108
  /**
103
- * Thrown when a request is structurally malformed — not a content-validation
104
- * failure, but input the adapter/server can't even interpret (e.g. an
105
- * undecodable pagination cursor, a malformed TypeId, search text the engine
106
- * cannot parse, or a typeId no definition exists for). Distinct from
107
- * StackValidationError, which means the request was well-formed but content
108
- * failed schema validation.
109
- *
110
- * Naming something absent belongs here rather than under `not_found`: a
111
- * request whose *type* is undefined never addressed a record, so answering
112
- * 404 would say a record was missing when none was asked for.
109
+ * A request structurally malformed — an undecodable cursor, a malformed
110
+ * TypeId, unparseable search text — as opposed to StackValidationError's
111
+ * well-formed request with invalid content. Naming an undefined type is
112
+ * here, not `not_found`: no record was asked for, so none is missing.
113
113
  */
114
114
  export class StackBadRequestError extends StackError {
115
115
  capability;
@@ -117,12 +117,9 @@ export class StackBadRequestError extends StackError {
117
117
  code = StackBadRequestError.code;
118
118
  constructor(message,
119
119
  /**
120
- * Which declared capability the query asked for and the adapter lacks,
121
- * or undefined when the query's own shape is what was wrong — a
122
- * malformed content path is a caller error at every capability level.
123
- * A client mapping this onto an error of its own reads the name from
124
- * here rather than re-deriving it from the query, which is how one
125
- * rule becomes two answers that can disagree.
120
+ * The declared capability the query needed and the adapter lacks, or
121
+ * undefined when the query's own shape was wrong. A client reads it from
122
+ * here rather than re-deriving it, so one rule has one answer.
126
123
  */
127
124
  capability) {
128
125
  super(message);
@@ -145,17 +142,10 @@ export class StackPayloadTooLargeError extends StackError {
145
142
  }
146
143
  }
147
144
  /**
148
- * Thrown when a server abandons an operation for taking too long — in
149
- * practice a full-text search, the one query whose cost the sanitizers
150
- * bound the *grammar* of but not the execution of (see
151
- * docs/spec/data-model.md § Capability-gated filters).
152
- *
153
- * Never produced in-process: both SQLite engines run synchronously, so
154
- * there is nothing to interrupt from inside the call. It exists so a
155
- * server that bounds query time has a class to serialize, and so the app
156
- * catching it can tell "too expensive, narrow it and retry" from
157
- * StackBadRequestError's "malformed, don't bother retrying" — the distinction
158
- * that would be lost if a timeout reused `bad_request`.
145
+ * A server abandoned an operation for taking too long — in practice a
146
+ * full-text search. Never produced in-process; it lets an app tell "too
147
+ * expensive, narrow and retry" from `bad_request`'s "malformed".
148
+ * See docs/spec/data-model.md § Capability-gated filters.
159
149
  */
160
150
  export class StackTimeoutError extends StackError {
161
151
  static code = 'timeout';
@@ -179,7 +169,7 @@ export class StackSchemaDriftError extends StackError {
179
169
  constructor(typeId, violations) {
180
170
  super(`Schema drift detected for type "${typeId}": the stored schema and the new definition ` +
181
171
  `differ beyond additive evolution (new optional fields only). Bump the version instead ` +
182
- `of redefining "${typeId}" in place — e.g. defineType({ id: \`${baseIdOf(typeId)}@${(parseTypeId(typeId)?.version ?? 0) + 1}\`, ... }) plus registerMigration().\n` +
172
+ `of redefining "${typeId}" in place — e.g. defineType({ id: \`${baseIdOf(typeId)}@${(parseTypeId(typeId)?.version ?? 0) + 1}\`, ... }) plus a migration() passed to Stack.open().\n` +
183
173
  violations.map((v) => ` ${v.path || '(root)'}: ${v.message}`).join('\n'));
184
174
  this.typeId = typeId;
185
175
  this.violations = violations;
@@ -233,23 +223,29 @@ export class OwnerMismatchError extends Error {
233
223
  }
234
224
  }
235
225
  /**
236
- * Thrown when a scoped view is asked to observe a stack whose adapter
237
- * relays a remote feed. Outside the StackError taxonomy for the same
238
- * reason UseAfterCloseError is: it reports a topology the caller assembled,
239
- * not a state a request can be in.
240
- *
241
- * A relayed frame is scoped by the authority that opened the feed, and a
242
- * narrower scope cannot re-derive that decision — a purge leaves no record
243
- * to check `canRead` against. Delivering anyway would break the promise
244
- * that a subscriber never sees what it may not read; delivering only local
245
- * writes would silently drop every change made elsewhere, which is the
246
- * failure that looks fine in testing. So it refuses.
247
- * See docs/spec/events.md § Permission scoping.
226
+ * Thrown by an adapter's element-set write when the stored set no longer
227
+ * matches the `expectSet` it was handed. Outside the StackError taxonomy:
228
+ * Stack catches it and reads again, so no request ever answers with it.
229
+ * See docs/spec/versioning.md § Element-set writes compare the set they read.
230
+ */
231
+ export class ElementSetMovedError extends Error {
232
+ recordId;
233
+ constructor(recordId) {
234
+ super(`Record "${recordId}"'s set changed since it was read`);
235
+ this.recordId = recordId;
236
+ this.name = 'ElementSetMovedError';
237
+ }
238
+ }
239
+ /**
240
+ * A scoped view asked of a stack that does not own its storage. A server
241
+ * scopes every request to the session that opened it, so such a stack is
242
+ * scoped by opening it with the session meant.
243
+ * See docs/spec/access-control.md § Enforcement: Stack.asEntity().
248
244
  */
249
- export class RelayScopeError extends Error {
245
+ export class ScopeUnavailableError extends Error {
250
246
  constructor(message) {
251
247
  super(message);
252
- this.name = 'RelayScopeError';
248
+ this.name = 'ScopeUnavailableError';
253
249
  }
254
250
  }
255
251
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/grants.d.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  *
9
9
  * See docs/spec/access-control.md § Type-level grants.
10
10
  */
11
- import type { EntityId, GrantAction, GrantContent, GrantGrantee, GroupRole, QueryResult, RecordId, StackQuery, StackRecord, TypeId } from './types.js';
11
+ import type { RosterRoles } from './access.js';
12
+ import type { BaseId, EntityId, GrantAction, GrantContent, GrantGrantee, GroupRole, RecordId, TypeId } from './types/index.js';
12
13
  import type { ValidationError } from './validate.js';
13
14
  /**
14
15
  * Valid GrantAction values, for runtime validation in Stack.grantType(). Built
@@ -17,33 +18,18 @@ import type { ValidationError } from './validate.js';
17
18
  */
18
19
  export declare const GRANT_ACTION_SET: ReadonlySet<GrantAction>;
19
20
  /**
20
- * The read actions that make a mutate action coherent, scope for scope: a
21
- * `-any` verb needs read over the same reach it can mutate, a `-own` verb
22
- * needs read over its author's own. A grant carrying neither conveys the
23
- * verb to nobody. `create` has no companion — writing a Record you then
24
- * cannot read is the drop-box, and it discloses nothing.
25
- * See docs/spec/access-control.md § Write implies read.
26
- */
27
- export declare const READ_COMPANIONS: ReadonlyMap<GrantAction, readonly GrantAction[]>;
28
- /**
29
- * Whether one grant's action list conveys `action`. The companion has to
30
- * sit in the same `_grant` Record: a grant is revoked whole, so a rule
31
- * satisfied across two records would let revoking the read one leave a
32
- * mutate-without-read grant standing.
33
- *
34
- * Takes the list grantReach() produced, never a stored field: `includes()`
35
- * over a string is substring matching, so one would answer for every verb
36
- * spelled inside it.
21
+ * Whether one grant's action list conveys `action`. The companion must sit
22
+ * in the same `_grant`: a grant is revoked whole, so a rule met across two
23
+ * records could leave mutate-without-read standing. Takes grantReach()'s
24
+ * list, never a stored field, whose `includes()` would match substrings.
37
25
  */
38
26
  export declare function grantConveys(actions: readonly GrantAction[], action: GrantAction): boolean;
39
27
  /**
40
28
  * What a stored `_grant` reaches, or null where it names nothing the
41
- * evaluator recognizes. Read as data, like the grantee beside it: a grant
42
- * Record can arrive from an import, a direct adapter write or a foreign
43
- * server, where no schema saw it, so both fields are asked for their shape
44
- * rather than taken from the type. An unknown action is dropped from the
45
- * list, on the same terms an unknown grantee `kind` confers nothing.
46
- * See docs/spec/access-control.md § Refused at the write, and again at evaluation.
29
+ * evaluator recognizes. Read as data — a grant can arrive from an import or
30
+ * a foreign server no schema saw — so an unknown action is dropped, as an
31
+ * unknown grantee confers nothing. See docs/spec/access-control.md
32
+ * § Refused at the write, and again at evaluation.
47
33
  */
48
34
  export declare function grantReach(content: unknown): {
49
35
  familyId: string;
@@ -61,6 +47,8 @@ export type GrantQuery = Exclude<GrantGrantee, {
61
47
  groupId: RecordId;
62
48
  role: GroupRole | 'any';
63
49
  };
50
+ /** Whether two action lists name the same verbs, order and repeats aside. */
51
+ export declare function sameActions(a: readonly GrantAction[], b: readonly GrantAction[]): boolean;
64
52
  /**
65
53
  * Direct (non-roster) match between a stored _grant's content and a target:
66
54
  * the whole grantee, `role` included. A target is the identity grantType()
@@ -69,13 +57,10 @@ export type GrantQuery = Exclude<GrantGrantee, {
69
57
  */
70
58
  export declare function matchesGrantTarget(content: GrantContent, target: GrantQuery): boolean;
71
59
  /**
72
- * Reject a grant target that names no tier, or whose tier names nobody. An
73
- * empty groupId or entityId reaches no one, so storing it would leave a
74
- * grant that can only ever deny while looking like a share that worked.
75
- * Read as data, not as the type: a target reaching Stack from a request
76
- * body or an import has whatever shape it arrived with, so a key its tier
77
- * does not define is refused too. `allowAny` admits
78
- * the listing-only `role: 'any'`, which grantType() and revokeType() refuse.
60
+ * Reject a grant target that names no tier, or whose tier names nobody: it
61
+ * would store a grant that can only deny while looking like a share that
62
+ * worked. Read as data, so a key its tier does not define is refused too.
63
+ * `allowAny` admits the listing-only `role: 'any'`.
79
64
  */
80
65
  export declare function validateGrantTarget(target: GrantQuery, allowAny?: boolean): void;
81
66
  /**
@@ -102,8 +87,7 @@ export declare function validateGrantBaseId(typeId: TypeId, content: unknown): V
102
87
  export declare function grantCoversGrantee(c: GrantContent, grantee: EntityId, opts: {
103
88
  allowDefault: boolean;
104
89
  allowGroup: boolean;
105
- groupRoles: Map<string, GroupRole | null>;
106
- resolveRecord: (id: RecordId) => Promise<StackRecord | null>;
90
+ roles: RosterRoles;
107
91
  }): Promise<boolean>;
108
92
  /**
109
93
  * System type families grantType() refuses to target: a grant on any of them
@@ -114,16 +98,8 @@ export declare function grantCoversGrantee(c: GrantContent, grantee: EntityId, o
114
98
  */
115
99
  export declare const UNGRANTABLE_SYSTEM_TYPES: ReadonlySet<string>;
116
100
  /**
117
- * Every `_grant` Record, cursor-walked. Read through an unscoped query at
118
- * every call site: a grant is what decides who may read, so it can never
119
- * itself sit behind a read check.
120
- *
121
- * `includeUnlisted`, because withholding a Record from enumeration decides
122
- * nothing about what it confers. Deleted grants are excluded on the
123
- * opposite grounds — a soft delete is how revokeType() withdraws one.
124
- * See docs/spec/unlisted.md.
101
+ * Actions must be known GrantAction values; the target is held to the
102
+ * same rule every `_grant` write meets — see validateGrantBaseId().
125
103
  */
126
- export declare function loadGrantRecords(query: (q: StackQuery) => Promise<QueryResult>): Promise<(StackRecord & {
127
- content: GrantContent;
128
- })[]>;
104
+ export declare function checkGrantValid(baseId: BaseId, actions: GrantAction[]): void;
129
105
  //# sourceMappingURL=grants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,EACX,MAAM,EACP,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,WAAW,CAA0B,CAAC;AAEjF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,WAAW,EAAE,SAAS,WAAW,EAAE,CAK3E,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAI1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,GAAG,IAAI,CAUhG;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACxC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,KAAK,CAAA;CAAE,CAAC;AAElE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CASrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,UAAQ,GAAG,IAAI,CA+B9E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAkDnF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAiBvF;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE;IACJ,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC,CAAC;IAC1C,aAAa,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC9D,GACA,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAwBD;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAKvD,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,WAAW,CAAC,GAC7C,OAAO,CAAC,CAAC,WAAW,GAAG;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,EAAE,CAAC,CAKtD"}
1
+ {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EACV,MAAM,EACN,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,WAAW,CAA0B,CAAC;AAejF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAI1F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,GAAG,IAAI,CAUhG;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACxC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,KAAK,CAAA;CAAE,CAAC;AAElE,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,WAAW,EAAE,EAAE,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CASrF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,UAAQ,GAAG,IAAI,CAa9E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAMnF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAiBvF;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GACvE,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAKvD,CAAC;AAEH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,CA2B5E"}
package/dist/grants.js CHANGED
@@ -9,11 +9,10 @@
9
9
  * See docs/spec/access-control.md § Type-level grants.
10
10
  */
11
11
  import { baseIdOf, familyIdProblem } from './schema.js';
12
- import { StackBadRequestError } from './errors.js';
13
- import { assertKnownKeys, GRANTEE_KEYS, unknownKeys } from './query-validation.js';
14
- import { SYSTEM_TYPES, GRANT_ACTIONS } from './types.js';
15
- import { carriesRoster, groupRoleFromAssociations } from './access.js';
16
- import { queryAllPages } from './stack-reads.js';
12
+ import { ARGUMENTS_INVALID, StackBadRequestError, StackValidationError } from './errors.js';
13
+ import { granteeErrors } from './associations/validation.js';
14
+ import { SYSTEM_TYPES, GRANT_ACTIONS } from './types/index.js';
15
+ import { granteeReaches } from './access.js';
17
16
  /**
18
17
  * Valid GrantAction values, for runtime validation in Stack.grantType(). Built
19
18
  * from GRANT_ACTIONS, which GrantAction itself derives from, so the set and
@@ -21,28 +20,22 @@ import { queryAllPages } from './stack-reads.js';
21
20
  */
22
21
  export const GRANT_ACTION_SET = new Set(GRANT_ACTIONS);
23
22
  /**
24
- * The read actions that make a mutate action coherent, scope for scope: a
25
- * `-any` verb needs read over the same reach it can mutate, a `-own` verb
26
- * needs read over its author's own. A grant carrying neither conveys the
27
- * verb to nobody. `create` has no companion — writing a Record you then
28
- * cannot read is the drop-box, and it discloses nothing.
23
+ * The read actions that make a mutate action coherent, scope for scope. A
24
+ * grant carrying none conveys the verb to nobody. `create` has none:
25
+ * writing a Record you cannot read is the drop-box, and discloses nothing.
29
26
  * See docs/spec/access-control.md § Write implies read.
30
27
  */
31
- export const READ_COMPANIONS = new Map([
28
+ const READ_COMPANIONS = new Map([
32
29
  ['update-own', ['read-own', 'read-any']],
33
30
  ['delete-own', ['read-own', 'read-any']],
34
31
  ['update-any', ['read-any']],
35
32
  ['delete-any', ['read-any']],
36
33
  ]);
37
34
  /**
38
- * Whether one grant's action list conveys `action`. The companion has to
39
- * sit in the same `_grant` Record: a grant is revoked whole, so a rule
40
- * satisfied across two records would let revoking the read one leave a
41
- * mutate-without-read grant standing.
42
- *
43
- * Takes the list grantReach() produced, never a stored field: `includes()`
44
- * over a string is substring matching, so one would answer for every verb
45
- * spelled inside it.
35
+ * Whether one grant's action list conveys `action`. The companion must sit
36
+ * in the same `_grant`: a grant is revoked whole, so a rule met across two
37
+ * records could leave mutate-without-read standing. Takes grantReach()'s
38
+ * list, never a stored field, whose `includes()` would match substrings.
46
39
  */
47
40
  export function grantConveys(actions, action) {
48
41
  if (!actions.includes(action))
@@ -52,12 +45,10 @@ export function grantConveys(actions, action) {
52
45
  }
53
46
  /**
54
47
  * What a stored `_grant` reaches, or null where it names nothing the
55
- * evaluator recognizes. Read as data, like the grantee beside it: a grant
56
- * Record can arrive from an import, a direct adapter write or a foreign
57
- * server, where no schema saw it, so both fields are asked for their shape
58
- * rather than taken from the type. An unknown action is dropped from the
59
- * list, on the same terms an unknown grantee `kind` confers nothing.
60
- * See docs/spec/access-control.md § Refused at the write, and again at evaluation.
48
+ * evaluator recognizes. Read as data — a grant can arrive from an import or
49
+ * a foreign server no schema saw — so an unknown action is dropped, as an
50
+ * unknown grantee confers nothing. See docs/spec/access-control.md
51
+ * § Refused at the write, and again at evaluation.
61
52
  */
62
53
  export function grantReach(content) {
63
54
  const c = content;
@@ -73,6 +64,12 @@ export function grantReach(content) {
73
64
  actions: c.actions.filter((a) => GRANT_ACTION_SET.has(a)),
74
65
  };
75
66
  }
67
+ /** Whether two action lists name the same verbs, order and repeats aside. */
68
+ export function sameActions(a, b) {
69
+ const as = new Set(a);
70
+ const bs = new Set(b);
71
+ return as.size === bs.size && [...bs].every((x) => as.has(x));
72
+ }
76
73
  /**
77
74
  * Direct (non-roster) match between a stored _grant's content and a target:
78
75
  * the whole grantee, `role` included. A target is the identity grantType()
@@ -92,40 +89,21 @@ export function matchesGrantTarget(content, target) {
92
89
  return g.kind === 'authenticated';
93
90
  }
94
91
  /**
95
- * Reject a grant target that names no tier, or whose tier names nobody. An
96
- * empty groupId or entityId reaches no one, so storing it would leave a
97
- * grant that can only ever deny while looking like a share that worked.
98
- * Read as data, not as the type: a target reaching Stack from a request
99
- * body or an import has whatever shape it arrived with, so a key its tier
100
- * does not define is refused too. `allowAny` admits
101
- * the listing-only `role: 'any'`, which grantType() and revokeType() refuse.
92
+ * Reject a grant target that names no tier, or whose tier names nobody: it
93
+ * would store a grant that can only deny while looking like a share that
94
+ * worked. Read as data, so a key its tier does not define is refused too.
95
+ * `allowAny` admits the listing-only `role: 'any'`.
102
96
  */
103
97
  export function validateGrantTarget(target, allowAny = false) {
104
- const t = target;
105
- if (t?.kind === 'authenticated' || t?.kind === 'entity' || t?.kind === 'group')
106
- assertKnownKeys(t, GRANTEE_KEYS[t.kind], 'grant target');
107
- switch (t?.kind) {
108
- case 'authenticated':
109
- return;
110
- case 'entity':
111
- if (typeof t.entityId !== 'string' || t.entityId.length === 0) {
112
- throw new StackBadRequestError('An entity grant target requires a non-empty entityId.');
113
- }
114
- return;
115
- case 'group':
116
- if (typeof t.groupId !== 'string' || t.groupId.length === 0) {
117
- throw new StackBadRequestError('A group grant target requires a non-empty groupId.');
118
- }
119
- if (t.role !== 'member' && t.role !== 'admin' && !(allowAny && t.role === 'any')) {
120
- throw new StackBadRequestError(allowAny
121
- ? "A group grant target requires role 'member', 'admin' or 'any'."
122
- : "A group grant target requires role 'member' or 'admin'.");
123
- }
124
- // No format check: groupId is a reference, like parentId or an
125
- // association's recordId. One that resolves to nothing simply denies.
126
- return;
127
- default:
128
- throw new StackBadRequestError("A grant target must name its tier: { kind: 'entity' }, { kind: 'group' } or { kind: 'authenticated' }.");
98
+ // No format check on a groupId: it is a reference, like parentId or an
99
+ // association's recordId. One that resolves to nothing simply denies.
100
+ const errors = granteeErrors(target, 'grant target', {
101
+ unknownKeys: 'throw',
102
+ authenticated: true,
103
+ anyRole: allowAny,
104
+ });
105
+ if (errors.length > 0) {
106
+ throw new StackBadRequestError(`Invalid grant target: ${errors.map((e) => e.message).join(' ')}`);
129
107
  }
130
108
  }
131
109
  /**
@@ -137,55 +115,11 @@ export function validateGrantTarget(target, allowAny = false) {
137
115
  export function validateGrantee(typeId, content) {
138
116
  if (baseIdOf(typeId) !== SYSTEM_TYPES.GRANT)
139
117
  return [];
140
- const c = content;
141
- const g = c?.grantee;
118
+ const g = content?.grantee;
142
119
  // An absent or non-object grantee is the schema's to refuse, and it does.
143
120
  if (!g || typeof g !== 'object')
144
121
  return [];
145
- if (g.kind === 'authenticated' || g.kind === 'entity' || g.kind === 'group') {
146
- const unknown = unknownKeys(g, GRANTEE_KEYS[g.kind]);
147
- if (unknown.length > 0)
148
- return unknown.map((key) => ({
149
- path: `grantee.${key}`,
150
- message: `A ${String(g.kind)} grantee does not carry ${key}`,
151
- }));
152
- }
153
- switch (g.kind) {
154
- case 'authenticated':
155
- return [];
156
- case 'entity':
157
- return typeof g.entityId === 'string' && g.entityId.length > 0
158
- ? []
159
- : [
160
- {
161
- path: 'grantee.entityId',
162
- message: 'An entity grantee requires a non-empty entityId',
163
- },
164
- ];
165
- case 'group': {
166
- const errors = [];
167
- if (typeof g.groupId !== 'string' || g.groupId.length === 0) {
168
- errors.push({
169
- path: 'grantee.groupId',
170
- message: 'A group grantee requires a non-empty groupId',
171
- });
172
- }
173
- if (g.role !== 'member' && g.role !== 'admin') {
174
- errors.push({
175
- path: 'grantee.role',
176
- message: "A group grantee requires role 'member' or 'admin'",
177
- });
178
- }
179
- return errors;
180
- }
181
- default:
182
- return [
183
- {
184
- path: 'grantee.kind',
185
- message: "A grantee must name its tier: 'entity', 'group' or 'authenticated'",
186
- },
187
- ];
188
- }
122
+ return granteeErrors(g, 'grantee', { unknownKeys: 'collect', authenticated: true });
189
123
  }
190
124
  /**
191
125
  * A `_grant`'s target, asked of its content on every write: a bare baseId
@@ -223,42 +157,9 @@ export function validateGrantBaseId(typeId, content) {
223
157
  */
224
158
  export async function grantCoversGrantee(c, grantee, opts) {
225
159
  const g = c.grantee;
226
- if (!g || typeof g !== 'object')
227
- return false;
228
- switch (g.kind) {
229
- case 'authenticated':
230
- return opts.allowDefault;
231
- case 'entity':
232
- return !!g.entityId && g.entityId === grantee;
233
- case 'group': {
234
- if (!opts.allowGroup)
235
- return false;
236
- if (!g.groupId)
237
- return false;
238
- if (g.role !== 'member' && g.role !== 'admin')
239
- return false;
240
- const role = await resolveGroupRoleMemoized(g.groupId, grantee, opts.groupRoles, opts.resolveRecord);
241
- return g.role === 'admin' ? role === 'admin' : role !== null;
242
- }
243
- default:
244
- return false;
245
- }
246
- }
247
- /**
248
- * An entity's role on a `_group` roster, memoized in the caller's
249
- * `groupRoles` map. That map is built per operation, so no resolved role
250
- * outlives the operation that resolved it — removal from a group must never
251
- * go stale.
252
- */
253
- async function resolveGroupRoleMemoized(groupId, entityId, groupRoles, resolveRecord) {
254
- const key = `${groupId}:${entityId}`;
255
- const cached = groupRoles.get(key);
256
- if (cached !== undefined)
257
- return cached;
258
- const group = await resolveRecord(groupId);
259
- const role = group && carriesRoster(group) ? groupRoleFromAssociations(group.associations, entityId) : null;
260
- groupRoles.set(key, role);
261
- return role;
160
+ if (g?.kind === 'authenticated')
161
+ return opts.allowDefault;
162
+ return granteeReaches(g, grantee, opts);
262
163
  }
263
164
  /**
264
165
  * System type families grantType() refuses to target: a grant on any of them
@@ -274,19 +175,36 @@ export const UNGRANTABLE_SYSTEM_TYPES = new Set([
274
175
  SYSTEM_TYPES.INSTALL,
275
176
  ]);
276
177
  /**
277
- * Every `_grant` Record, cursor-walked. Read through an unscoped query at
278
- * every call site: a grant is what decides who may read, so it can never
279
- * itself sit behind a read check.
280
- *
281
- * `includeUnlisted`, because withholding a Record from enumeration decides
282
- * nothing about what it confers. Deleted grants are excluded on the
283
- * opposite grounds — a soft delete is how revokeType() withdraws one.
284
- * See docs/spec/unlisted.md.
178
+ * Actions must be known GrantAction values; the target is held to the
179
+ * same rule every `_grant` write meets — see validateGrantBaseId().
285
180
  */
286
- export async function loadGrantRecords(query) {
287
- const records = await queryAllPages(query, {
288
- filter: { typeId: `${SYSTEM_TYPES.GRANT}@1`, includeUnlisted: true },
181
+ export function checkGrantValid(baseId, actions) {
182
+ const errors = [];
183
+ actions.forEach((action, j) => {
184
+ if (!GRANT_ACTION_SET.has(action)) {
185
+ errors.push({ path: `actions[${j}]`, message: `Unknown grant action "${action}"` });
186
+ }
289
187
  });
290
- return records;
188
+ const problem = familyIdProblem(baseId, 'grantType');
189
+ if (problem) {
190
+ errors.push({ path: 'baseId', message: problem });
191
+ }
192
+ else {
193
+ errors.push(...validateGrantBaseId(`${SYSTEM_TYPES.GRANT}@1`, { baseId }));
194
+ }
195
+ actions.forEach((action, j) => {
196
+ if (grantConveys(actions, action))
197
+ return;
198
+ const companions = READ_COMPANIONS.get(action);
199
+ if (!companions)
200
+ return;
201
+ errors.push({
202
+ path: `actions[${j}]`,
203
+ message: `"${action}" requires ${companions.map((c) => `"${c}"`).join(' or ')} in the same grant: a mutate verb reaches the record and its history, so it conveys nothing without read. Name both in one grant: actions: [${companions.map((c) => `'${c}'`).join(' | ')}, '${action}']`,
204
+ });
205
+ });
206
+ if (errors.length > 0) {
207
+ throw new StackValidationError(errors, ARGUMENTS_INVALID);
208
+ }
291
209
  }
292
210
  //# sourceMappingURL=grants.js.map