@haverstack/core 0.40.0 → 0.42.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 (287) hide show
  1. package/README.md +121 -59
  2. package/dist/access.d.ts +34 -36
  3. package/dist/access.d.ts.map +1 -1
  4. package/dist/access.js +65 -42
  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} +7 -0
  9. package/dist/adapter/combine.js.map +1 -0
  10. package/dist/{adapter-entry.d.ts → adapter/index.d.ts} +6 -5
  11. package/dist/adapter/index.d.ts.map +1 -0
  12. package/dist/{adapter-entry.js → adapter/index.js} +5 -4
  13. package/dist/adapter/index.js.map +1 -0
  14. package/dist/{sort.d.ts → adapter/sort.d.ts} +20 -20
  15. package/dist/adapter/sort.d.ts.map +1 -0
  16. package/dist/{sort.js → adapter/sort.js} +37 -27
  17. package/dist/adapter/sort.js.map +1 -0
  18. package/dist/associations/identity.d.ts +93 -0
  19. package/dist/associations/identity.d.ts.map +1 -0
  20. package/dist/associations/identity.js +198 -0
  21. package/dist/associations/identity.js.map +1 -0
  22. package/dist/associations/validation.d.ts +90 -0
  23. package/dist/associations/validation.d.ts.map +1 -0
  24. package/dist/associations/validation.js +339 -0
  25. package/dist/associations/validation.js.map +1 -0
  26. package/dist/attachment-records.d.ts +48 -0
  27. package/dist/attachment-records.d.ts.map +1 -0
  28. package/dist/attachment-records.js +52 -0
  29. package/dist/attachment-records.js.map +1 -0
  30. package/dist/collection.d.ts +182 -0
  31. package/dist/collection.d.ts.map +1 -0
  32. package/dist/collection.js +407 -0
  33. package/dist/collection.js.map +1 -0
  34. package/dist/content-match.d.ts +25 -0
  35. package/dist/content-match.d.ts.map +1 -0
  36. package/dist/content-match.js +51 -0
  37. package/dist/content-match.js.map +1 -0
  38. package/dist/content-translation.d.ts +42 -0
  39. package/dist/content-translation.d.ts.map +1 -0
  40. package/dist/content-translation.js +108 -0
  41. package/dist/content-translation.js.map +1 -0
  42. package/dist/did/did.d.ts.map +1 -0
  43. package/dist/{did.js → did/did.js} +3 -4
  44. package/dist/did/did.js.map +1 -0
  45. package/dist/{did-entry.d.ts → did/index.d.ts} +1 -1
  46. package/dist/did/index.d.ts.map +1 -0
  47. package/dist/{did-entry.js → did/index.js} +3 -3
  48. package/dist/did/index.js.map +1 -0
  49. package/dist/errors.d.ts +36 -63
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +35 -58
  52. package/dist/errors.js.map +1 -1
  53. package/dist/grants.d.ts +20 -43
  54. package/dist/grants.d.ts.map +1 -1
  55. package/dist/grants.js +71 -134
  56. package/dist/grants.js.map +1 -1
  57. package/dist/id.d.ts +13 -31
  58. package/dist/id.d.ts.map +1 -1
  59. package/dist/id.js +13 -31
  60. package/dist/id.js.map +1 -1
  61. package/dist/index.d.ts +14 -8
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +9 -5
  64. package/dist/index.js.map +1 -1
  65. package/dist/install.d.ts +141 -0
  66. package/dist/install.d.ts.map +1 -0
  67. package/dist/install.js +219 -0
  68. package/dist/install.js.map +1 -0
  69. package/dist/merge.d.ts.map +1 -1
  70. package/dist/merge.js +5 -11
  71. package/dist/merge.js.map +1 -1
  72. package/dist/migration-steps.d.ts +102 -0
  73. package/dist/migration-steps.d.ts.map +1 -0
  74. package/dist/migration-steps.js +293 -0
  75. package/dist/migration-steps.js.map +1 -0
  76. package/dist/query-validation.d.ts +28 -94
  77. package/dist/query-validation.d.ts.map +1 -1
  78. package/dist/query-validation.js +156 -336
  79. package/dist/query-validation.js.map +1 -1
  80. package/dist/record-changes.d.ts +2 -117
  81. package/dist/record-changes.d.ts.map +1 -1
  82. package/dist/record-changes.js +10 -212
  83. package/dist/record-changes.js.map +1 -1
  84. package/dist/schema.d.ts +18 -7
  85. package/dist/schema.d.ts.map +1 -1
  86. package/dist/schema.js +59 -30
  87. package/dist/schema.js.map +1 -1
  88. package/dist/scoped-stack/authority.d.ts +156 -0
  89. package/dist/scoped-stack/authority.d.ts.map +1 -0
  90. package/dist/scoped-stack/authority.js +384 -0
  91. package/dist/scoped-stack/authority.js.map +1 -0
  92. package/dist/scoped-stack/scoped-stack.d.ts +338 -0
  93. package/dist/scoped-stack/scoped-stack.d.ts.map +1 -0
  94. package/dist/scoped-stack/scoped-stack.js +776 -0
  95. package/dist/scoped-stack/scoped-stack.js.map +1 -0
  96. package/dist/scoped-stack/subscription.d.ts +55 -0
  97. package/dist/scoped-stack/subscription.d.ts.map +1 -0
  98. package/dist/scoped-stack/subscription.js +110 -0
  99. package/dist/scoped-stack/subscription.js.map +1 -0
  100. package/dist/stack/attachments.d.ts +24 -0
  101. package/dist/stack/attachments.d.ts.map +1 -0
  102. package/dist/stack/attachments.js +113 -0
  103. package/dist/stack/attachments.js.map +1 -0
  104. package/dist/{changes.d.ts → stack/changes.d.ts} +29 -65
  105. package/dist/stack/changes.d.ts.map +1 -0
  106. package/dist/{changes.js → stack/changes.js} +32 -66
  107. package/dist/stack/changes.js.map +1 -0
  108. package/dist/stack/client.d.ts +271 -0
  109. package/dist/stack/client.d.ts.map +1 -0
  110. package/dist/stack/client.js +9 -0
  111. package/dist/stack/client.js.map +1 -0
  112. package/dist/stack/identity-bindings.d.ts.map +1 -0
  113. package/dist/{identity-bindings.js → stack/identity-bindings.js} +8 -14
  114. package/dist/stack/identity-bindings.js.map +1 -0
  115. package/dist/stack/install-app.d.ts +22 -0
  116. package/dist/stack/install-app.d.ts.map +1 -0
  117. package/dist/stack/install-app.js +260 -0
  118. package/dist/stack/install-app.js.map +1 -0
  119. package/dist/stack/integrity.d.ts +94 -0
  120. package/dist/stack/integrity.d.ts.map +1 -0
  121. package/dist/stack/integrity.js +268 -0
  122. package/dist/stack/integrity.js.map +1 -0
  123. package/dist/{limits.d.ts → stack/limits.d.ts} +2 -5
  124. package/dist/stack/limits.d.ts.map +1 -0
  125. package/dist/{limits.js → stack/limits.js} +3 -6
  126. package/dist/stack/limits.js.map +1 -0
  127. package/dist/stack/migrations.d.ts +59 -0
  128. package/dist/stack/migrations.d.ts.map +1 -0
  129. package/dist/stack/migrations.js +141 -0
  130. package/dist/stack/migrations.js.map +1 -0
  131. package/dist/{stack-reads.d.ts → stack/reads.d.ts} +33 -6
  132. package/dist/stack/reads.d.ts.map +1 -0
  133. package/dist/{stack-reads.js → stack/reads.js} +49 -7
  134. package/dist/stack/reads.js.map +1 -0
  135. package/dist/stack/record-id.d.ts +35 -0
  136. package/dist/stack/record-id.d.ts.map +1 -0
  137. package/dist/stack/record-id.js +82 -0
  138. package/dist/stack/record-id.js.map +1 -0
  139. package/dist/stack/stack.d.ts +438 -0
  140. package/dist/stack/stack.d.ts.map +1 -0
  141. package/dist/stack/stack.js +1366 -0
  142. package/dist/stack/stack.js.map +1 -0
  143. package/dist/stack/system-types.d.ts +16 -0
  144. package/dist/stack/system-types.d.ts.map +1 -0
  145. package/dist/stack/system-types.js +111 -0
  146. package/dist/stack/system-types.js.map +1 -0
  147. package/dist/stack/type-grants.d.ts +20 -0
  148. package/dist/stack/type-grants.d.ts.map +1 -0
  149. package/dist/stack/type-grants.js +69 -0
  150. package/dist/stack/type-grants.js.map +1 -0
  151. package/dist/stack/write-expectation.d.ts +78 -0
  152. package/dist/stack/write-expectation.d.ts.map +1 -0
  153. package/dist/stack/write-expectation.js +71 -0
  154. package/dist/stack/write-expectation.js.map +1 -0
  155. package/dist/testing.d.ts +21 -26
  156. package/dist/testing.d.ts.map +1 -1
  157. package/dist/testing.js +50 -70
  158. package/dist/testing.js.map +1 -1
  159. package/dist/type-handle.d.ts +67 -78
  160. package/dist/type-handle.d.ts.map +1 -1
  161. package/dist/type-handle.js +44 -111
  162. package/dist/type-handle.js.map +1 -1
  163. package/dist/types/adapter.d.ts +223 -0
  164. package/dist/types/adapter.d.ts.map +1 -0
  165. package/dist/types/adapter.js +5 -0
  166. package/dist/types/adapter.js.map +1 -0
  167. package/dist/types/associations.d.ts +156 -0
  168. package/dist/types/associations.d.ts.map +1 -0
  169. package/dist/types/associations.js +13 -0
  170. package/dist/types/associations.js.map +1 -0
  171. package/dist/types/capabilities.d.ts +89 -0
  172. package/dist/types/capabilities.d.ts.map +1 -0
  173. package/dist/types/capabilities.js +5 -0
  174. package/dist/types/capabilities.js.map +1 -0
  175. package/dist/types/events.d.ts +155 -0
  176. package/dist/types/events.d.ts.map +1 -0
  177. package/dist/types/events.js +5 -0
  178. package/dist/types/events.js.map +1 -0
  179. package/dist/types/ids.d.ts +43 -0
  180. package/dist/types/ids.d.ts.map +1 -0
  181. package/dist/types/ids.js +5 -0
  182. package/dist/types/ids.js.map +1 -0
  183. package/dist/types/index.d.ts +19 -0
  184. package/dist/types/index.d.ts.map +1 -0
  185. package/dist/types/index.js +19 -0
  186. package/dist/types/index.js.map +1 -0
  187. package/dist/types/journal.d.ts +47 -0
  188. package/dist/types/journal.d.ts.map +1 -0
  189. package/dist/types/journal.js +5 -0
  190. package/dist/types/journal.js.map +1 -0
  191. package/dist/types/query.d.ts +185 -0
  192. package/dist/types/query.d.ts.map +1 -0
  193. package/dist/types/query.js +10 -0
  194. package/dist/types/query.js.map +1 -0
  195. package/dist/types/records.d.ts +76 -0
  196. package/dist/types/records.d.ts.map +1 -0
  197. package/dist/types/records.js +5 -0
  198. package/dist/types/records.js.map +1 -0
  199. package/dist/types/schema.d.ts +74 -0
  200. package/dist/types/schema.d.ts.map +1 -0
  201. package/dist/types/schema.js +5 -0
  202. package/dist/types/schema.js.map +1 -0
  203. package/dist/types/system.d.ts +161 -0
  204. package/dist/types/system.d.ts.map +1 -0
  205. package/dist/types/system.js +32 -0
  206. package/dist/types/system.js.map +1 -0
  207. package/dist/types/tokens.d.ts +39 -0
  208. package/dist/types/tokens.d.ts.map +1 -0
  209. package/dist/types/tokens.js +5 -0
  210. package/dist/types/tokens.js.map +1 -0
  211. package/dist/validate.d.ts +29 -70
  212. package/dist/validate.d.ts.map +1 -1
  213. package/dist/validate.js +65 -97
  214. package/dist/validate.js.map +1 -1
  215. package/dist/{attachment-download.d.ts → wire/attachment-download.d.ts} +0 -57
  216. package/dist/wire/attachment-download.d.ts.map +1 -0
  217. package/dist/{attachment-download.js → wire/attachment-download.js} +0 -61
  218. package/dist/wire/attachment-download.js.map +1 -0
  219. package/dist/{auth.d.ts → wire/auth.d.ts} +5 -8
  220. package/dist/wire/auth.d.ts.map +1 -0
  221. package/dist/{auth.js → wire/auth.js} +5 -8
  222. package/dist/wire/auth.js.map +1 -0
  223. package/dist/{wire-body.d.ts → wire/body.d.ts} +13 -5
  224. package/dist/wire/body.d.ts.map +1 -0
  225. package/dist/{wire-body.js → wire/body.js} +65 -7
  226. package/dist/wire/body.js.map +1 -0
  227. package/dist/{wire-entry.d.ts → wire/index.d.ts} +12 -12
  228. package/dist/wire/index.d.ts.map +1 -0
  229. package/dist/{wire-entry.js → wire/index.js} +13 -17
  230. package/dist/wire/index.js.map +1 -0
  231. package/dist/{wire-record.d.ts → wire/record.d.ts} +10 -22
  232. package/dist/wire/record.d.ts.map +1 -0
  233. package/dist/{wire-record.js → wire/record.js} +12 -24
  234. package/dist/wire/record.js.map +1 -0
  235. package/dist/{wire-request.d.ts → wire/request.d.ts} +20 -37
  236. package/dist/wire/request.d.ts.map +1 -0
  237. package/dist/{wire-request.js → wire/request.js} +79 -54
  238. package/dist/wire/request.js.map +1 -0
  239. package/package.json +7 -7
  240. package/dist/adapter-entry.d.ts.map +0 -1
  241. package/dist/adapter-entry.js.map +0 -1
  242. package/dist/attachment-download.d.ts.map +0 -1
  243. package/dist/attachment-download.js.map +0 -1
  244. package/dist/auth.d.ts.map +0 -1
  245. package/dist/auth.js.map +0 -1
  246. package/dist/changes.d.ts.map +0 -1
  247. package/dist/changes.js.map +0 -1
  248. package/dist/combine.d.ts.map +0 -1
  249. package/dist/combine.js.map +0 -1
  250. package/dist/did-entry.d.ts.map +0 -1
  251. package/dist/did-entry.js.map +0 -1
  252. package/dist/did.d.ts.map +0 -1
  253. package/dist/did.js.map +0 -1
  254. package/dist/identity-bindings.d.ts.map +0 -1
  255. package/dist/identity-bindings.js.map +0 -1
  256. package/dist/limits.d.ts.map +0 -1
  257. package/dist/limits.js.map +0 -1
  258. package/dist/record-id.d.ts +0 -70
  259. package/dist/record-id.d.ts.map +0 -1
  260. package/dist/record-id.js +0 -127
  261. package/dist/record-id.js.map +0 -1
  262. package/dist/scoped-stack.d.ts +0 -532
  263. package/dist/scoped-stack.d.ts.map +0 -1
  264. package/dist/scoped-stack.js +0 -1219
  265. package/dist/scoped-stack.js.map +0 -1
  266. package/dist/sort.d.ts.map +0 -1
  267. package/dist/sort.js.map +0 -1
  268. package/dist/stack-reads.d.ts.map +0 -1
  269. package/dist/stack-reads.js.map +0 -1
  270. package/dist/stack.d.ts +0 -971
  271. package/dist/stack.d.ts.map +0 -1
  272. package/dist/stack.js +0 -2012
  273. package/dist/stack.js.map +0 -1
  274. package/dist/types.d.ts +0 -1250
  275. package/dist/types.d.ts.map +0 -1
  276. package/dist/types.js +0 -49
  277. package/dist/types.js.map +0 -1
  278. package/dist/wire-body.d.ts.map +0 -1
  279. package/dist/wire-body.js.map +0 -1
  280. package/dist/wire-entry.d.ts.map +0 -1
  281. package/dist/wire-entry.js.map +0 -1
  282. package/dist/wire-record.d.ts.map +0 -1
  283. package/dist/wire-record.js.map +0 -1
  284. package/dist/wire-request.d.ts.map +0 -1
  285. package/dist/wire-request.js.map +0 -1
  286. /package/dist/{did.d.ts → did/did.d.ts} +0 -0
  287. /package/dist/{identity-bindings.d.ts → stack/identity-bindings.d.ts} +0 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Association validation
3
+ * -------------------------------------------------------
4
+ * The rules an association is held to before it is stored: a known kind,
5
+ * only the keys that kind defines, a well-formed target or grantee, the
6
+ * right half of the authority/data partition, and each identity named
7
+ * once. A discriminated union is a compile-time promise, and a server
8
+ * mapping a request body onto one supplies raw JSON no compiler has seen,
9
+ * so the rules live in the invariant layer where no adapter can forget
10
+ * one. See docs/spec/data-model.md § Associations.
11
+ */
12
+ import { ARGUMENTS_INVALID, StackBadRequestError, StackValidationError } from '../errors.js';
13
+ import { associationEqual, isAuthorityAssociation } from './identity.js';
14
+ /** The identifier spaces a relationship target may name. */
15
+ const TARGET_KINDS = new Set(['record', 'entity', 'external']);
16
+ /**
17
+ * The keys of one arm of a union, listed as an object so the compiler
18
+ * holds the list to the type: a field added to the arm fails to compile
19
+ * here until it is listed.
20
+ */
21
+ const keysOf = (keys) => Object.keys(keys);
22
+ /** Every key each relationship target arm defines. */
23
+ export const TARGET_KEYS = {
24
+ record: keysOf({ kind: true, recordId: true, stackUrl: true }),
25
+ entity: keysOf({ kind: true, entityId: true }),
26
+ external: keysOf({ kind: true, ns: true, id: true }),
27
+ };
28
+ /** Every key each association kind defines. */
29
+ const ASSOCIATION_KEYS = {
30
+ tag: keysOf({ kind: true, label: true }),
31
+ attachment: keysOf({
32
+ kind: true,
33
+ label: true,
34
+ fileId: true,
35
+ attachmentRecordId: true,
36
+ }),
37
+ relationship: keysOf({ kind: true, label: true, target: true }),
38
+ permission: keysOf({ kind: true, label: true, grantee: true }),
39
+ anyone: keysOf({ kind: true, label: true }),
40
+ };
41
+ /** Every key each grantee arm defines, the default tier included. */
42
+ export const GRANTEE_KEYS = {
43
+ entity: keysOf({ kind: true, entityId: true }),
44
+ group: keysOf({ kind: true, groupId: true, role: true }),
45
+ authenticated: keysOf({ kind: true }),
46
+ };
47
+ /**
48
+ * The keys of `value` its arm does not define. An element carrying one is
49
+ * refused rather than stored: an adapter keeps only the keys it has
50
+ * columns for, so the rest would answer 200 and then vanish.
51
+ * See docs/spec/data-model.md § Associations.
52
+ */
53
+ export function unknownKeys(value, keys) {
54
+ return Object.keys(value).filter((key) => !keys.includes(key));
55
+ }
56
+ /** unknownKeys() as a refusal. 400, not 422: the key addresses nothing. */
57
+ export function assertKnownKeys(value, keys, path) {
58
+ const unknown = unknownKeys(value, keys);
59
+ if (unknown.length > 0)
60
+ throw new StackBadRequestError(`Unknown key${unknown.length > 1 ? 's' : ''} in ${path}: ${unknown.join(', ')}`);
61
+ }
62
+ /**
63
+ * Collect what makes a relationship target malformed. Absence is
64
+ * meaningful on `stackUrl` and an external `id` — this stack, and the
65
+ * whole namespace — so every part that names something must be non-empty:
66
+ * an empty string stores and matches as though it were absent.
67
+ * See docs/spec/data-model.md § Relationship targets.
68
+ */
69
+ export function targetErrors(target, path, opts = {}) {
70
+ const fail = (message) => [{ path, message }];
71
+ if (!target || typeof target !== 'object')
72
+ return fail('A relationship target must be an object.');
73
+ if (!TARGET_KINDS.has(target.kind)) {
74
+ return fail(`Unknown relationship target kind "${target.kind}": expected "record", "entity" or "external".`);
75
+ }
76
+ assertKnownKeys(target, TARGET_KEYS[target.kind], path);
77
+ if (target.kind === 'record') {
78
+ if (!target.recordId)
79
+ return fail('A record target requires a non-empty recordId.');
80
+ if (target.stackUrl !== undefined && !target.stackUrl) {
81
+ return fail("A record target's stackUrl must be non-empty; omit it to name this stack.");
82
+ }
83
+ return [];
84
+ }
85
+ if (target.kind === 'entity') {
86
+ return target.entityId ? [] : fail('An entity target requires a non-empty entityId.');
87
+ }
88
+ if (!target.ns)
89
+ return fail('An external target requires a non-empty ns.');
90
+ if (target.id === undefined) {
91
+ return opts.externalIdOptional ? [] : fail('An external target requires an id.');
92
+ }
93
+ return target.id ? [] : fail("An external target's id must be non-empty when present.");
94
+ }
95
+ /** The kinds an association may name — the closed set every surface reads. */
96
+ const ASSOCIATION_KINDS = new Set(['tag', 'attachment', 'relationship', 'permission', 'anyone']);
97
+ /**
98
+ * Whether a value is an association at all. Asked ahead of the partition
99
+ * checks, so a malformed element is named as one rather than reported as
100
+ * the wrong surface for a kind it never had — a request body supplies raw
101
+ * JSON, and `null` or `{}` is neither half of the partition.
102
+ */
103
+ function namesKind(association) {
104
+ return (!!association &&
105
+ typeof association === 'object' &&
106
+ ASSOCIATION_KINDS.has(association.kind));
107
+ }
108
+ /**
109
+ * Reject an association outside the closed set the types promise: a request
110
+ * body is raw JSON, and an unknown target kind would otherwise be stored as
111
+ * a Record in this stack. A key its kind does not define is a 400 at every
112
+ * depth, as on a request body. See docs/spec/data-model.md § Associations.
113
+ */
114
+ export function validateAssociation(association, path = 'association') {
115
+ if (!namesKind(association)) {
116
+ return [
117
+ {
118
+ path: `${path}.kind`,
119
+ message: `Unknown association kind "${String(association?.kind)}": expected ${[...ASSOCIATION_KINDS].map((k) => `"${k}"`).join(', ')}.`,
120
+ },
121
+ ];
122
+ }
123
+ assertKnownKeys(association, ASSOCIATION_KEYS[association.kind], path);
124
+ if (association.kind === 'permission') {
125
+ return permissionErrors(association, path);
126
+ }
127
+ if (association.kind === 'anyone') {
128
+ return association.label === 'read'
129
+ ? []
130
+ : [{ path: `${path}.label`, message: 'An `anyone` association carries only `read`.' }];
131
+ }
132
+ if (association.kind !== 'relationship')
133
+ return [];
134
+ return targetErrors(association.target, `${path}.target`);
135
+ }
136
+ /**
137
+ * Who a duplicated permission names, since kind and label alone don't
138
+ * distinguish two grants of the same bit to different grantees.
139
+ */
140
+ function granteeSuffix(association) {
141
+ if (association.kind !== 'permission')
142
+ return '';
143
+ const g = association.grantee;
144
+ return g.kind === 'entity' ? ` for "${g.entityId}"` : ` for ${g.role}s of "${g.groupId}"`;
145
+ }
146
+ /** The bits a permission element may name — the whole of its meaning. */
147
+ const PERMISSION_LABELS = new Set(['read', 'write']);
148
+ /** A permission element's label and grantee — see granteeErrors(). */
149
+ function permissionErrors(association, path) {
150
+ if (!PERMISSION_LABELS.has(association.label)) {
151
+ return [
152
+ {
153
+ path: `${path}.label`,
154
+ message: `Unknown permission label "${association.label}": expected "read" or "write".`,
155
+ },
156
+ ];
157
+ }
158
+ const grantee = association.grantee;
159
+ if (!grantee || typeof grantee !== 'object') {
160
+ return [{ path: `${path}.grantee`, message: 'A permission requires a grantee.' }];
161
+ }
162
+ return granteeErrors(grantee, `${path}.grantee`, { unknownKeys: 'throw' });
163
+ }
164
+ /**
165
+ * What makes a grantee malformed, under `path`: first a key its tier does not
166
+ * define — a 400 under `throw`, as on a request body, or collected on a
167
+ * stored `_grant` — then its tier's own fields. `role` has no default to fall
168
+ * back on. `authenticated` is a `_grant` tier alone; `anyRole` admits `'any'`.
169
+ */
170
+ export function granteeErrors(grantee, path, opts) {
171
+ const g = (grantee ?? {});
172
+ const tier = g.kind === 'entity' || g.kind === 'group' || (opts.authenticated && g.kind === 'authenticated')
173
+ ? g.kind
174
+ : null;
175
+ if (tier === null) {
176
+ return [
177
+ {
178
+ path: `${path}.kind`,
179
+ message: opts.authenticated
180
+ ? "A grantee must name its tier: 'entity', 'group' or 'authenticated'."
181
+ : "A grantee must name its tier: 'entity' or 'group'.",
182
+ },
183
+ ];
184
+ }
185
+ if (opts.unknownKeys === 'throw')
186
+ assertKnownKeys(g, GRANTEE_KEYS[tier], path);
187
+ const unknown = unknownKeys(g, GRANTEE_KEYS[tier]);
188
+ if (unknown.length > 0) {
189
+ const article = tier === 'group' ? 'A' : 'An';
190
+ return unknown.map((key) => ({
191
+ path: `${path}.${key}`,
192
+ message: `${article} ${tier} grantee does not carry ${key}.`,
193
+ }));
194
+ }
195
+ if (tier === 'authenticated')
196
+ return [];
197
+ if (tier === 'entity') {
198
+ return typeof g.entityId === 'string' && g.entityId.length > 0
199
+ ? []
200
+ : [{ path: `${path}.entityId`, message: 'An entity grantee requires a non-empty entityId.' }];
201
+ }
202
+ const errors = [];
203
+ if (typeof g.groupId !== 'string' || g.groupId.length === 0) {
204
+ errors.push({
205
+ path: `${path}.groupId`,
206
+ message: 'A group grantee requires a non-empty groupId.',
207
+ });
208
+ }
209
+ if (g.role !== 'member' && g.role !== 'admin' && !(opts.anyRole && g.role === 'any')) {
210
+ errors.push({
211
+ path: `${path}.role`,
212
+ message: opts.anyRole
213
+ ? "A group grantee requires role 'member', 'admin' or 'any'."
214
+ : "A group grantee requires role 'member' or 'admin'.",
215
+ });
216
+ }
217
+ return errors;
218
+ }
219
+ /**
220
+ * Refuse an authority element on the data surface. The two halves share
221
+ * storage but never a call, or an app editing tags could replace an ACL it
222
+ * was never shown. Thrown, not collected: the caller named the wrong
223
+ * surface, and wanted grantAccess()/revokeAccess().
224
+ * See docs/spec/access-control.md § Record-level permissions.
225
+ */
226
+ export function assertDataAssociations(associations, surface) {
227
+ const authority = associations.find((a) => namesKind(a) && isAuthorityAssociation(a));
228
+ if (!authority)
229
+ return;
230
+ throw new StackBadRequestError(`${surface} does not carry authority: a "${authority.kind}" association belongs to the ` +
231
+ '`permissions` surface — use grantAccess()/revokeAccess(), or the `permissions` change-set key.');
232
+ }
233
+ /**
234
+ * The mirror of assertDataAssociations(): the `permissions` surface takes
235
+ * authority kinds alone, so a tag routed through it is refused rather than
236
+ * quietly becoming an ACL entry the `associations` projection never shows.
237
+ */
238
+ export function assertAuthorityAssociations(permissions, surface) {
239
+ const data = permissions.find((a) => namesKind(a) && !isAuthorityAssociation(a));
240
+ if (!data)
241
+ return;
242
+ throw new StackBadRequestError(`${surface} carries authority alone: a "${data.kind}" association belongs to the ` +
243
+ '`associations` surface — use associate()/dissociate(), or the `associations` change-set key.');
244
+ }
245
+ /**
246
+ * Every association in a list, plus the list's own rule: identities are
247
+ * distinct. An adapter keys associations by identity, so a repeat would
248
+ * displace the first and leave which one stands to chance — a caller bug,
249
+ * refused rather than collapsed. See docs/spec/data-model.md § Associations.
250
+ */
251
+ export function validateAssociations(associations, path = 'associations') {
252
+ const list = associations ?? [];
253
+ return [
254
+ ...list.flatMap((a, i) => validateAssociation(a, `${path}[${i}]`)),
255
+ ...duplicateIdentityErrors(list, path),
256
+ ];
257
+ }
258
+ /** Each element naming an identity an earlier element already named. */
259
+ function duplicateIdentityErrors(list, path) {
260
+ return list.flatMap((a, i) => list.slice(0, i).some((b) => associationEqual(a, b))
261
+ ? [
262
+ {
263
+ path: `${path}[${i}]`,
264
+ message: `Duplicate association identity: ${a.kind} "${a.label}"${granteeSuffix(a)} is named more than once.`,
265
+ },
266
+ ]
267
+ : []);
268
+ }
269
+ /**
270
+ * Hold an edit list to the rules every verb shares: non-empty, known `op`s
271
+ * over well-formed associations, one surface, each identity once — removing
272
+ * and adding one identity is as ambiguous as naming it twice. `repoint` is
273
+ * the journal's word, never a request. See docs/spec/data-model.md § Mutations.
274
+ */
275
+ export function assertAssociationEdits(changes, surface, half) {
276
+ if (!Array.isArray(changes) || changes.length === 0) {
277
+ throw new StackValidationError([{ path: 'changes', message: `${surface} names at least one change.` }], ARGUMENTS_INVALID);
278
+ }
279
+ const errors = [];
280
+ changes.forEach((raw, i) => {
281
+ const path = `changes[${i}]`;
282
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
283
+ errors.push({ path, message: `${path} must be an object.` });
284
+ return;
285
+ }
286
+ const edit = raw;
287
+ if (edit.op !== 'repoint')
288
+ assertKnownKeys(edit, ['op', 'association'], path);
289
+ if (edit.op === 'repoint') {
290
+ errors.push({
291
+ path: `${path}.op`,
292
+ message: 'op: "repoint" is recorded by the journal, not requested. ' +
293
+ "Send { op: 'add', association }: an add naming an attachment the record already holds re-points it in place.",
294
+ });
295
+ }
296
+ else if (edit.op !== 'add' && edit.op !== 'remove') {
297
+ errors.push({ path: `${path}.op`, message: 'op must be "add" or "remove".' });
298
+ }
299
+ errors.push(...validateAssociation(edit.association, `${path}.association`));
300
+ });
301
+ if (errors.length > 0)
302
+ throw new StackValidationError(errors, ARGUMENTS_INVALID);
303
+ const associations = changes.map((c) => c.association);
304
+ if (half === 'data')
305
+ assertDataAssociations(associations, surface);
306
+ else
307
+ assertAuthorityAssociations(associations, surface);
308
+ const duplicates = duplicateIdentityErrors(associations, 'changes');
309
+ if (duplicates.length > 0)
310
+ throw new StackValidationError(duplicates, ARGUMENTS_INVALID);
311
+ }
312
+ /**
313
+ * assertAssociationEdits() for the verbs taking a bare list — associate(),
314
+ * grantAccess() and their inverses — asked before wrapping, so problems are
315
+ * reported under `param`, the name the caller used. Same order of checks,
316
+ * so a wrong-surface element is refused as such before duplicates count.
317
+ */
318
+ export function assertAssociationList(associations, surface, param, half) {
319
+ if (!Array.isArray(associations) || associations.length === 0) {
320
+ throw new StackValidationError([{ path: param, message: `${surface} names at least one association.` }], ARGUMENTS_INVALID);
321
+ }
322
+ const errors = associations.flatMap((raw, i) => typeof raw === 'object' && raw !== null && !Array.isArray(raw)
323
+ ? []
324
+ : [{ path: `${param}[${i}]`, message: `${param}[${i}] must be an object.` }]);
325
+ if (errors.length > 0)
326
+ throw new StackValidationError(errors, ARGUMENTS_INVALID);
327
+ const list = associations;
328
+ const shapeErrors = list.flatMap((a, i) => validateAssociation(a, `${param}[${i}]`));
329
+ if (shapeErrors.length > 0)
330
+ throw new StackValidationError(shapeErrors, ARGUMENTS_INVALID);
331
+ if (half === 'data')
332
+ assertDataAssociations(list, surface);
333
+ else
334
+ assertAuthorityAssociations(list, surface);
335
+ const listErrors = validateAssociations(list, param);
336
+ if (listErrors.length > 0)
337
+ throw new StackValidationError(listErrors, ARGUMENTS_INVALID);
338
+ }
339
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/associations/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAqBzE,4DAA4D;AAC5D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,GAAG,CAAI,IAA2B,EAAqB,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAExF,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAA0D;IAChF,MAAM,EAAE,MAAM,CAAe,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5E,MAAM,EAAE,MAAM,CAAe,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5D,QAAQ,EAAE,MAAM,CAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;CACrE,CAAC;AAEF,+CAA+C;AAC/C,MAAM,gBAAgB,GAAmD;IACvE,GAAG,EAAE,MAAM,CAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACxD,UAAU,EAAE,MAAM,CAAwB;QACxC,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,IAAI;QACZ,kBAAkB,EAAE,IAAI;KACzB,CAAC;IACF,YAAY,EAAE,MAAM,CAA0B,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACxF,UAAU,EAAE,MAAM,CAAwB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACrF,MAAM,EAAE,MAAM,CAAoB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;CAC/D,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAoD;IAC3E,MAAM,EAAE,MAAM,CAAuC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACpF,KAAK,EAAE,MAAM,CAAsC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7F,aAAa,EAAE,MAAM,CAAmD,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;CACxF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,IAAuB;IAChE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,IAAuB,EAAE,IAAY;IAClF,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QACpB,MAAM,IAAI,oBAAoB,CAC5B,cAAc,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChF,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAsD,EACtD,IAAY,EACZ,OAAyC,EAAE;IAE3C,MAAM,IAAI,GAAG,CAAC,OAAe,EAAqB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QACvC,OAAO,IAAI,CAAC,0CAA0C,CAAC,CAAC;IAC1D,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,CACT,qCAAqC,MAAM,CAAC,IAAI,+CAA+C,CAChG,CAAC;IACJ,CAAC;IACD,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC,gDAAgD,CAAC,CAAC;QACpF,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,2EAA2E,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,6CAA6C,CAAC,CAAC;IAC3E,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;AAC1F,CAAC;AAED,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEjG;;;;;GAKG;AACH,SAAS,SAAS,CAAC,WAAwB;IACzC,OAAO,CACL,CAAC,CAAC,WAAW;QACb,OAAO,WAAW,KAAK,QAAQ;QAC/B,iBAAiB,CAAC,GAAG,CAAE,WAAkC,CAAC,IAAc,CAAC,CAC1E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAwB,EACxB,IAAI,GAAG,aAAa;IAEpB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL;gBACE,IAAI,EAAE,GAAG,IAAI,OAAO;gBACpB,OAAO,EAAE,6BAA6B,MAAM,CAAE,WAAkC,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aAChK;SACF,CAAC;IACJ,CAAC;IACD,eAAe,CAAC,WAAW,EAAE,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACvE,IAAI,WAAW,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACtC,OAAO,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,WAAW,CAAC,KAAK,KAAK,MAAM;YACjC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,QAAQ,EAAE,OAAO,EAAE,8CAA8C,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,KAAK,cAAc;QAAE,OAAO,EAAE,CAAC;IACnD,OAAO,YAAY,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,WAAwB;IAC7C,IAAI,WAAW,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,EAAE,CAAC;IACjD,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC;IAC9B,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC;AAC5F,CAAC;AAED,yEAAyE;AACzE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAErD,sEAAsE;AACtE,SAAS,gBAAgB,CACvB,WAAgD,EAChD,IAAY;IAEZ,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,OAAO;YACL;gBACE,IAAI,EAAE,GAAG,IAAI,QAAQ;gBACrB,OAAO,EAAE,6BAA6B,WAAW,CAAC,KAAK,gCAAgC;aACxF;SACF,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;IACpC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,UAAU,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,aAAa,CAAC,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,IAAY,EACZ,IAAsF;IAEtF,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAuE,CAAC;IAChG,MAAM,IAAI,GACR,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC;QAC7F,CAAC,CAAC,CAAC,CAAC,IAAI;QACR,CAAC,CAAC,IAAI,CAAC;IACX,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO;YACL;gBACE,IAAI,EAAE,GAAG,IAAI,OAAO;gBACpB,OAAO,EAAE,IAAI,CAAC,aAAa;oBACzB,CAAC,CAAC,qEAAqE;oBACvE,CAAC,CAAC,oDAAoD;aACzD;SACF,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,WAAW,KAAK,OAAO;QAAE,eAAe,CAAC,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC3B,IAAI,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE;YACtB,OAAO,EAAE,GAAG,OAAO,IAAI,IAAI,2BAA2B,GAAG,GAAG;SAC7D,CAAC,CAAC,CAAC;IACN,CAAC;IACD,IAAI,IAAI,KAAK,eAAe;QAAE,OAAO,EAAE,CAAC;IACxC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;YAC5D,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,WAAW,EAAE,OAAO,EAAE,kDAAkD,EAAE,CAAC,CAAC;IAClG,CAAC;IACD,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,GAAG,IAAI,UAAU;YACvB,OAAO,EAAE,+CAA+C;SACzD,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QACrF,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,GAAG,IAAI,OAAO;YACpB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACnB,CAAC,CAAC,2DAA2D;gBAC7D,CAAC,CAAC,oDAAoD;SACzD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,YAAoC,EACpC,OAAe;IAEf,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,IAAI,CAAC,SAAS;QAAE,OAAO;IACvB,MAAM,IAAI,oBAAoB,CAC5B,GAAG,OAAO,iCAAiC,SAAS,CAAC,IAAI,+BAA+B;QACtF,gGAAgG,CACnG,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,WAAmC,EACnC,OAAe;IAEf,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,IAAI,oBAAoB,CAC5B,GAAG,OAAO,gCAAgC,IAAI,CAAC,IAAI,+BAA+B;QAChF,8FAA8F,CACjG,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,YAAuC,EACvC,IAAI,GAAG,cAAc;IAErB,MAAM,IAAI,GAAG,YAAY,IAAI,EAAE,CAAC;IAChC,OAAO;QACL,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;QAClE,GAAG,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC;KACvC,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,SAAS,uBAAuB,CAAC,IAAmB,EAAE,IAAY;IAChE,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC3B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC;YACE;gBACE,IAAI,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG;gBACrB,OAAO,EAAE,mCAAmC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC,2BAA2B;aAC9G;SACF;QACH,CAAC,CAAC,EAAE,CACP,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAgB,EAChB,OAAe,EACf,IAA0B;IAE1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,oBAAoB,CAC5B,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,6BAA6B,EAAE,CAAC,EACvE,iBAAiB,CAClB,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAY,EAAE,CAAC,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC;QAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAClE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,qBAAqB,EAAE,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,GAA8C,CAAC;QAC5D,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS;YAAE,eAAe,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9E,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,GAAG,IAAI,KAAK;gBAClB,OAAO,EACL,2DAA2D;oBAC3D,8GAA8G;aACjH,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,IAAI,CAAC,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,KAAK,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,WAA0B,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAEjF,MAAM,YAAY,GAAI,OAA6B,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC9E,IAAI,IAAI,KAAK,MAAM;QAAE,sBAAsB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;;QAC9D,2BAA2B,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAExD,MAAM,UAAU,GAAG,uBAAuB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACpE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,YAAqB,EACrB,OAAe,EACf,KAAa,EACb,IAA0B;IAE1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,oBAAoB,CAC5B,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,kCAAkC,EAAE,CAAC,EACxE,iBAAiB,CAClB,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,GAAY,EAAE,CAAC,EAAE,EAAE,CACtD,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAC5D,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAC/E,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,YAA6B,CAAC;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,oBAAoB,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAC3F,IAAI,IAAI,KAAK,MAAM;QAAE,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;;QACtD,2BAA2B,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACrD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Attachment metadata records
3
+ * -------------------------------------------------------
4
+ * Which of a fileId's `_attachment` records answers for it: the one that
5
+ * establishes its mimeType, and the one a given reference names. Pure and
6
+ * generic over the record shape, so `Stack` and a server's own rows reach
7
+ * the same answer — `@haverstack/core/wire` exports the selections, and
8
+ * this module imports nothing so that entry point stays light.
9
+ */
10
+ /**
11
+ * Which `_attachment@1` record establishes a fileId's mimeType: earliest
12
+ * `createdAt`, ties to the lower `id`. Two racing first uploads can both
13
+ * land, so determinism rests on core's conflict check and every server
14
+ * ordering through here. The caller filters to one fileId.
15
+ * See docs/spec/attachments.md § Finding a `fileId`'s metadata records.
16
+ */
17
+ export declare function firstRecordedAttachment<T extends {
18
+ id: string;
19
+ createdAt: Date;
20
+ }>(records: readonly T[]): T | undefined;
21
+ /**
22
+ * The same total order as a comparator, for a caller handing back the whole
23
+ * candidate set rather than the winner — sorted this way, the winner is
24
+ * element zero. Package-internal: `core/wire` exports the selection,
25
+ * since the ordering is only useful to something that already has every
26
+ * record.
27
+ */
28
+ export declare function compareRecordedAttachments<T extends {
29
+ id: string;
30
+ createdAt: Date;
31
+ }>(a: T, b: T): number;
32
+ /**
33
+ * Which `_attachment` record a *reference* to a fileId names: its
34
+ * `attachmentRecordId`, else the requester's own upload, else the
35
+ * first-recorded — each step falling back rather than failing.
36
+ * See docs/spec/attachments.md § Naming the upload a reference came from.
37
+ */
38
+ export declare function resolveReferencedAttachment<T extends {
39
+ id: string;
40
+ createdAt: Date;
41
+ createdBy?: {
42
+ subjectId: string;
43
+ };
44
+ }>(records: readonly T[], opts?: {
45
+ attachmentRecordId?: string;
46
+ requesterEntityId?: string;
47
+ }): T | undefined;
48
+ //# sourceMappingURL=attachment-records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-records.d.ts","sourceRoot":"","sources":["../src/attachment-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,EAC/E,OAAO,EAAE,SAAS,CAAC,EAAE,GACpB,CAAC,GAAG,SAAS,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,EAClF,CAAC,EAAE,CAAC,EACJ,CAAC,EAAE,CAAC,GACH,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EAE5E,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,IAAI,GAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GACrE,CAAC,GAAG,SAAS,CAUf"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Attachment metadata records
3
+ * -------------------------------------------------------
4
+ * Which of a fileId's `_attachment` records answers for it: the one that
5
+ * establishes its mimeType, and the one a given reference names. Pure and
6
+ * generic over the record shape, so `Stack` and a server's own rows reach
7
+ * the same answer — `@haverstack/core/wire` exports the selections, and
8
+ * this module imports nothing so that entry point stays light.
9
+ */
10
+ /**
11
+ * Which `_attachment@1` record establishes a fileId's mimeType: earliest
12
+ * `createdAt`, ties to the lower `id`. Two racing first uploads can both
13
+ * land, so determinism rests on core's conflict check and every server
14
+ * ordering through here. The caller filters to one fileId.
15
+ * See docs/spec/attachments.md § Finding a `fileId`'s metadata records.
16
+ */
17
+ export function firstRecordedAttachment(records) {
18
+ return records.reduce((first, record) => (!first || compareRecordedAttachments(record, first) < 0 ? record : first), undefined);
19
+ }
20
+ /**
21
+ * The same total order as a comparator, for a caller handing back the whole
22
+ * candidate set rather than the winner — sorted this way, the winner is
23
+ * element zero. Package-internal: `core/wire` exports the selection,
24
+ * since the ordering is only useful to something that already has every
25
+ * record.
26
+ */
27
+ export function compareRecordedAttachments(a, b) {
28
+ const delta = a.createdAt.getTime() - b.createdAt.getTime();
29
+ if (delta !== 0)
30
+ return delta;
31
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
32
+ }
33
+ /**
34
+ * Which `_attachment` record a *reference* to a fileId names: its
35
+ * `attachmentRecordId`, else the requester's own upload, else the
36
+ * first-recorded — each step falling back rather than failing.
37
+ * See docs/spec/attachments.md § Naming the upload a reference came from.
38
+ */
39
+ export function resolveReferencedAttachment(records, opts = {}) {
40
+ if (opts.attachmentRecordId) {
41
+ const named = records.find((record) => record.id === opts.attachmentRecordId);
42
+ if (named)
43
+ return named;
44
+ }
45
+ if (opts.requesterEntityId) {
46
+ const own = records.filter((record) => record.createdBy?.subjectId === opts.requesterEntityId);
47
+ if (own.length > 0)
48
+ return firstRecordedAttachment(own);
49
+ }
50
+ return firstRecordedAttachment(records);
51
+ }
52
+ //# sourceMappingURL=attachment-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-records.js","sourceRoot":"","sources":["../src/attachment-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAqB;IAErB,OAAO,OAAO,CAAC,MAAM,CACnB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,0BAA0B,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAC7F,SAAS,CACV,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,CAAI,EACJ,CAAI;IAEJ,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC5D,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CAGzC,OAAqB,EACrB,OAAoE,EAAE;IAEtE,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9E,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC/F,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Collections — typed access to one type family
3
+ * -------------------------------------------------------
4
+ * `client.collection(handle)` returns a `Collection<C>`: every verb that
5
+ * addresses records of the handle's family, each taking and returning the
6
+ * handle's content type with one signature. Implemented once over the
7
+ * public `StackClient` verbs, so `Stack` and `ScopedStack` share it and a
8
+ * collection can do nothing its client could not.
9
+ *
10
+ * The collection's scope is the handle's whole family. Each record is read
11
+ * as stored and migrated in memory to the handle's version, one at a time,
12
+ * so a record the handle cannot represent — one stored at a newer version —
13
+ * is reported as a misfit rather than failing the page it sits on. A
14
+ * content write to a record at an older version commits its migration as a
15
+ * write of its own first. See docs/spec/data-model.md § Type handles.
16
+ */
17
+ import type { MigrationRegistry } from './stack/migrations.js';
18
+ import type { CreateRecordOptions, DeleteRecordOptions, DeleteResult, StackClient } from './stack/client.js';
19
+ import type { ContentOf, ReadonlyTypeSchema, TypeHandle, UnlistedValue } from './type-handle.js';
20
+ import type { AssociationEdit, AuthorityAssociation, ChangeFilter, DataAssociation, IfVersionOptions, NativeSortField, RecordChange, RecordChangeSet, RecordFilter, RecordId, StackQuery, StackType, StackRecord, SubscribeOptions, Unsubscribe } from './types/index.js';
21
+ /**
22
+ * Content `C` as a read returns it: an enum may hold a value the handle
23
+ * does not list, since an enum can widen in place. Never flattens `C`, so
24
+ * an app that names its content with an `interface` keeps that name.
25
+ */
26
+ export type StoredContent<C> = C extends string ? string extends C ? C : C | UnlistedValue : C extends readonly (infer I)[] ? StoredContent<I>[] : C extends object ? {
27
+ [K in keyof C]: StoredContent<C[K]>;
28
+ } : C;
29
+ /**
30
+ * The `contentPatch` content `C` accepts: every field optional, and `null`
31
+ * (removal) only on a field that is optional in `C`.
32
+ */
33
+ export type ContentPatch<C> = {
34
+ [K in keyof C]?: Pick<C, K> extends Required<Pick<C, K>> ? C[K] : Exclude<C[K], undefined> | null;
35
+ };
36
+ /** A record of the collection, migrated to the handle's version. */
37
+ export interface CollectionRecord<C> extends Omit<StackRecord, 'content'> {
38
+ content: StoredContent<C>;
39
+ }
40
+ /**
41
+ * A record in the handle's family that the handle cannot represent. Stored
42
+ * at a version newer than the handle's, it cannot be migrated down.
43
+ */
44
+ export type Misfit = {
45
+ id: RecordId;
46
+ reason: 'newer-version';
47
+ /** Untyped, as stored. */
48
+ record: StackRecord;
49
+ };
50
+ /**
51
+ * A page of a collection. Misfits do not count as records, so a page can
52
+ * hold fewer than `limit` records with a cursor still set: the results end
53
+ * when `cursor` is `null`, not when a page comes back short.
54
+ */
55
+ export type CollectionPage<C> = {
56
+ records: CollectionRecord<C>[];
57
+ misfits: Misfit[];
58
+ cursor: string | null;
59
+ /**
60
+ * Set when records at a version a content filter or sort could not reach
61
+ * match, or may match, the rest of the query. On the first page only,
62
+ * since it depends on the query rather than the page. See
63
+ * docs/spec/data-model.md § Content queries across versions.
64
+ */
65
+ skipped?: {
66
+ older: boolean;
67
+ newer: boolean;
68
+ };
69
+ };
70
+ /**
71
+ * Path typing stops at this depth and accepts any suffix below it, since a
72
+ * recursive path type otherwise exceeds the compiler's instantiation limit.
73
+ * A typing limit, not a query limit: see docs/spec/data-model.md § Nested
74
+ * content paths.
75
+ */
76
+ type PathDepth = [never, 0, 1, 2, 3, 4];
77
+ /** What a filter value can equal: an adapter compares the value a path reaches. */
78
+ type Leaf = string | number | boolean;
79
+ /**
80
+ * Each content path of `V` reached at `P`, paired with the value a filter
81
+ * compares there. An array is matched element-wise, so it is its element.
82
+ * An object never equals a filter value, so its path takes only `null`.
83
+ */
84
+ type PathEntry<P extends string, V, D extends number> = [D] extends [never] ? [P | `${P}.${string}`, Leaf] : V extends readonly (infer E)[] ? unknown extends E ? [P | `${P}.${string}`, Leaf] : PathEntry<P, E, PathDepth[D]> : V extends object ? string extends keyof V ? [P, null] | [`${P}.${string}`, Leaf] : [P, null] | FieldEntry<V, `${P}.`, PathDepth[D]> : [P, V];
85
+ /** `Required<C>[K]` rather than `NonNullable`, which would leak into errors. */
86
+ type FieldEntry<C, Prefix extends string, D extends number> = [D] extends [never] ? [`${Prefix}${string}`, Leaf] : {
87
+ [K in keyof C & string]-?: PathEntry<`${Prefix}${K}`, Required<C>[K], D>;
88
+ }[keyof C & string];
89
+ /**
90
+ * Every content path of `C`: its fields, and dotted paths through declared
91
+ * objects and arrays. Below an `open` object or array, or six segments
92
+ * deep, any suffix is a path.
93
+ */
94
+ export type ContentPathOf<C> = FieldEntry<C, '', 5>[0];
95
+ /** The top-level scalar fields of `C`, the ones a content sort reaches. */
96
+ export type SortableFieldOf<C> = string extends keyof C ? string : {
97
+ [K in keyof C & string]-?: Required<C>[K] extends string | number | boolean ? K : never;
98
+ }[keyof C & string];
99
+ /**
100
+ * A collection reads live records of its own family only, as stored, so
101
+ * none of `typeId`, `baseId`, `includeDeleted`, `presentAt` or the per-Type
102
+ * shapes it generates itself is offered. Content filter keys and the sort
103
+ * field are checked against `C`, and carried to older versions through the
104
+ * migrations. See docs/spec/data-model.md § Type handles.
105
+ */
106
+ export type CollectionQuery<C> = Omit<StackQuery, 'filter' | 'presentAt' | 'sort'> & {
107
+ filter?: Omit<RecordFilter, 'typeId' | 'baseId' | 'includeDeleted' | 'content' | 'contentPresent' | 'contentByType'> & {
108
+ content?: {
109
+ [E in FieldEntry<C, '', 5> as E[0]]?: E[1] | null;
110
+ };
111
+ contentPresent?: ContentPathOf<C>[];
112
+ };
113
+ sort?: {
114
+ field: NativeSortField;
115
+ contentField?: never;
116
+ direction?: 'asc' | 'desc';
117
+ } | {
118
+ field?: never;
119
+ contentField: SortableFieldOf<C>;
120
+ direction?: 'asc' | 'desc';
121
+ };
122
+ };
123
+ /** The family is the collection's, so neither `typeId` nor `baseId` is a filter key. */
124
+ export type CollectionSubscribeOptions = Omit<SubscribeOptions, 'filter'> & {
125
+ filter?: Omit<ChangeFilter, 'typeId' | 'baseId'>;
126
+ };
127
+ /**
128
+ * A change to a record of the family. `record`, when present, is migrated
129
+ * to the handle's version; `typeId` stays the stored one. A change whose
130
+ * record does not fit carries `misfit` instead, and one whose record is a
131
+ * tombstone carries neither.
132
+ */
133
+ export type CollectionChange<C> = Omit<RecordChange, 'record'> & {
134
+ record?: CollectionRecord<C>;
135
+ misfit?: Misfit;
136
+ };
137
+ export type CollectionChangeSet<C> = Omit<RecordChangeSet, 'contentPatch'> & {
138
+ contentPatch?: ContentPatch<C>;
139
+ };
140
+ /**
141
+ * Typed access to one type family. History (`getVersions`, `getVersion`,
142
+ * `getJournal`) spans versions by nature and stays on `StackClient`. See
143
+ * docs/spec/data-model.md § Type handles.
144
+ */
145
+ export interface Collection<C> {
146
+ /** `null` for a missing record, a tombstone, or a record of another family. */
147
+ get(id: RecordId): Promise<CollectionRecord<C> | null>;
148
+ query(query?: CollectionQuery<C>): Promise<CollectionPage<C>>;
149
+ subscribe(handler: (change: CollectionChange<C>) => void, opts?: CollectionSubscribeOptions): Promise<Unsubscribe>;
150
+ create(content: C, opts?: CreateRecordOptions): Promise<CollectionRecord<C>>;
151
+ mutate(id: RecordId, changes: CollectionChangeSet<C>, opts?: IfVersionOptions): Promise<CollectionRecord<C>>;
152
+ patchContent(id: RecordId, patch: ContentPatch<C>, opts?: IfVersionOptions): Promise<CollectionRecord<C>>;
153
+ associate(id: RecordId, associations: DataAssociation[]): Promise<CollectionRecord<C>>;
154
+ dissociate(id: RecordId, associations: DataAssociation[]): Promise<CollectionRecord<C>>;
155
+ amendAssociations(id: RecordId, changes: AssociationEdit[]): Promise<CollectionRecord<C>>;
156
+ grantAccess(id: RecordId, permissions: AuthorityAssociation[]): Promise<CollectionRecord<C>>;
157
+ revokeAccess(id: RecordId, permissions: AuthorityAssociation[]): Promise<CollectionRecord<C>>;
158
+ amendAccess(id: RecordId, changes: AssociationEdit[]): Promise<CollectionRecord<C>>;
159
+ delete(id: RecordId, opts?: DeleteRecordOptions): Promise<DeleteResult>;
160
+ undelete(id: RecordId, opts?: IfVersionOptions): Promise<CollectionRecord<C>>;
161
+ restoreVersion(id: RecordId, version: number, opts?: IfVersionOptions): Promise<CollectionRecord<C>>;
162
+ }
163
+ /**
164
+ * A record the collection asked for does not fit the handle. Raised only
165
+ * in the app's own process and never on the wire, so it sits outside the
166
+ * `StackError` taxonomy. A write that raises it after writing says so.
167
+ * See docs/spec/wire-format.md § The taxonomy root.
168
+ */
169
+ export declare class MisfitError extends Error {
170
+ readonly reason: Misfit['reason'];
171
+ readonly record: StackRecord;
172
+ constructor(misfit: Misfit, handle: TypeHandle, written?: boolean);
173
+ }
174
+ /**
175
+ * The collection over `handle`, shared by `Stack.collection()` and
176
+ * `ScopedStack.collection()`. `registry` is the opening `Stack`'s, which
177
+ * the app's own process filled. `listTypes` names the family's versions,
178
+ * which `StackClient` does not offer.
179
+ */
180
+ export declare const createCollection: <S extends ReadonlyTypeSchema>(client: StackClient, registry: MigrationRegistry, handle: TypeHandle<S>, listTypes: () => Promise<StackType[]>) => Collection<ContentOf<S>>;
181
+ export {};
182
+ //# sourceMappingURL=collection.d.ts.map