@agentshouse/mdruntime 0.1.0-cd3486bd400ed → 0.1.0-cd35ccee27a19

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 (343) hide show
  1. package/README.md +22 -10
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +308 -0
  5. package/dist/actions.d.ts +20 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +251 -0
  8. package/dist/adapter.d.ts +195 -91
  9. package/dist/adapter.d.ts.map +1 -1
  10. package/dist/attached-model.d.ts +7 -10
  11. package/dist/attached-model.d.ts.map +1 -1
  12. package/dist/attached-model.js +40 -41
  13. package/dist/attempt.d.ts.map +1 -1
  14. package/dist/attempt.js +14 -1
  15. package/dist/binary.d.ts +18 -0
  16. package/dist/binary.d.ts.map +1 -0
  17. package/dist/binary.js +8 -0
  18. package/dist/bootstrap.d.ts +31 -11
  19. package/dist/bootstrap.d.ts.map +1 -1
  20. package/dist/bootstrap.js +311 -92
  21. package/dist/cell.d.ts +6 -0
  22. package/dist/cell.d.ts.map +1 -0
  23. package/dist/cell.js +9 -0
  24. package/dist/chain.d.ts +12 -0
  25. package/dist/chain.d.ts.map +1 -0
  26. package/dist/chain.js +64 -0
  27. package/dist/change-set.d.ts +13 -14
  28. package/dist/change-set.d.ts.map +1 -1
  29. package/dist/change-set.js +17 -62
  30. package/dist/change.d.ts +42 -0
  31. package/dist/change.d.ts.map +1 -1
  32. package/dist/change.js +36 -0
  33. package/dist/chat-types.d.ts +5 -0
  34. package/dist/chat-types.d.ts.map +1 -0
  35. package/dist/chat-types.js +13 -0
  36. package/dist/client.d.ts +49 -21
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +131 -17
  39. package/dist/collection.d.ts +31 -9
  40. package/dist/collection.d.ts.map +1 -1
  41. package/dist/collection.js +49 -80
  42. package/dist/compile.d.ts +16 -0
  43. package/dist/compile.d.ts.map +1 -0
  44. package/dist/compile.js +711 -0
  45. package/dist/connection.d.ts +8 -111
  46. package/dist/connection.d.ts.map +1 -1
  47. package/dist/connection.js +107 -2218
  48. package/dist/context.d.ts +2 -0
  49. package/dist/context.d.ts.map +1 -0
  50. package/dist/context.js +7 -0
  51. package/dist/conversation-live.d.ts +136 -0
  52. package/dist/conversation-live.d.ts.map +1 -0
  53. package/dist/conversation-live.js +249 -0
  54. package/dist/conversation-row.d.ts +33 -0
  55. package/dist/conversation-row.d.ts.map +1 -0
  56. package/dist/conversation-row.js +53 -0
  57. package/dist/conversation.d.ts +71 -42
  58. package/dist/conversation.d.ts.map +1 -1
  59. package/dist/conversation.js +1068 -586
  60. package/dist/declaration.d.ts +10 -0
  61. package/dist/declaration.d.ts.map +1 -0
  62. package/dist/declaration.js +24 -0
  63. package/dist/digest.d.ts +7 -0
  64. package/dist/digest.d.ts.map +1 -0
  65. package/dist/digest.js +113 -0
  66. package/dist/discovery.d.ts +10 -0
  67. package/dist/discovery.d.ts.map +1 -0
  68. package/dist/discovery.js +148 -0
  69. package/dist/durability.d.ts +19 -1
  70. package/dist/durability.d.ts.map +1 -1
  71. package/dist/durability.js +5 -0
  72. package/dist/entry.d.ts +20 -0
  73. package/dist/entry.d.ts.map +1 -0
  74. package/dist/entry.js +161 -0
  75. package/dist/expectation.d.ts +4 -2
  76. package/dist/expectation.d.ts.map +1 -1
  77. package/dist/expectation.js +33 -11
  78. package/dist/failure.d.ts +1 -0
  79. package/dist/failure.d.ts.map +1 -1
  80. package/dist/failure.js +3 -0
  81. package/dist/figures.d.ts +49 -0
  82. package/dist/figures.d.ts.map +1 -0
  83. package/dist/figures.js +197 -0
  84. package/dist/file.d.ts +2 -19
  85. package/dist/file.d.ts.map +1 -1
  86. package/dist/file.js +7 -122
  87. package/dist/holdings.d.ts +29 -0
  88. package/dist/holdings.d.ts.map +1 -0
  89. package/dist/holdings.js +95 -0
  90. package/dist/host.d.ts +28 -4
  91. package/dist/host.d.ts.map +1 -1
  92. package/dist/host.js +163 -19
  93. package/dist/identity.d.ts +1 -0
  94. package/dist/identity.d.ts.map +1 -1
  95. package/dist/identity.js +3 -0
  96. package/dist/index.d.ts +37 -10
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +15 -0
  99. package/dist/install.d.ts +54 -0
  100. package/dist/install.d.ts.map +1 -0
  101. package/dist/install.js +538 -0
  102. package/dist/lifecycle.d.ts +7 -0
  103. package/dist/lifecycle.d.ts.map +1 -0
  104. package/dist/lifecycle.js +272 -0
  105. package/dist/listing.d.ts +48 -0
  106. package/dist/listing.d.ts.map +1 -0
  107. package/dist/listing.js +139 -0
  108. package/dist/live.d.ts +23 -0
  109. package/dist/live.d.ts.map +1 -0
  110. package/dist/live.js +165 -0
  111. package/dist/locks.d.ts +7 -0
  112. package/dist/locks.d.ts.map +1 -0
  113. package/dist/locks.js +33 -0
  114. package/dist/log.d.ts +1 -0
  115. package/dist/log.d.ts.map +1 -1
  116. package/dist/lost.d.ts +5 -0
  117. package/dist/lost.d.ts.map +1 -0
  118. package/dist/lost.js +50 -0
  119. package/dist/media.d.ts +8 -0
  120. package/dist/media.d.ts.map +1 -0
  121. package/dist/media.js +218 -0
  122. package/dist/memory.d.ts +24 -8
  123. package/dist/memory.d.ts.map +1 -1
  124. package/dist/memory.js +289 -96
  125. package/dist/messages.d.ts +49 -0
  126. package/dist/messages.d.ts.map +1 -0
  127. package/dist/messages.js +150 -0
  128. package/dist/model.d.ts +11 -12
  129. package/dist/model.d.ts.map +1 -1
  130. package/dist/model.js +7 -15
  131. package/dist/models.d.ts +10 -0
  132. package/dist/models.d.ts.map +1 -0
  133. package/dist/models.js +156 -0
  134. package/dist/name.d.ts +4 -0
  135. package/dist/name.d.ts.map +1 -0
  136. package/dist/name.js +122 -0
  137. package/dist/observation.d.ts +22 -4
  138. package/dist/observation.d.ts.map +1 -1
  139. package/dist/observation.js +127 -26
  140. package/dist/opened.d.ts +22 -0
  141. package/dist/opened.d.ts.map +1 -0
  142. package/dist/opened.js +107 -0
  143. package/dist/opening.d.ts +4 -0
  144. package/dist/opening.d.ts.map +1 -0
  145. package/dist/opening.js +59 -0
  146. package/dist/outcome.d.ts +56 -5
  147. package/dist/outcome.d.ts.map +1 -1
  148. package/dist/outcome.js +49 -0
  149. package/dist/overlay.d.ts +17 -5
  150. package/dist/overlay.d.ts.map +1 -1
  151. package/dist/overlay.js +161 -187
  152. package/dist/page-metadata.d.ts +8 -0
  153. package/dist/page-metadata.d.ts.map +1 -0
  154. package/dist/page-metadata.js +19 -0
  155. package/dist/parts.d.ts +11 -0
  156. package/dist/parts.d.ts.map +1 -0
  157. package/dist/parts.js +21 -0
  158. package/dist/paths.d.ts +2 -0
  159. package/dist/paths.d.ts.map +1 -1
  160. package/dist/paths.js +26 -0
  161. package/dist/preparation.d.ts +20 -0
  162. package/dist/preparation.d.ts.map +1 -0
  163. package/dist/preparation.js +233 -0
  164. package/dist/presence.d.ts +52 -0
  165. package/dist/presence.d.ts.map +1 -0
  166. package/dist/presence.js +220 -0
  167. package/dist/queries.d.ts +16 -0
  168. package/dist/queries.d.ts.map +1 -0
  169. package/dist/queries.js +270 -0
  170. package/dist/query.d.ts +51 -183
  171. package/dist/query.d.ts.map +1 -1
  172. package/dist/query.js +3 -464
  173. package/dist/{chat/questions.d.ts → questions.d.ts} +5 -2
  174. package/dist/questions.d.ts.map +1 -0
  175. package/dist/{chat/questions.js → questions.js} +13 -2
  176. package/dist/queue.d.ts +40 -0
  177. package/dist/queue.d.ts.map +1 -0
  178. package/dist/queue.js +455 -0
  179. package/dist/reach.d.ts +21 -5
  180. package/dist/reach.d.ts.map +1 -1
  181. package/dist/reach.js +313 -72
  182. package/dist/react.d.ts +20 -6
  183. package/dist/react.d.ts.map +1 -1
  184. package/dist/react.js +17 -18
  185. package/dist/{chat/read.d.ts → read.d.ts} +2 -0
  186. package/dist/read.d.ts.map +1 -0
  187. package/dist/{chat/read.js → read.js} +4 -0
  188. package/dist/readiness.d.ts +49 -0
  189. package/dist/readiness.d.ts.map +1 -0
  190. package/dist/readiness.js +27 -0
  191. package/dist/reads.d.ts +37 -0
  192. package/dist/reads.d.ts.map +1 -0
  193. package/dist/reads.js +174 -0
  194. package/dist/recovery.d.ts +26 -0
  195. package/dist/recovery.d.ts.map +1 -0
  196. package/dist/recovery.js +232 -0
  197. package/dist/refresh.d.ts +17 -14
  198. package/dist/refresh.d.ts.map +1 -1
  199. package/dist/refresh.js +145 -58
  200. package/dist/refusal.d.ts +5 -0
  201. package/dist/refusal.d.ts.map +1 -0
  202. package/dist/refusal.js +57 -0
  203. package/dist/replica.d.ts +53 -33
  204. package/dist/replica.d.ts.map +1 -1
  205. package/dist/replica.js +348 -166
  206. package/dist/room-draft.d.ts +55 -9
  207. package/dist/room-draft.d.ts.map +1 -1
  208. package/dist/room-draft.js +262 -73
  209. package/dist/room-drafts.d.ts +24 -0
  210. package/dist/room-drafts.d.ts.map +1 -0
  211. package/dist/room-drafts.js +217 -0
  212. package/dist/rows.d.ts +30 -35
  213. package/dist/rows.d.ts.map +1 -1
  214. package/dist/rows.js +60 -126
  215. package/dist/runtime.d.ts +34 -0
  216. package/dist/runtime.d.ts.map +1 -0
  217. package/dist/runtime.js +1 -0
  218. package/dist/save.d.ts +44 -22
  219. package/dist/save.d.ts.map +1 -1
  220. package/dist/save.js +950 -169
  221. package/dist/saves.d.ts +32 -0
  222. package/dist/saves.d.ts.map +1 -0
  223. package/dist/saves.js +413 -0
  224. package/dist/search.d.ts +15 -55
  225. package/dist/search.d.ts.map +1 -1
  226. package/dist/search.js +10 -121
  227. package/dist/seed.d.ts +25 -0
  228. package/dist/seed.d.ts.map +1 -0
  229. package/dist/seed.js +145 -0
  230. package/dist/shared.d.ts +10 -0
  231. package/dist/shared.d.ts.map +1 -0
  232. package/dist/shared.js +339 -0
  233. package/dist/shown.d.ts +32 -0
  234. package/dist/shown.d.ts.map +1 -0
  235. package/dist/shown.js +290 -0
  236. package/dist/socket/admission.d.ts +7 -0
  237. package/dist/socket/admission.d.ts.map +1 -0
  238. package/dist/socket/admission.js +39 -0
  239. package/dist/socket/answers.d.ts +6 -0
  240. package/dist/socket/answers.d.ts.map +1 -0
  241. package/dist/socket/answers.js +20 -0
  242. package/dist/socket/lanes.d.ts +20 -0
  243. package/dist/socket/lanes.d.ts.map +1 -0
  244. package/dist/socket/lanes.js +77 -0
  245. package/dist/socket/reads.d.ts +10 -0
  246. package/dist/socket/reads.d.ts.map +1 -0
  247. package/dist/socket/reads.js +31 -0
  248. package/dist/socket/session.d.ts +38 -0
  249. package/dist/socket/session.d.ts.map +1 -0
  250. package/dist/socket/session.js +23 -0
  251. package/dist/socket.d.ts +67 -0
  252. package/dist/socket.d.ts.map +1 -0
  253. package/dist/socket.js +170 -0
  254. package/dist/source.d.ts +61 -0
  255. package/dist/source.d.ts.map +1 -0
  256. package/dist/source.js +82 -0
  257. package/dist/status.d.ts +5 -2
  258. package/dist/status.d.ts.map +1 -1
  259. package/dist/status.js +3 -7
  260. package/dist/store/installs.d.ts +30 -0
  261. package/dist/store/installs.d.ts.map +1 -0
  262. package/dist/store/installs.js +34 -0
  263. package/dist/store/readiness.d.ts +10 -0
  264. package/dist/store/readiness.d.ts.map +1 -0
  265. package/dist/store/readiness.js +52 -0
  266. package/dist/store/records.d.ts +212 -0
  267. package/dist/store/records.d.ts.map +1 -0
  268. package/dist/store/records.js +430 -0
  269. package/dist/store/room-held.d.ts +8 -0
  270. package/dist/store/room-held.d.ts.map +1 -0
  271. package/dist/store/room-held.js +17 -0
  272. package/dist/store/space.d.ts +11 -0
  273. package/dist/store/space.d.ts.map +1 -0
  274. package/dist/store/space.js +41 -0
  275. package/dist/store/watchers.d.ts +17 -0
  276. package/dist/store/watchers.d.ts.map +1 -0
  277. package/dist/store/watchers.js +56 -0
  278. package/dist/store/writes.d.ts +24 -0
  279. package/dist/store/writes.d.ts.map +1 -0
  280. package/dist/store/writes.js +125 -0
  281. package/dist/store.d.ts +109 -117
  282. package/dist/store.d.ts.map +1 -1
  283. package/dist/store.js +155 -494
  284. package/dist/stored.d.ts +156 -0
  285. package/dist/stored.d.ts.map +1 -0
  286. package/dist/stored.js +649 -0
  287. package/dist/surface.d.ts +88 -0
  288. package/dist/surface.d.ts.map +1 -0
  289. package/dist/surface.js +223 -0
  290. package/dist/sync.d.ts +19 -0
  291. package/dist/sync.d.ts.map +1 -0
  292. package/dist/sync.js +521 -0
  293. package/dist/tables.d.ts +412 -0
  294. package/dist/tables.d.ts.map +1 -0
  295. package/dist/tables.js +737 -0
  296. package/dist/tallies.d.ts +18 -0
  297. package/dist/tallies.d.ts.map +1 -0
  298. package/dist/tallies.js +129 -0
  299. package/dist/transfer.d.ts +0 -5
  300. package/dist/transfer.d.ts.map +1 -1
  301. package/dist/turn.d.ts +9 -0
  302. package/dist/turn.d.ts.map +1 -0
  303. package/dist/turn.js +75 -0
  304. package/dist/validation.d.ts +29 -0
  305. package/dist/validation.d.ts.map +1 -0
  306. package/dist/validation.js +104 -0
  307. package/dist/windows.d.ts +11 -0
  308. package/dist/windows.d.ts.map +1 -0
  309. package/dist/windows.js +16 -0
  310. package/dist/wire.d.ts +220 -34
  311. package/dist/wire.d.ts.map +1 -1
  312. package/dist/wire.js +73 -31
  313. package/package.json +27 -15
  314. package/AGENTS.md +0 -290
  315. package/dist/attachment.d.ts +0 -3
  316. package/dist/attachment.d.ts.map +0 -1
  317. package/dist/attachment.js +0 -8
  318. package/dist/chat/acts.d.ts +0 -34
  319. package/dist/chat/acts.d.ts.map +0 -1
  320. package/dist/chat/acts.js +0 -36
  321. package/dist/chat/messages.d.ts +0 -21
  322. package/dist/chat/messages.d.ts.map +0 -1
  323. package/dist/chat/messages.js +0 -151
  324. package/dist/chat/overlay.d.ts +0 -31
  325. package/dist/chat/overlay.d.ts.map +0 -1
  326. package/dist/chat/overlay.js +0 -97
  327. package/dist/chat/parts.d.ts +0 -57
  328. package/dist/chat/parts.d.ts.map +0 -1
  329. package/dist/chat/parts.js +0 -119
  330. package/dist/chat/process.d.ts +0 -20
  331. package/dist/chat/process.d.ts.map +0 -1
  332. package/dist/chat/process.js +0 -32
  333. package/dist/chat/provider.d.ts +0 -57
  334. package/dist/chat/provider.d.ts.map +0 -1
  335. package/dist/chat/provider.js +0 -637
  336. package/dist/chat/questions.d.ts.map +0 -1
  337. package/dist/chat/read.d.ts.map +0 -1
  338. package/dist/chat.d.ts +0 -7
  339. package/dist/chat.d.ts.map +0 -1
  340. package/dist/chat.js +0 -2
  341. package/dist/read-side.d.ts +0 -18
  342. package/dist/read-side.d.ts.map +0 -1
  343. package/dist/read-side.js +0 -433
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useCallback, useLayoutEffect, useRef, useState } from 'react';
2
- import { failed, runtimeError } from './failure.js';
1
+ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
2
+ import { failed } from './failure.js';
3
3
  const LOADING = { state: 'loading' };
4
4
  export function useObserve(client, topic, ...input) {
5
5
  const key = JSON.stringify([topic, ...input]);
@@ -32,25 +32,22 @@ export function useObserve(client, topic, ...input) {
32
32
  }, [client, key]);
33
33
  return held !== null && held.client === client && held.key === key ? held.observed : LOADING;
34
34
  }
35
- const IDLE = { busy: false, outcome: null, failure: null };
36
- export async function saveRoomDrafts(client, room, roomDrafts) {
37
- const kept = [];
38
- for (const [path, parts] of roomDrafts) {
39
- const prior = await client.roomDraft.read(room, path);
40
- const write = await client.roomDraft.write(room, path, prior?.revision ?? null, parts);
41
- if (write.outcome !== 'written' || write.roomDraft === null) {
42
- throw runtimeError(write.outcome === 'stale' ? write.code : 'room-draft-unwritten', `the Room Draft at ${path} was not written`);
43
- }
44
- kept.push({ path, revision: write.roomDraft.revision });
45
- }
46
- return client.save(room, kept);
35
+ export function useReading(store, asks, shown) {
36
+ const reader = useId();
37
+ useLayoutEffect(() => store.reads(reader, asks), [store, reader, asks]);
38
+ useLayoutEffect(() => () => store.left(reader), [store, reader]);
39
+ useEffect(() => {
40
+ if (shown !== null)
41
+ store.heard(reader, shown);
42
+ }, [store, reader, shown]);
47
43
  }
44
+ const IDLE = { busy: false, outcome: null, failure: null };
48
45
  export function useSave(client) {
49
46
  const [state, setState] = useState(IDLE);
50
- const save = useCallback(async (room, roomDrafts) => {
47
+ const settled = useCallback(async (saving) => {
51
48
  setState({ busy: true, outcome: null, failure: null });
52
49
  try {
53
- const outcome = await saveRoomDrafts(client, room, roomDrafts);
50
+ const outcome = await saving();
54
51
  setState({ busy: false, outcome, failure: null });
55
52
  return outcome;
56
53
  }
@@ -59,6 +56,8 @@ export function useSave(client) {
59
56
  setState({ busy: false, outcome: null, failure });
60
57
  throw failure;
61
58
  }
62
- }, [client]);
63
- return { ...state, save };
59
+ }, []);
60
+ const save = useCallback((room, parts) => settled(() => client.saveParts(room, [...parts].map(([path, one]) => ({ path, parts: one })))), [client, settled]);
61
+ const saveWritten = useCallback((room, roomDrafts) => settled(() => client.save(room, roomDrafts)), [client, settled]);
62
+ return { ...state, save, saveWritten };
64
63
  }
@@ -1,4 +1,6 @@
1
1
  export type Fields = Readonly<Record<string, unknown>>;
2
+ export declare const EXCERPT = 512;
3
+ export declare function excerpt(said: string): string;
2
4
  export declare function record(entry: unknown): Fields;
3
5
  export declare function text(fields: Fields, name: string): string | null;
4
6
  export declare function list(fields: Fields, name: string): readonly unknown[];
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvD,eAAO,MAAM,OAAO,MAAM,CAAC;AAE3B,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI7C;AAED,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,CAGrE"}
@@ -1,3 +1,7 @@
1
+ export const EXCERPT = 512;
2
+ export function excerpt(said) {
3
+ return [...said.slice(0, EXCERPT * 2)].slice(0, EXCERPT).join('');
4
+ }
1
5
  export function record(entry) {
2
6
  return typeof entry === 'object' && entry !== null && !Array.isArray(entry)
3
7
  ? entry
@@ -0,0 +1,49 @@
1
+ import type { Open } from './open.js';
2
+ export type CodeAvailability = Open<'stored' | 'absent'>;
3
+ export type RoomPreparation = Open<'complete' | 'acquiring' | 'preparing' | 'unadmitted'>;
4
+ export type RoomCoverage = {
5
+ readonly room: string;
6
+ readonly state: RoomPreparation;
7
+ };
8
+ export type ConversationCoverage = Open<'complete' | 'acquiring' | 'undeclared'>;
9
+ export type TargetCoverage = {
10
+ readonly complete: boolean;
11
+ readonly listed: boolean;
12
+ readonly rooms: readonly RoomCoverage[];
13
+ readonly conversations: ConversationCoverage;
14
+ };
15
+ export type Persistence = Open<'granted' | 'denied' | 'unavailable'>;
16
+ export type Durability = Open<'persistent' | 'best-effort'>;
17
+ export type ConnectionAvailability = Open<'connected' | 'connecting' | 'unreachable'>;
18
+ export type HouseOutcome = Open<'open' | 'write-halted' | 'refused'>;
19
+ export type Space = Open<'full'>;
20
+ export type InstallationStep = Open<'downloading' | 'writing' | 'replaying'>;
21
+ export type InstallationAmount = {
22
+ readonly done: number;
23
+ readonly total: number;
24
+ };
25
+ export type Installation = {
26
+ readonly step: InstallationStep | null;
27
+ readonly bytes: InstallationAmount;
28
+ };
29
+ export type ReadinessStatus = Open<'preparing' | 'paused' | 'ready' | 'lost'>;
30
+ export type Readiness = {
31
+ readonly status: ReadinessStatus;
32
+ readonly ready: boolean;
33
+ readonly usable: boolean;
34
+ readonly code: CodeAvailability;
35
+ readonly coverage: TargetCoverage;
36
+ readonly durability: Durability;
37
+ readonly persistence: Persistence;
38
+ readonly connection: ConnectionAvailability;
39
+ readonly house: HouseOutcome;
40
+ readonly space: Space | null;
41
+ readonly installation: Installation;
42
+ readonly lost: boolean;
43
+ };
44
+ export type ReadinessFacts = Omit<Readiness, 'status' | 'ready' | 'usable' | 'durability' | 'coverage'> & {
45
+ readonly coverage: Omit<TargetCoverage, 'complete'>;
46
+ readonly established: boolean;
47
+ };
48
+ export declare function readinessOf(facts: ReadinessFacts): Readiness;
49
+ //# sourceMappingURL=readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AAE1F,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;CAAE,CAAC;AAEtF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AAEjF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,GAAG,QAAQ,GAAG,aAAa,CAAC,CAAC;AAErE,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC;AAE5D,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,WAAW,GAAG,YAAY,GAAG,aAAa,CAAC,CAAC;AAEtF,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC,CAAC;AAErE,MAAM,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;AAEjC,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC,CAAC;AAE7E,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAE9E,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,SAAS,EACT,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,CAC1D,GAAG;IACF,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B,CAAC;AAIF,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CA2B5D"}
@@ -0,0 +1,27 @@
1
+ const COMPLETE_ROOMS = new Set(['complete', 'unadmitted']);
2
+ export function readinessOf(facts) {
3
+ const { established, coverage: held, ...reported } = facts;
4
+ const settledConversations = held.conversations === 'complete' || held.conversations === 'undeclared';
5
+ const complete = held.listed &&
6
+ settledConversations &&
7
+ held.rooms.every((room) => COMPLETE_ROOMS.has(room.state));
8
+ const coverage = { ...held, complete };
9
+ const stored = facts.code === 'stored';
10
+ const ready = stored && complete;
11
+ const usable = stored && (ready || established);
12
+ const status = ready
13
+ ? 'ready'
14
+ : facts.space !== null
15
+ ? 'paused'
16
+ : facts.lost && !established
17
+ ? 'lost'
18
+ : 'preparing';
19
+ return {
20
+ ...reported,
21
+ coverage,
22
+ status,
23
+ ready,
24
+ usable,
25
+ durability: facts.persistence === 'granted' ? 'persistent' : 'best-effort',
26
+ };
27
+ }
@@ -0,0 +1,37 @@
1
+ import { type CallResult } from './outcome.js';
2
+ import type { Recovery } from './recovery.js';
3
+ import type { Connection, RuntimeDatabase } from './tables.js';
4
+ export declare const READ_TABLES: readonly string[];
5
+ export type ReadAt = {
6
+ readonly name: string;
7
+ readonly input: string;
8
+ readonly build: string;
9
+ };
10
+ export type ObservedRead = {
11
+ readonly answer: unknown;
12
+ readonly failure: CallResult | null;
13
+ readonly kept: boolean;
14
+ };
15
+ export type ReadAnswers = {
16
+ watch(name: string, input: unknown, deliver: (read: ObservedRead) => void): () => void;
17
+ again(): void;
18
+ reopened(): void;
19
+ };
20
+ export type ReadAnswersOptions = {
21
+ readonly database: RuntimeDatabase;
22
+ readonly build: string;
23
+ readonly unkept: readonly string[];
24
+ readonly leading: readonly string[];
25
+ readonly recovery: Recovery;
26
+ call(name: string, input: unknown): Promise<CallResult>;
27
+ online(): boolean;
28
+ streaming(): boolean;
29
+ closed(): boolean;
30
+ full(): boolean;
31
+ filled(): void;
32
+ };
33
+ export declare function storedRead(connection: Connection, { name, input, build }: ReadAt): string | null;
34
+ export declare function keptRead(connection: Connection, { name, input, build }: ReadAt, answer: string): null;
35
+ export declare function readsOfOtherBuildsForgotten(connection: Connection, build: string): null;
36
+ export declare function readAnswers(options: ReadAnswersOptions): ReadAnswers;
37
+ //# sourceMappingURL=reads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reads.d.ts","sourceRoot":"","sources":["../src/reads.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE/D,eAAO,MAAM,WAAW,EAAE,SAAS,MAAM,EAExC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACvF,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,IAAI,IAAI,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,IAAI,OAAO,CAAC;IAClB,SAAS,IAAI,OAAO,CAAC;IACrB,MAAM,IAAI,OAAO,CAAC;IAClB,IAAI,IAAI,OAAO,CAAC;IAChB,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAeF,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhG;AAED,wBAAgB,QAAQ,CACtB,UAAU,EAAE,UAAU,EACtB,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,MAAM,EAC9B,MAAM,EAAE,MAAM,GACb,IAAI,CAMN;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGvF;AAMD,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAoJpE"}
package/dist/reads.js ADDED
@@ -0,0 +1,174 @@
1
+ import { refusedForSpace } from './durability.js';
2
+ import { uncaught } from './failure.js';
3
+ import { answeredResult, UNCERTAIN } from './outcome.js';
4
+ export const READ_TABLES = [
5
+ 'CREATE TABLE read_answers (name TEXT NOT NULL, input TEXT NOT NULL, build TEXT NOT NULL, answer TEXT NOT NULL, PRIMARY KEY (name, input)) WITHOUT ROWID',
6
+ ];
7
+ export function storedRead(connection, { name, input, build }) {
8
+ const [held] = connection.rows('SELECT answer FROM read_answers WHERE name = ? AND input = ? AND build = ?', [name, input, build]);
9
+ return held === undefined ? null : String(held.answer);
10
+ }
11
+ export function keptRead(connection, { name, input, build }, answer) {
12
+ connection.run('INSERT OR REPLACE INTO read_answers (name, input, build, answer) VALUES (?, ?, ?, ?)', [name, input, build, answer]);
13
+ return null;
14
+ }
15
+ export function readsOfOtherBuildsForgotten(connection, build) {
16
+ connection.run('DELETE FROM read_answers WHERE build <> ?', [build]);
17
+ return null;
18
+ }
19
+ function answered(result) {
20
+ return result.kind === 'action' && answeredResult(result.result);
21
+ }
22
+ export function readAnswers(options) {
23
+ const { database, build, recovery } = options;
24
+ const unkept = new Set(options.unkept);
25
+ const leading = new Set(options.leading);
26
+ const reads = new Map();
27
+ function leads(read) {
28
+ return leading.has(read.at.name);
29
+ }
30
+ function show(read, shown) {
31
+ read.shown = shown;
32
+ for (const deliver of read.observers)
33
+ deliver(shown);
34
+ }
35
+ async function kept(read, answer) {
36
+ if (!read.shown.kept || options.full())
37
+ return;
38
+ await database.ask({ kind: 'read.keep', ...read.at, answer }).catch((error) => {
39
+ if (refusedForSpace(error))
40
+ options.filled();
41
+ else
42
+ uncaught(error);
43
+ });
44
+ }
45
+ async function asked(read, led) {
46
+ if (!options.online()) {
47
+ read.owed = true;
48
+ return;
49
+ }
50
+ const result = await options.call(read.at.name, read.asked).catch(() => UNCERTAIN);
51
+ if (options.closed())
52
+ return;
53
+ read.owed = !answered(result);
54
+ if (read.owed) {
55
+ show(read, { ...read.shown, failure: result });
56
+ return;
57
+ }
58
+ const answer = result.result;
59
+ const text = JSON.stringify(answer);
60
+ if (text === read.text && read.shown.failure === null)
61
+ return;
62
+ read.text = text;
63
+ await kept(read, text);
64
+ await led;
65
+ if (!options.closed())
66
+ show(read, { ...read.shown, answer, failure: null });
67
+ }
68
+ function lost(read) {
69
+ return read.owed && read.shown.failure?.kind === 'transport-uncertainty' && options.streaming();
70
+ }
71
+ function retrying(read) {
72
+ return `${read.key} again`;
73
+ }
74
+ function askedLater(read) {
75
+ void recovery.attempt(retrying(read), async () => {
76
+ if (options.closed() || read.observers.size === 0 || !lost(read))
77
+ return 'done';
78
+ await attempted(read);
79
+ return lost(read) ? 'again' : 'done';
80
+ }, { again: true });
81
+ }
82
+ function forgotten(read) {
83
+ read.text = null;
84
+ read.shown = { ...read.shown, answer: null, failure: null };
85
+ }
86
+ function attempted(read, led) {
87
+ read.asking = true;
88
+ return recovery
89
+ .attempt(read.key, async () => {
90
+ await asked(read, led);
91
+ return 'done';
92
+ })
93
+ .finally(() => {
94
+ const unanswered = read.joined && read.owed;
95
+ read.asking = false;
96
+ read.joined = false;
97
+ if (unanswered)
98
+ void ask(read);
99
+ });
100
+ }
101
+ function ask(read, led) {
102
+ return attempted(read, led).finally(() => {
103
+ if (lost(read))
104
+ askedLater(read);
105
+ });
106
+ }
107
+ function opened(name, input, key) {
108
+ const held = !unkept.has(name);
109
+ const read = {
110
+ key,
111
+ at: { name, input: JSON.stringify(input ?? null), build },
112
+ asked: input,
113
+ observers: new Set(),
114
+ stored: Promise.resolve(),
115
+ text: null,
116
+ shown: { answer: null, failure: null, kept: held },
117
+ owed: false,
118
+ asking: false,
119
+ joined: false,
120
+ };
121
+ if (held) {
122
+ read.stored = database.ask({ kind: 'read.stored', ...read.at }).then((text) => {
123
+ if (text === null || read.text !== null)
124
+ return;
125
+ read.text = text;
126
+ read.shown = { ...read.shown, answer: JSON.parse(text) };
127
+ }, uncaught);
128
+ }
129
+ reads.set(key, read);
130
+ return read;
131
+ }
132
+ return {
133
+ watch(name, input, deliver) {
134
+ const key = JSON.stringify([name, input ?? null]);
135
+ const read = reads.get(key) ?? opened(name, input, key);
136
+ read.observers.add(deliver);
137
+ void read.stored.then(() => {
138
+ if (!read.observers.has(deliver))
139
+ return;
140
+ if (!read.shown.kept)
141
+ forgotten(read);
142
+ deliver(read.shown);
143
+ if (read.asking)
144
+ read.joined = true;
145
+ else
146
+ void ask(read);
147
+ });
148
+ return () => {
149
+ read.observers.delete(deliver);
150
+ if (read.observers.size === 0 && reads.get(key) === read)
151
+ reads.delete(key);
152
+ };
153
+ },
154
+ again() {
155
+ const all = [...reads.values()];
156
+ const led = Promise.all(all.filter(leads).map((read) => ask(read)));
157
+ for (const read of all)
158
+ if (!leads(read))
159
+ void ask(read, led);
160
+ },
161
+ reopened() {
162
+ for (const read of reads.values()) {
163
+ recovery.cancel(retrying(read));
164
+ if (read.text === null) {
165
+ if (read.owed)
166
+ void ask(read);
167
+ }
168
+ else if (read.shown.failure?.kind === 'transport-uncertainty') {
169
+ show(read, { ...read.shown, failure: null });
170
+ }
171
+ }
172
+ },
173
+ };
174
+ }
@@ -0,0 +1,26 @@
1
+ export type Attempted = 'done' | 'again' | {
2
+ readonly at: number;
3
+ };
4
+ export type Attempt = (signal: AbortSignal) => Promise<Attempted>;
5
+ export type AttemptOptions = {
6
+ readonly at?: number;
7
+ readonly again?: boolean;
8
+ };
9
+ export type RecoveryOptions = {
10
+ readonly firstMs?: number;
11
+ readonly maxMs?: number;
12
+ readonly attempts?: number;
13
+ readonly inFlight?: number;
14
+ };
15
+ export type Recovery = {
16
+ attempt(intent: string, run: Attempt, options?: AttemptOptions): Promise<boolean>;
17
+ pending(intent: string): boolean;
18
+ resume(): void;
19
+ cancel(intent: string): void;
20
+ scope(inFlight?: number): Recovery;
21
+ retire(): Promise<void>;
22
+ };
23
+ export declare function retryDelay(tries: number, firstMs?: number, maxMs?: number): number;
24
+ export declare function untilAnswered<T>(ask: () => Promise<T>, signal: AbortSignal, answered?: (answer: T) => boolean): Promise<T>;
25
+ export declare function createRecovery(options?: RecoveryOptions): Recovery;
26
+ //# sourceMappingURL=recovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,MAAM,IAAI,IAAI,CAAC;IACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAsBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAW,EAAE,KAAK,SAAS,GAAG,MAAM,CAGpF;AAED,wBAAsB,aAAa,CAAC,CAAC,EACnC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACrB,MAAM,EAAE,WAAW,EACnB,QAAQ,GAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAoB,GAC5C,OAAO,CAAC,CAAC,CAAC,CAsBZ;AA8KD,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAStE"}
@@ -0,0 +1,232 @@
1
+ // A first retry waits about a second, so a passing failure clears before the next attempt.
2
+ const FIRST_MS = 1_000;
3
+ // Waits stop doubling at half a minute, so work resumes soon after a long failure ends.
4
+ const MAX_MS = 30_000;
5
+ const ATTEMPTS = 8;
6
+ export function retryDelay(tries, firstMs = FIRST_MS, maxMs = MAX_MS) {
7
+ const longest = Math.min(maxMs, firstMs * 2 ** tries);
8
+ return longest / 2 + (Math.random() * longest) / 2;
9
+ }
10
+ export async function untilAnswered(ask, signal, answered = () => true) {
11
+ for (let tries = 0;; tries += 1) {
12
+ try {
13
+ const answer = await ask();
14
+ if (answered(answer) || signal.aborted)
15
+ return answer;
16
+ }
17
+ catch (error) {
18
+ if (signal.aborted)
19
+ throw error;
20
+ }
21
+ await new Promise((resume) => {
22
+ const stop = () => {
23
+ clearTimeout(again);
24
+ resume();
25
+ };
26
+ // A read that failed is asked again on the doubling backoff, so its asker is answered once the failure clears.
27
+ const again = setTimeout(() => {
28
+ signal.removeEventListener('abort', stop);
29
+ resume();
30
+ }, retryDelay(tries));
31
+ signal.addEventListener('abort', stop, { once: true });
32
+ });
33
+ signal.throwIfAborted();
34
+ }
35
+ }
36
+ function owner(backoff, inFlight, release = () => { }) {
37
+ const intents = new Map();
38
+ const children = new Set();
39
+ const running = new Set();
40
+ const queued = [];
41
+ let free = inFlight;
42
+ let retired = false;
43
+ function finish(key, intent, done) {
44
+ if (intents.get(key) === intent)
45
+ intents.delete(key);
46
+ if (intent.timer !== null)
47
+ clearTimeout(intent.timer);
48
+ intent.timer = null;
49
+ intent.end(done);
50
+ }
51
+ function wait(key, intent, at, guided) {
52
+ if (intent.timer !== null)
53
+ clearTimeout(intent.timer);
54
+ intent.due = at;
55
+ intent.guided = guided;
56
+ intent.timer = setTimeout(() => {
57
+ intent.timer = null;
58
+ launch(key, intent);
59
+ }, Math.max(0, at - Date.now()));
60
+ }
61
+ function launch(key, intent) {
62
+ intent.due = null;
63
+ intent.guided = false;
64
+ const controller = new AbortController();
65
+ intent.controller = controller;
66
+ const run = start(key, intent, controller);
67
+ running.add(run);
68
+ void run.finally(() => running.delete(run));
69
+ }
70
+ async function start(key, intent, controller) {
71
+ while (free === 0)
72
+ await new Promise((admit) => queued.push(admit));
73
+ if (controller.signal.aborted) {
74
+ queued.shift()?.();
75
+ return;
76
+ }
77
+ free -= 1;
78
+ const abandoned = new Promise((resolve) => {
79
+ controller.signal.addEventListener('abort', () => resolve(null), { once: true });
80
+ });
81
+ let result;
82
+ try {
83
+ result = await Promise.race([intent.run(controller.signal), abandoned]);
84
+ }
85
+ catch {
86
+ result = 'again';
87
+ }
88
+ finally {
89
+ free += 1;
90
+ queued.shift()?.();
91
+ }
92
+ if (result === null || controller.signal.aborted || intents.get(key) !== intent)
93
+ return;
94
+ intent.controller = null;
95
+ const again = intent.again;
96
+ intent.again = null;
97
+ if (result === 'done') {
98
+ intent.tries = 0;
99
+ if (again === null)
100
+ finish(key, intent, true);
101
+ else if (again === 'now')
102
+ launch(key, intent);
103
+ else
104
+ wait(key, intent, again, true);
105
+ return;
106
+ }
107
+ intent.tries += 1;
108
+ if (intent.tries >= backoff.attempts) {
109
+ if (again === null) {
110
+ finish(key, intent, false);
111
+ return;
112
+ }
113
+ intent.tries = 0;
114
+ if (again === 'now')
115
+ launch(key, intent);
116
+ else
117
+ wait(key, intent, again, true);
118
+ return;
119
+ }
120
+ if (result === 'again') {
121
+ // The next attempt waits the doubling backoff, up to its maximum.
122
+ const delay = retryDelay(intent.tries - 1, backoff.firstMs, backoff.maxMs);
123
+ wait(key, intent, Date.now() + delay, false);
124
+ }
125
+ else
126
+ wait(key, intent, result.at, true);
127
+ }
128
+ const recovery = {
129
+ attempt(key, run, options = {}) {
130
+ if (retired)
131
+ return Promise.resolve(false);
132
+ const held = intents.get(key);
133
+ if (held !== undefined) {
134
+ held.run = run;
135
+ if (held.controller !== null) {
136
+ const at = options.at;
137
+ held.again =
138
+ at === undefined || held.again === 'now'
139
+ ? 'now'
140
+ : Math.min(at, held.again ?? Number.POSITIVE_INFINITY);
141
+ }
142
+ else if (held.due !== null && held.due <= Date.now()) {
143
+ if (held.timer !== null)
144
+ clearTimeout(held.timer);
145
+ held.timer = null;
146
+ launch(key, held);
147
+ }
148
+ else if (options.at !== undefined && (held.due === null || options.at < held.due)) {
149
+ wait(key, held, options.at, true);
150
+ }
151
+ return held.ended;
152
+ }
153
+ let end = () => { };
154
+ const ended = new Promise((resolve) => {
155
+ end = resolve;
156
+ });
157
+ const intent = {
158
+ run,
159
+ tries: 0,
160
+ timer: null,
161
+ due: null,
162
+ guided: false,
163
+ controller: null,
164
+ again: null,
165
+ ended,
166
+ end,
167
+ };
168
+ intents.set(key, intent);
169
+ if (options.at !== undefined)
170
+ wait(key, intent, options.at, true);
171
+ else if (options.again === true) {
172
+ intent.tries = 1;
173
+ wait(key, intent, Date.now() + retryDelay(0, backoff.firstMs, backoff.maxMs), false);
174
+ }
175
+ else
176
+ launch(key, intent);
177
+ return ended;
178
+ },
179
+ pending(key) {
180
+ const intent = intents.get(key);
181
+ return intent !== undefined && intent.controller === null;
182
+ },
183
+ resume() {
184
+ if (retired)
185
+ return;
186
+ for (const [key, intent] of [...intents]) {
187
+ if (intent.guided && intent.due !== null && intent.due > Date.now())
188
+ continue;
189
+ if (intent.timer !== null)
190
+ clearTimeout(intent.timer);
191
+ intent.timer = null;
192
+ intent.controller?.abort();
193
+ intent.again = null;
194
+ intent.tries = 0;
195
+ launch(key, intent);
196
+ }
197
+ },
198
+ cancel(key) {
199
+ const intent = intents.get(key);
200
+ if (intent === undefined)
201
+ return;
202
+ intent.controller?.abort();
203
+ finish(key, intent, false);
204
+ },
205
+ scope(bound = Infinity) {
206
+ const child = owner(backoff, bound, () => children.delete(child));
207
+ if (retired)
208
+ void child.retire();
209
+ else
210
+ children.add(child);
211
+ return child;
212
+ },
213
+ async retire() {
214
+ retired = true;
215
+ release();
216
+ for (const [key, intent] of [...intents]) {
217
+ intent.controller?.abort();
218
+ finish(key, intent, false);
219
+ }
220
+ await Promise.all([...children].map((child) => child.retire()));
221
+ await Promise.all(running);
222
+ },
223
+ };
224
+ return recovery;
225
+ }
226
+ export function createRecovery(options = {}) {
227
+ return owner({
228
+ firstMs: options.firstMs ?? FIRST_MS,
229
+ maxMs: options.maxMs ?? MAX_MS,
230
+ attempts: options.attempts ?? ATTEMPTS,
231
+ }, options.inFlight ?? Infinity);
232
+ }