@brass-build/client 0.4.0 → 0.5.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 (327) hide show
  1. package/AGENTS.md +142 -41
  2. package/CHANGELOG.md +93 -4
  3. package/README.md +666 -179
  4. package/dist/acting-organization.d.ts.map +1 -1
  5. package/dist/acting-organization.js +6 -11
  6. package/dist/acting-organization.js.map +1 -1
  7. package/dist/api-fetch.d.ts +8 -0
  8. package/dist/api-fetch.d.ts.map +1 -1
  9. package/dist/api-fetch.js +35 -8
  10. package/dist/api-fetch.js.map +1 -1
  11. package/dist/batched-get.d.ts +11 -0
  12. package/dist/batched-get.d.ts.map +1 -0
  13. package/dist/batched-get.js +92 -0
  14. package/dist/batched-get.js.map +1 -0
  15. package/dist/call-service.d.ts +4 -0
  16. package/dist/call-service.d.ts.map +1 -1
  17. package/dist/call-service.js +66 -3
  18. package/dist/call-service.js.map +1 -1
  19. package/dist/client.d.ts +51 -2
  20. package/dist/client.d.ts.map +1 -1
  21. package/dist/client.js +982 -124
  22. package/dist/client.js.map +1 -1
  23. package/dist/document-checkpoints.d.ts +25 -2
  24. package/dist/document-checkpoints.d.ts.map +1 -1
  25. package/dist/document-checkpoints.js +258 -61
  26. package/dist/document-checkpoints.js.map +1 -1
  27. package/dist/document-commands.d.ts.map +1 -1
  28. package/dist/document-commands.js +5 -3
  29. package/dist/document-commands.js.map +1 -1
  30. package/dist/document-contents.d.ts +10 -0
  31. package/dist/document-contents.d.ts.map +1 -1
  32. package/dist/document-contents.js +23 -1
  33. package/dist/document-contents.js.map +1 -1
  34. package/dist/document-set-subscription.d.ts +56 -0
  35. package/dist/document-set-subscription.d.ts.map +1 -0
  36. package/dist/document-set-subscription.js +240 -0
  37. package/dist/document-set-subscription.js.map +1 -0
  38. package/dist/document-signals.d.ts +6 -0
  39. package/dist/document-signals.d.ts.map +1 -1
  40. package/dist/document-signals.js +31 -1
  41. package/dist/document-signals.js.map +1 -1
  42. package/dist/document-streams.d.ts +36 -2
  43. package/dist/document-streams.d.ts.map +1 -1
  44. package/dist/document-streams.js +69 -18
  45. package/dist/document-streams.js.map +1 -1
  46. package/dist/document-thumbnail-capture.d.ts +12 -2
  47. package/dist/document-thumbnail-capture.d.ts.map +1 -1
  48. package/dist/document-thumbnail-capture.js +103 -27
  49. package/dist/document-thumbnail-capture.js.map +1 -1
  50. package/dist/documents-namespace.d.ts +92 -10
  51. package/dist/documents-namespace.d.ts.map +1 -1
  52. package/dist/documents-namespace.js +285 -39
  53. package/dist/documents-namespace.js.map +1 -1
  54. package/dist/documents.d.ts +173 -8
  55. package/dist/documents.d.ts.map +1 -1
  56. package/dist/documents.js +1778 -310
  57. package/dist/documents.js.map +1 -1
  58. package/dist/errors.d.ts +3 -3
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js.map +1 -1
  61. package/dist/fetch-retry.d.ts +1 -0
  62. package/dist/fetch-retry.d.ts.map +1 -1
  63. package/dist/fetch-retry.js +2 -0
  64. package/dist/fetch-retry.js.map +1 -1
  65. package/dist/file-choice.d.ts +2 -0
  66. package/dist/file-choice.d.ts.map +1 -0
  67. package/dist/file-choice.js +27 -0
  68. package/dist/file-choice.js.map +1 -0
  69. package/dist/guest-link.d.ts +18 -1
  70. package/dist/guest-link.d.ts.map +1 -1
  71. package/dist/guest-link.js +329 -1
  72. package/dist/guest-link.js.map +1 -1
  73. package/dist/handle-types.d.ts +67 -5
  74. package/dist/handle-types.d.ts.map +1 -1
  75. package/dist/handles.d.ts +15 -19
  76. package/dist/handles.d.ts.map +1 -1
  77. package/dist/handles.js +231 -76
  78. package/dist/handles.js.map +1 -1
  79. package/dist/hosted-ui.d.ts +61 -0
  80. package/dist/hosted-ui.d.ts.map +1 -1
  81. package/dist/hosted-ui.js +94 -42
  82. package/dist/hosted-ui.js.map +1 -1
  83. package/dist/import-contract.d.ts +74 -0
  84. package/dist/import-contract.d.ts.map +1 -0
  85. package/dist/import-contract.js +163 -0
  86. package/dist/import-contract.js.map +1 -0
  87. package/dist/import-guest.d.ts +9 -0
  88. package/dist/import-guest.d.ts.map +1 -0
  89. package/dist/import-guest.js +155 -0
  90. package/dist/import-guest.js.map +1 -0
  91. package/dist/import-report.d.ts +17 -0
  92. package/dist/import-report.d.ts.map +1 -0
  93. package/dist/import-report.js +7 -0
  94. package/dist/import-report.js.map +1 -0
  95. package/dist/importer-wait.d.ts +2 -0
  96. package/dist/importer-wait.d.ts.map +1 -0
  97. package/dist/importer-wait.js +5 -0
  98. package/dist/importer-wait.js.map +1 -0
  99. package/dist/importer.d.ts +18 -4
  100. package/dist/importer.d.ts.map +1 -1
  101. package/dist/importer.js +322 -105
  102. package/dist/importer.js.map +1 -1
  103. package/dist/in-development.d.ts +2 -0
  104. package/dist/in-development.d.ts.map +1 -0
  105. package/dist/in-development.js +10 -0
  106. package/dist/in-development.js.map +1 -0
  107. package/dist/index.d.ts +16 -8
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +24 -8
  110. package/dist/index.js.map +1 -1
  111. package/dist/init-keys.d.ts +1 -1
  112. package/dist/init-keys.d.ts.map +1 -1
  113. package/dist/init-keys.js +6 -1
  114. package/dist/init-keys.js.map +1 -1
  115. package/dist/input-documents.d.ts +67 -0
  116. package/dist/input-documents.d.ts.map +1 -0
  117. package/dist/input-documents.js +693 -0
  118. package/dist/input-documents.js.map +1 -0
  119. package/dist/inputs-status.d.ts +80 -0
  120. package/dist/inputs-status.d.ts.map +1 -0
  121. package/dist/inputs-status.js +460 -0
  122. package/dist/inputs-status.js.map +1 -0
  123. package/dist/inputs.d.ts +2 -0
  124. package/dist/inputs.d.ts.map +1 -0
  125. package/dist/inputs.js +793 -0
  126. package/dist/inputs.js.map +1 -0
  127. package/dist/internal.d.ts +6 -1
  128. package/dist/internal.d.ts.map +1 -1
  129. package/dist/internal.js +4 -1
  130. package/dist/internal.js.map +1 -1
  131. package/dist/join-content.d.ts +33 -0
  132. package/dist/join-content.d.ts.map +1 -0
  133. package/dist/join-content.js +114 -0
  134. package/dist/join-content.js.map +1 -0
  135. package/dist/join-stamp.d.ts +3 -0
  136. package/dist/join-stamp.d.ts.map +1 -0
  137. package/dist/join-stamp.js +12 -0
  138. package/dist/join-stamp.js.map +1 -0
  139. package/dist/manifest-drift.d.ts +9 -0
  140. package/dist/manifest-drift.d.ts.map +1 -0
  141. package/dist/manifest-drift.js +46 -0
  142. package/dist/manifest-drift.js.map +1 -0
  143. package/dist/open-document.d.ts +109 -2
  144. package/dist/open-document.d.ts.map +1 -1
  145. package/dist/open-document.js +1822 -811
  146. package/dist/open-document.js.map +1 -1
  147. package/dist/page-restore.d.ts +12 -0
  148. package/dist/page-restore.d.ts.map +1 -0
  149. package/dist/page-restore.js +23 -0
  150. package/dist/page-restore.js.map +1 -0
  151. package/dist/placement.d.ts +13 -0
  152. package/dist/placement.d.ts.map +1 -0
  153. package/dist/placement.js +34 -0
  154. package/dist/placement.js.map +1 -0
  155. package/dist/presence.d.ts +1 -0
  156. package/dist/presence.d.ts.map +1 -1
  157. package/dist/presence.js +16 -4
  158. package/dist/presence.js.map +1 -1
  159. package/dist/read-only-document.d.ts +87 -0
  160. package/dist/read-only-document.d.ts.map +1 -0
  161. package/dist/read-only-document.js +4 -0
  162. package/dist/read-only-document.js.map +1 -0
  163. package/dist/record-paging.d.ts +63 -6
  164. package/dist/record-paging.d.ts.map +1 -1
  165. package/dist/record-paging.js +122 -29
  166. package/dist/record-paging.js.map +1 -1
  167. package/dist/references.d.ts +40 -0
  168. package/dist/references.d.ts.map +1 -0
  169. package/dist/references.js +135 -0
  170. package/dist/references.js.map +1 -0
  171. package/dist/request-lane.d.ts +5 -0
  172. package/dist/request-lane.d.ts.map +1 -0
  173. package/dist/request-lane.js +48 -0
  174. package/dist/request-lane.js.map +1 -0
  175. package/dist/sealed-arrow.d.ts +170 -0
  176. package/dist/sealed-arrow.d.ts.map +1 -0
  177. package/dist/sealed-arrow.js +769 -0
  178. package/dist/sealed-arrow.js.map +1 -0
  179. package/dist/sealed-cache.d.ts +21 -0
  180. package/dist/sealed-cache.d.ts.map +1 -0
  181. package/dist/sealed-cache.js +247 -0
  182. package/dist/sealed-cache.js.map +1 -0
  183. package/dist/sealed-encode.d.ts +87 -0
  184. package/dist/sealed-encode.d.ts.map +1 -0
  185. package/dist/sealed-encode.js +876 -0
  186. package/dist/sealed-encode.js.map +1 -0
  187. package/dist/sealed-handles.d.ts +45 -0
  188. package/dist/sealed-handles.d.ts.map +1 -0
  189. package/dist/sealed-handles.js +408 -0
  190. package/dist/sealed-handles.js.map +1 -0
  191. package/dist/sealed-store.d.ts +242 -0
  192. package/dist/sealed-store.d.ts.map +1 -0
  193. package/dist/sealed-store.js +1155 -0
  194. package/dist/sealed-store.js.map +1 -0
  195. package/dist/serializer.d.ts +6 -15
  196. package/dist/serializer.d.ts.map +1 -1
  197. package/dist/session-toolbar.d.ts +20 -5
  198. package/dist/session-toolbar.d.ts.map +1 -1
  199. package/dist/session-toolbar.js +419 -67
  200. package/dist/session-toolbar.js.map +1 -1
  201. package/dist/session.d.ts +38 -1
  202. package/dist/session.d.ts.map +1 -1
  203. package/dist/session.js +562 -79
  204. package/dist/session.js.map +1 -1
  205. package/dist/sha256.d.ts +3 -0
  206. package/dist/sha256.d.ts.map +1 -0
  207. package/dist/sha256.js +74 -0
  208. package/dist/sha256.js.map +1 -0
  209. package/dist/stable-handles.d.ts +19 -0
  210. package/dist/stable-handles.d.ts.map +1 -0
  211. package/dist/stable-handles.js +193 -0
  212. package/dist/stable-handles.js.map +1 -0
  213. package/dist/staged-seal.d.ts +2 -0
  214. package/dist/staged-seal.d.ts.map +1 -0
  215. package/dist/staged-seal.js +76 -0
  216. package/dist/staged-seal.js.map +1 -0
  217. package/dist/subscriber-delivery.d.ts +8 -0
  218. package/dist/subscriber-delivery.d.ts.map +1 -0
  219. package/dist/subscriber-delivery.js +31 -0
  220. package/dist/subscriber-delivery.js.map +1 -0
  221. package/dist/tail-op-import.d.ts +10 -0
  222. package/dist/tail-op-import.d.ts.map +1 -0
  223. package/dist/tail-op-import.js +33 -0
  224. package/dist/tail-op-import.js.map +1 -0
  225. package/dist/topic-lifecycle.d.ts +6 -0
  226. package/dist/topic-lifecycle.d.ts.map +1 -1
  227. package/dist/topic-lifecycle.js +32 -2
  228. package/dist/topic-lifecycle.js.map +1 -1
  229. package/dist/trial-replace-dialog.d.ts +10 -0
  230. package/dist/trial-replace-dialog.d.ts.map +1 -0
  231. package/dist/trial-replace-dialog.js +153 -0
  232. package/dist/trial-replace-dialog.js.map +1 -0
  233. package/dist/types.d.ts +8 -1
  234. package/dist/types.d.ts.map +1 -1
  235. package/dist/undo.d.ts +60 -5
  236. package/dist/undo.d.ts.map +1 -1
  237. package/dist/undo.js +344 -75
  238. package/dist/undo.js.map +1 -1
  239. package/dist/version-bar.d.ts +1 -0
  240. package/dist/version-bar.d.ts.map +1 -1
  241. package/dist/version-bar.js +2 -1
  242. package/dist/version-bar.js.map +1 -1
  243. package/dist/version.d.ts +2 -2
  244. package/dist/version.js +1 -1
  245. package/dist/ws-client.d.ts +21 -0
  246. package/dist/ws-client.d.ts.map +1 -1
  247. package/dist/ws-client.js +192 -8
  248. package/dist/ws-client.js.map +1 -1
  249. package/dist/ws-frame-limit.d.ts +1 -0
  250. package/dist/ws-frame-limit.d.ts.map +1 -1
  251. package/dist/ws-frame-limit.js.map +1 -1
  252. package/docs/app-manifest.md +121 -17
  253. package/docs/custom-chrome.md +24 -16
  254. package/docs/hosted-ui-advanced.md +38 -6
  255. package/docs/imported-documents.md +104 -51
  256. package/docs/presence.md +16 -4
  257. package/docs/schemas.md +8 -5
  258. package/docs/services.md +6 -6
  259. package/docs/streams.md +139 -150
  260. package/docs/text-fields.md +26 -19
  261. package/package.json +5 -2
  262. package/src/acting-organization.ts +6 -12
  263. package/src/api-fetch.ts +53 -3
  264. package/src/auth-flow.ts +1 -1
  265. package/src/batched-get.ts +117 -0
  266. package/src/call-service.ts +78 -3
  267. package/src/client.ts +1092 -121
  268. package/src/document-checkpoints.ts +307 -65
  269. package/src/document-commands.ts +5 -5
  270. package/src/document-contents.ts +27 -1
  271. package/src/document-set-subscription.ts +274 -0
  272. package/src/document-signals.ts +47 -5
  273. package/src/document-streams.ts +85 -16
  274. package/src/document-thumbnail-capture.ts +118 -32
  275. package/src/documents-namespace.ts +539 -84
  276. package/src/documents.ts +2017 -348
  277. package/src/errors.ts +17 -6
  278. package/src/fetch-retry.ts +5 -0
  279. package/src/file-choice.ts +26 -0
  280. package/src/guest-link.ts +362 -1
  281. package/src/handle-types.ts +73 -47
  282. package/src/handles.ts +253 -83
  283. package/src/hosted-ui.ts +211 -50
  284. package/src/import-contract.ts +227 -0
  285. package/src/import-guest.ts +193 -0
  286. package/src/import-report.ts +24 -0
  287. package/src/importer-wait.ts +4 -0
  288. package/src/importer.ts +386 -131
  289. package/src/in-development.ts +10 -0
  290. package/src/index.ts +76 -16
  291. package/src/init-keys.ts +6 -1
  292. package/src/input-documents.ts +819 -0
  293. package/src/inputs-status.ts +598 -0
  294. package/src/inputs.ts +1090 -0
  295. package/src/internal.ts +7 -0
  296. package/src/join-content.ts +135 -0
  297. package/src/join-stamp.ts +12 -0
  298. package/src/manifest-drift.ts +54 -0
  299. package/src/open-document.ts +2346 -930
  300. package/src/page-restore.ts +38 -0
  301. package/src/placement.ts +54 -0
  302. package/src/presence.ts +21 -5
  303. package/src/read-only-document.ts +133 -0
  304. package/src/record-paging.ts +147 -25
  305. package/src/references.ts +207 -0
  306. package/src/request-lane.ts +59 -0
  307. package/src/sealed-arrow.ts +843 -0
  308. package/src/sealed-cache.ts +267 -0
  309. package/src/sealed-encode.ts +1015 -0
  310. package/src/sealed-handles.ts +486 -0
  311. package/src/sealed-store.ts +1330 -0
  312. package/src/serializer.ts +8 -20
  313. package/src/session-toolbar.ts +463 -72
  314. package/src/session.ts +740 -99
  315. package/src/sha256.ts +65 -0
  316. package/src/stable-handles.ts +235 -0
  317. package/src/staged-seal.ts +90 -0
  318. package/src/subscriber-delivery.ts +37 -0
  319. package/src/tail-op-import.ts +30 -0
  320. package/src/topic-lifecycle.ts +38 -2
  321. package/src/trial-replace-dialog.ts +163 -0
  322. package/src/types.ts +6 -6
  323. package/src/undo.ts +386 -73
  324. package/src/version-bar.ts +5 -1
  325. package/src/version.ts +1 -1
  326. package/src/ws-client.ts +208 -16
  327. package/src/ws-frame-limit.ts +1 -0
@@ -0,0 +1,207 @@
1
+ // The SDK's side of a document's references and read marks: the root names
2
+ // it keeps from apps, the mark that acknowledges a removed input, and the
3
+ // provisional post that follows an acknowledged change. The shapes and write
4
+ // rules are the platform contract's, through the SDK's copy in `inputs.ts`.
5
+
6
+ import {
7
+ inputReadMarkAfter,
8
+ isInputLineId,
9
+ parseInputReadEntryKey,
10
+ type InputReadMark,
11
+ type InputsKeyState,
12
+ type ProvisionalInputsAnswer,
13
+ type ProvisionalInputsPost,
14
+ } from './inputs.js';
15
+
16
+ /**
17
+ * Root names the platform keeps for itself, the two input roots among them.
18
+ * They are refused as names an app passes to `doc.map` / `doc.list` /
19
+ * `doc.text`, and every app-visible enumeration of a document's roots leaves
20
+ * them out.
21
+ */
22
+ const RESERVED_ROOT_PREFIX = '__brass_';
23
+
24
+ export function isReservedRootName(name: string): boolean {
25
+ return name.startsWith(RESERVED_ROOT_PREFIX);
26
+ }
27
+
28
+ /**
29
+ * @internal
30
+ * Posts a provisional body. Resolves the platform's answer, or null without
31
+ * one. `superseded` turns true once a later post for the same key has been
32
+ * sent (or the document closed), which ends any retry of this one.
33
+ */
34
+ export type ProvisionalInputsPoster = (
35
+ post: ProvisionalInputsPost,
36
+ opts?: { superseded?: () => boolean },
37
+ ) => Promise<ProvisionalInputsAnswer | null>;
38
+
39
+ interface PendingPost {
40
+ key: string;
41
+ lineId: string;
42
+ state: InputsKeyState;
43
+ awaiting: Set<string>;
44
+ seq: number;
45
+ }
46
+
47
+ /**
48
+ * @internal
49
+ * Posts each key's provisional entry once every op carrying its latest change
50
+ * is acknowledged with a seq. A later change to a key replaces the post an
51
+ * earlier one was waiting to make, and a refused op cancels the post it
52
+ * carried. A post's outcome never reaches the document: the platform
53
+ * projects the key from the document itself either way.
54
+ */
55
+ export class ProvisionalInputsTracker {
56
+ private _poster: ProvisionalInputsPoster | null = null;
57
+ private _onAnswer: ((answer: ProvisionalInputsAnswer) => void) | null = null;
58
+ // One commit can change several keys, so one op can carry several posts.
59
+ private readonly _byOp = new Map<string, Set<PendingPost>>();
60
+ private readonly _byKey = new Map<string, PendingPost>();
61
+ // The post each key last sent, which a retry of an earlier one defers to.
62
+ private readonly _sent = new Map<string, ProvisionalInputsPost>();
63
+
64
+ setPoster(poster: ProvisionalInputsPoster | null): void {
65
+ this._poster = poster;
66
+ }
67
+
68
+ /** Hears each answer a post receives: the key's row revision after it. */
69
+ onAnswer(listener: ((answer: ProvisionalInputsAnswer) => void) | null): void {
70
+ this._onAnswer = listener;
71
+ }
72
+
73
+ /** Wait for the ops a commit shipped, then post `state` for `key`. */
74
+ register(key: string, lineId: string | null, state: InputsKeyState, clientOpIds: readonly string[]): void {
75
+ if (this._poster === null || !isInputLineId(lineId) || clientOpIds.length === 0) return;
76
+ this._drop(this._byKey.get(key));
77
+ const pending: PendingPost = { key, lineId, state, awaiting: new Set(clientOpIds), seq: 0 };
78
+ this._byKey.set(key, pending);
79
+ for (const id of clientOpIds) {
80
+ const posts = this._byOp.get(id) ?? new Set<PendingPost>();
81
+ posts.add(pending);
82
+ this._byOp.set(id, posts);
83
+ }
84
+ }
85
+
86
+ acked(clientOpId: string, seq: number | undefined): void {
87
+ const posts = this._byOp.get(clientOpId);
88
+ if (posts === undefined) return;
89
+ this._byOp.delete(clientOpId);
90
+ for (const pending of posts) this._ackedFor(pending, clientOpId, seq);
91
+ }
92
+
93
+ private _ackedFor(pending: PendingPost, clientOpId: string, seq: number | undefined): void {
94
+ if (seq === undefined) {
95
+ this._drop(pending);
96
+ return;
97
+ }
98
+ pending.awaiting.delete(clientOpId);
99
+ pending.seq = Math.max(pending.seq, seq);
100
+ if (pending.awaiting.size > 0) return;
101
+ this._drop(pending);
102
+ const poster = this._poster;
103
+ if (poster === null || pending.seq < 1) return;
104
+ const post: ProvisionalInputsPost = {
105
+ key: pending.key,
106
+ body: {
107
+ branch_id: pending.lineId,
108
+ op_seq: pending.seq,
109
+ refs: pending.state.refs,
110
+ read: pending.state.read,
111
+ },
112
+ };
113
+ this._sent.set(post.key, post);
114
+ const superseded = (): boolean => this._sent.get(post.key) !== post;
115
+ try {
116
+ void poster(post, { superseded })
117
+ .then((answer) => {
118
+ if (!superseded()) this._sent.delete(post.key);
119
+ if (answer !== null && answer.key === post.key) this._onAnswer?.(answer);
120
+ })
121
+ .catch(() => undefined);
122
+ } catch {
123
+ // The post is best-effort; the document already holds the change.
124
+ }
125
+ }
126
+
127
+ failed(clientOpId: string): void {
128
+ for (const pending of this._byOp.get(clientOpId) ?? []) this._drop(pending);
129
+ }
130
+
131
+ clear(): void {
132
+ this._byOp.clear();
133
+ this._byKey.clear();
134
+ this._sent.clear();
135
+ }
136
+
137
+ private _drop(pending: PendingPost | undefined): void {
138
+ if (pending === undefined) return;
139
+ for (const id of pending.awaiting) {
140
+ const posts = this._byOp.get(id);
141
+ posts?.delete(pending);
142
+ if (posts?.size === 0) this._byOp.delete(id);
143
+ }
144
+ pending.awaiting.clear();
145
+ if (this._byKey.get(pending.key) === pending) this._byKey.delete(pending.key);
146
+ }
147
+ }
148
+
149
+ /** @internal The latest mark this copy holds for `docId` under `key`, or null. */
150
+ export function latestReadMark(
151
+ readRoot: Readonly<Record<string, unknown>>,
152
+ key: string,
153
+ docId: string,
154
+ ): InputReadMark | null {
155
+ let latest: InputReadMark | null = null;
156
+ for (const [entryKey, value] of Object.entries(readRoot)) {
157
+ if (value !== true) continue;
158
+ const parsed = parseInputReadEntryKey(entryKey);
159
+ if (parsed?.key !== key || parsed.docId !== docId) continue;
160
+ if (latest === null || inputReadMarkAfter(parsed.mark, latest)) latest = parsed.mark;
161
+ }
162
+ return latest;
163
+ }
164
+
165
+ /** The part of an input status `markRead` reads. */
166
+ export interface MarkReadStatus {
167
+ readonly key: string;
168
+ readonly changed: ReadonlyArray<{
169
+ readonly doc_id: string;
170
+ readonly change: string;
171
+ readonly updated_at?: string;
172
+ }>;
173
+ /** Each input at the version the status reports. An input `markRead`
174
+ * records from the status is recorded at this version. */
175
+ readonly inputs?: ReadonlyArray<{ readonly doc_id: string; readonly updated_at: string }>;
176
+ }
177
+
178
+ /** A document `markRead` records as read: a copy this app opened, recorded at
179
+ * the `updatedAt` it was opened at. */
180
+ export interface MarkReadHandle {
181
+ readonly id: string | null;
182
+ readonly updatedAt: string | null;
183
+ readonly status: { readonly pending: boolean } | null;
184
+ }
185
+
186
+ /** A file `markRead` records as read from the status that reported it: an
187
+ * entry of its `files` or `changed`, recorded at the version the status
188
+ * reports. */
189
+ export interface MarkReadEntry {
190
+ readonly doc_id: string;
191
+ }
192
+
193
+ /** What `markRead` records, and whether it joins the Undo step. */
194
+ export interface MarkReadOptions {
195
+ /** The files to record. Defaults to every file the status lists as added
196
+ * or modified. */
197
+ readonly files?: ReadonlyArray<MarkReadHandle | MarkReadEntry>;
198
+ /** `false` keeps the marks off the Undo stack. Defaults to `true`. */
199
+ readonly undoable?: boolean;
200
+ }
201
+
202
+ /** @internal One queued reference write: applies itself and names the key it
203
+ * changed, or null when it changed nothing. */
204
+ export interface InputsWrite {
205
+ undoable: boolean;
206
+ apply: () => string | null;
207
+ }
@@ -0,0 +1,59 @@
1
+ // A bounded queue for one kind of read. At most `limit` requests run at once
2
+ // and the rest wait in order. Calls with the same key that arrive while one
3
+ // for that key is still WAITING share it, since nothing has been sent yet and
4
+ // the answer it brings back is as fresh as a request of their own would be.
5
+ // A call arriving after its key's request has started sends its own, so a
6
+ // read that follows a write never answers with what the server held before.
7
+
8
+ export interface RequestLane {
9
+ run<T>(key: string, send: () => Promise<T>): Promise<T>;
10
+ }
11
+
12
+ interface Waiting {
13
+ send: () => Promise<unknown>;
14
+ promise: Promise<unknown>;
15
+ resolve: (value: unknown) => void;
16
+ reject: (err: unknown) => void;
17
+ }
18
+
19
+ export function createRequestLane(limit: number): RequestLane {
20
+ const queue: string[] = [];
21
+ const waiting = new Map<string, Waiting>();
22
+ let running = 0;
23
+
24
+ const pump = (): void => {
25
+ while (running < limit && queue.length > 0) {
26
+ const key = queue.shift()!;
27
+ const entry = waiting.get(key)!;
28
+ waiting.delete(key);
29
+ running += 1;
30
+ let sent: Promise<unknown>;
31
+ try {
32
+ sent = entry.send();
33
+ } catch (err) {
34
+ sent = Promise.reject(err);
35
+ }
36
+ sent.then(entry.resolve, entry.reject).finally(() => {
37
+ running -= 1;
38
+ pump();
39
+ });
40
+ }
41
+ };
42
+
43
+ return {
44
+ run<T>(key: string, send: () => Promise<T>): Promise<T> {
45
+ const pending = waiting.get(key);
46
+ if (pending !== undefined) return pending.promise as Promise<T>;
47
+ let resolve!: (value: unknown) => void;
48
+ let reject!: (err: unknown) => void;
49
+ const promise = new Promise<unknown>((res, rej) => {
50
+ resolve = res;
51
+ reject = rej;
52
+ });
53
+ waiting.set(key, { send, promise, resolve, reject });
54
+ queue.push(key);
55
+ pump();
56
+ return promise as Promise<T>;
57
+ },
58
+ };
59
+ }