@ny4n/durable 0.0.0-stage → 1.0.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 (303) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +711 -2
  3. package/dist/documents.d.ts +66 -0
  4. package/dist/documents.d.ts.map +1 -0
  5. package/dist/documents.js +98 -0
  6. package/dist/documents.js.map +1 -0
  7. package/dist/entries.d.ts +31 -0
  8. package/dist/entries.d.ts.map +1 -0
  9. package/dist/entries.js +31 -0
  10. package/dist/entries.js.map +1 -0
  11. package/dist/env/decode.d.ts +16 -0
  12. package/dist/env/decode.d.ts.map +1 -0
  13. package/dist/env/decode.js +28 -0
  14. package/dist/env/decode.js.map +1 -0
  15. package/dist/env/index.d.ts +263 -0
  16. package/dist/env/index.d.ts.map +1 -0
  17. package/dist/env/index.js +49 -0
  18. package/dist/env/index.js.map +1 -0
  19. package/dist/env/line-scan.d.ts +13 -0
  20. package/dist/env/line-scan.d.ts.map +1 -0
  21. package/dist/env/line-scan.js +147 -0
  22. package/dist/env/line-scan.js.map +1 -0
  23. package/dist/env/node-watch.d.ts +29 -0
  24. package/dist/env/node-watch.d.ts.map +1 -0
  25. package/dist/env/node-watch.js +485 -0
  26. package/dist/env/node-watch.js.map +1 -0
  27. package/dist/env/node.d.ts +56 -0
  28. package/dist/env/node.d.ts.map +1 -0
  29. package/dist/env/node.js +1166 -0
  30. package/dist/env/node.js.map +1 -0
  31. package/dist/errors.d.ts +32 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +53 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/harness/agent.d.ts +33 -0
  36. package/dist/harness/agent.d.ts.map +1 -0
  37. package/dist/harness/agent.js +235 -0
  38. package/dist/harness/agent.js.map +1 -0
  39. package/dist/harness/assistant.d.ts +15 -0
  40. package/dist/harness/assistant.d.ts.map +1 -0
  41. package/dist/harness/assistant.js +23 -0
  42. package/dist/harness/assistant.js.map +1 -0
  43. package/dist/harness/compaction.d.ts +56 -0
  44. package/dist/harness/compaction.d.ts.map +1 -0
  45. package/dist/harness/compaction.js +384 -0
  46. package/dist/harness/compaction.js.map +1 -0
  47. package/dist/harness/context.d.ts +63 -0
  48. package/dist/harness/context.d.ts.map +1 -0
  49. package/dist/harness/context.js +221 -0
  50. package/dist/harness/context.js.map +1 -0
  51. package/dist/harness/define.d.ts +18 -0
  52. package/dist/harness/define.d.ts.map +1 -0
  53. package/dist/harness/define.js +25 -0
  54. package/dist/harness/define.js.map +1 -0
  55. package/dist/harness/events.d.ts +180 -0
  56. package/dist/harness/events.d.ts.map +1 -0
  57. package/dist/harness/events.js +370 -0
  58. package/dist/harness/events.js.map +1 -0
  59. package/dist/harness/generation.d.ts +54 -0
  60. package/dist/harness/generation.d.ts.map +1 -0
  61. package/dist/harness/generation.js +514 -0
  62. package/dist/harness/generation.js.map +1 -0
  63. package/dist/harness/harness.d.ts +17 -0
  64. package/dist/harness/harness.d.ts.map +1 -0
  65. package/dist/harness/harness.js +332 -0
  66. package/dist/harness/harness.js.map +1 -0
  67. package/dist/harness/inbox.d.ts +59 -0
  68. package/dist/harness/inbox.d.ts.map +1 -0
  69. package/dist/harness/inbox.js +97 -0
  70. package/dist/harness/inbox.js.map +1 -0
  71. package/dist/harness/json.d.ts +10 -0
  72. package/dist/harness/json.d.ts.map +1 -0
  73. package/dist/harness/json.js +33 -0
  74. package/dist/harness/json.js.map +1 -0
  75. package/dist/harness/live.d.ts +142 -0
  76. package/dist/harness/live.d.ts.map +1 -0
  77. package/dist/harness/live.js +149 -0
  78. package/dist/harness/live.js.map +1 -0
  79. package/dist/harness/output.d.ts +69 -0
  80. package/dist/harness/output.d.ts.map +1 -0
  81. package/dist/harness/output.js +318 -0
  82. package/dist/harness/output.js.map +1 -0
  83. package/dist/harness/prompt.d.ts +27 -0
  84. package/dist/harness/prompt.d.ts.map +1 -0
  85. package/dist/harness/prompt.js +131 -0
  86. package/dist/harness/prompt.js.map +1 -0
  87. package/dist/harness/provider.d.ts +14 -0
  88. package/dist/harness/provider.d.ts.map +1 -0
  89. package/dist/harness/provider.js +30 -0
  90. package/dist/harness/provider.js.map +1 -0
  91. package/dist/harness/registry.d.ts +6 -0
  92. package/dist/harness/registry.d.ts.map +1 -0
  93. package/dist/harness/registry.js +101 -0
  94. package/dist/harness/registry.js.map +1 -0
  95. package/dist/harness/run.d.ts +17 -0
  96. package/dist/harness/run.d.ts.map +1 -0
  97. package/dist/harness/run.js +26 -0
  98. package/dist/harness/run.js.map +1 -0
  99. package/dist/harness/scheduler.d.ts +106 -0
  100. package/dist/harness/scheduler.d.ts.map +1 -0
  101. package/dist/harness/scheduler.js +1743 -0
  102. package/dist/harness/scheduler.js.map +1 -0
  103. package/dist/harness/submissions.d.ts +29 -0
  104. package/dist/harness/submissions.d.ts.map +1 -0
  105. package/dist/harness/submissions.js +175 -0
  106. package/dist/harness/submissions.js.map +1 -0
  107. package/dist/harness/task-graph.d.ts +49 -0
  108. package/dist/harness/task-graph.d.ts.map +1 -0
  109. package/dist/harness/task-graph.js +168 -0
  110. package/dist/harness/task-graph.js.map +1 -0
  111. package/dist/harness/tool.d.ts +76 -0
  112. package/dist/harness/tool.d.ts.map +1 -0
  113. package/dist/harness/tool.js +736 -0
  114. package/dist/harness/tool.js.map +1 -0
  115. package/dist/harness/types.d.ts +713 -0
  116. package/dist/harness/types.d.ts.map +1 -0
  117. package/dist/harness/types.js +2 -0
  118. package/dist/harness/types.js.map +1 -0
  119. package/dist/harness/usage.d.ts +18 -0
  120. package/dist/harness/usage.d.ts.map +1 -0
  121. package/dist/harness/usage.js +57 -0
  122. package/dist/harness/usage.js.map +1 -0
  123. package/dist/harness/util.d.ts +19 -0
  124. package/dist/harness/util.d.ts.map +1 -0
  125. package/dist/harness/util.js +58 -0
  126. package/dist/harness/util.js.map +1 -0
  127. package/dist/harness/view.d.ts +47 -0
  128. package/dist/harness/view.d.ts.map +1 -0
  129. package/dist/harness/view.js +206 -0
  130. package/dist/harness/view.js.map +1 -0
  131. package/dist/ids.d.ts +6 -0
  132. package/dist/ids.d.ts.map +1 -0
  133. package/dist/ids.js +9 -0
  134. package/dist/ids.js.map +1 -0
  135. package/dist/images/cloudflare.d.ts +4 -0
  136. package/dist/images/cloudflare.d.ts.map +1 -0
  137. package/dist/images/cloudflare.js +9 -0
  138. package/dist/images/cloudflare.js.map +1 -0
  139. package/dist/images/exif.d.ts +3 -0
  140. package/dist/images/exif.d.ts.map +1 -0
  141. package/dist/images/exif.js +71 -0
  142. package/dist/images/exif.js.map +1 -0
  143. package/dist/images/index.d.ts +18 -0
  144. package/dist/images/index.d.ts.map +1 -0
  145. package/dist/images/index.js +125 -0
  146. package/dist/images/index.js.map +1 -0
  147. package/dist/images/node-worker.d.ts +17 -0
  148. package/dist/images/node-worker.d.ts.map +1 -0
  149. package/dist/images/node-worker.js +16 -0
  150. package/dist/images/node-worker.js.map +1 -0
  151. package/dist/images/node.d.ts +21 -0
  152. package/dist/images/node.d.ts.map +1 -0
  153. package/dist/images/node.js +121 -0
  154. package/dist/images/node.js.map +1 -0
  155. package/dist/index.d.ts +24 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +20 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/session/forks.d.ts +10 -0
  160. package/dist/session/forks.d.ts.map +1 -0
  161. package/dist/session/forks.js +42 -0
  162. package/dist/session/forks.js.map +1 -0
  163. package/dist/session/observation.d.ts +42 -0
  164. package/dist/session/observation.d.ts.map +1 -0
  165. package/dist/session/observation.js +281 -0
  166. package/dist/session/observation.js.map +1 -0
  167. package/dist/session/session.d.ts +133 -0
  168. package/dist/session/session.d.ts.map +1 -0
  169. package/dist/session/session.js +811 -0
  170. package/dist/session/session.js.map +1 -0
  171. package/dist/session/transaction.d.ts +119 -0
  172. package/dist/session/transaction.d.ts.map +1 -0
  173. package/dist/session/transaction.js +795 -0
  174. package/dist/session/transaction.js.map +1 -0
  175. package/dist/storage/jsonl/index.d.ts +2 -0
  176. package/dist/storage/jsonl/index.d.ts.map +1 -0
  177. package/dist/storage/jsonl/index.js +2 -0
  178. package/dist/storage/jsonl/index.js.map +1 -0
  179. package/dist/storage/jsonl/node.d.ts +6 -0
  180. package/dist/storage/jsonl/node.d.ts.map +1 -0
  181. package/dist/storage/jsonl/node.js +8 -0
  182. package/dist/storage/jsonl/node.js.map +1 -0
  183. package/dist/storage/jsonl/storage.d.ts +143 -0
  184. package/dist/storage/jsonl/storage.d.ts.map +1 -0
  185. package/dist/storage/jsonl/storage.js +682 -0
  186. package/dist/storage/jsonl/storage.js.map +1 -0
  187. package/dist/storage/memory.d.ts +62 -0
  188. package/dist/storage/memory.d.ts.map +1 -0
  189. package/dist/storage/memory.js +733 -0
  190. package/dist/storage/memory.js.map +1 -0
  191. package/dist/storage/scan.d.ts +18 -0
  192. package/dist/storage/scan.d.ts.map +1 -0
  193. package/dist/storage/scan.js +29 -0
  194. package/dist/storage/scan.js.map +1 -0
  195. package/dist/storage/sqlite/cloudflare.d.ts +46 -0
  196. package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
  197. package/dist/storage/sqlite/cloudflare.js +106 -0
  198. package/dist/storage/sqlite/cloudflare.js.map +1 -0
  199. package/dist/storage/sqlite/database.d.ts +37 -0
  200. package/dist/storage/sqlite/database.d.ts.map +1 -0
  201. package/dist/storage/sqlite/database.js +2 -0
  202. package/dist/storage/sqlite/database.js.map +1 -0
  203. package/dist/storage/sqlite/index.d.ts +4 -0
  204. package/dist/storage/sqlite/index.d.ts.map +1 -0
  205. package/dist/storage/sqlite/index.js +3 -0
  206. package/dist/storage/sqlite/index.js.map +1 -0
  207. package/dist/storage/sqlite/migrations.d.ts +11 -0
  208. package/dist/storage/sqlite/migrations.d.ts.map +1 -0
  209. package/dist/storage/sqlite/migrations.js +111 -0
  210. package/dist/storage/sqlite/migrations.js.map +1 -0
  211. package/dist/storage/sqlite/node.d.ts +41 -0
  212. package/dist/storage/sqlite/node.d.ts.map +1 -0
  213. package/dist/storage/sqlite/node.js +179 -0
  214. package/dist/storage/sqlite/node.js.map +1 -0
  215. package/dist/storage/sqlite/storage.d.ts +65 -0
  216. package/dist/storage/sqlite/storage.d.ts.map +1 -0
  217. package/dist/storage/sqlite/storage.js +670 -0
  218. package/dist/storage/sqlite/storage.js.map +1 -0
  219. package/dist/tasks.d.ts +6 -0
  220. package/dist/tasks.d.ts.map +1 -0
  221. package/dist/tasks.js +5 -0
  222. package/dist/tasks.js.map +1 -0
  223. package/dist/testing/assertions.d.ts +5 -0
  224. package/dist/testing/assertions.d.ts.map +1 -0
  225. package/dist/testing/assertions.js +25 -0
  226. package/dist/testing/assertions.js.map +1 -0
  227. package/dist/testing/env-conformance.d.ts +7 -0
  228. package/dist/testing/env-conformance.d.ts.map +1 -0
  229. package/dist/testing/env-conformance.js +431 -0
  230. package/dist/testing/env-conformance.js.map +1 -0
  231. package/dist/testing/index.d.ts +7 -0
  232. package/dist/testing/index.d.ts.map +1 -0
  233. package/dist/testing/index.js +6 -0
  234. package/dist/testing/index.js.map +1 -0
  235. package/dist/testing/runner.d.ts +12 -0
  236. package/dist/testing/runner.d.ts.map +1 -0
  237. package/dist/testing/runner.js +23 -0
  238. package/dist/testing/runner.js.map +1 -0
  239. package/dist/testing/storage-benchmark.d.ts +41 -0
  240. package/dist/testing/storage-benchmark.d.ts.map +1 -0
  241. package/dist/testing/storage-benchmark.js +350 -0
  242. package/dist/testing/storage-benchmark.js.map +1 -0
  243. package/dist/testing/storage-conformance.d.ts +4 -0
  244. package/dist/testing/storage-conformance.d.ts.map +1 -0
  245. package/dist/testing/storage-conformance.js +1213 -0
  246. package/dist/testing/storage-conformance.js.map +1 -0
  247. package/dist/testing/types.d.ts +37 -0
  248. package/dist/testing/types.d.ts.map +1 -0
  249. package/dist/testing/types.js +2 -0
  250. package/dist/testing/types.js.map +1 -0
  251. package/dist/tools/bash.d.ts +55 -0
  252. package/dist/tools/bash.d.ts.map +1 -0
  253. package/dist/tools/bash.js +138 -0
  254. package/dist/tools/bash.js.map +1 -0
  255. package/dist/tools/edit-diff.d.ts +89 -0
  256. package/dist/tools/edit-diff.d.ts.map +1 -0
  257. package/dist/tools/edit-diff.js +386 -0
  258. package/dist/tools/edit-diff.js.map +1 -0
  259. package/dist/tools/edit.d.ts +18 -0
  260. package/dist/tools/edit.d.ts.map +1 -0
  261. package/dist/tools/edit.js +117 -0
  262. package/dist/tools/edit.js.map +1 -0
  263. package/dist/tools/env.d.ts +5 -0
  264. package/dist/tools/env.d.ts.map +1 -0
  265. package/dist/tools/env.js +7 -0
  266. package/dist/tools/env.js.map +1 -0
  267. package/dist/tools/file-mutation-queue.d.ts +9 -0
  268. package/dist/tools/file-mutation-queue.d.ts.map +1 -0
  269. package/dist/tools/file-mutation-queue.js +52 -0
  270. package/dist/tools/file-mutation-queue.js.map +1 -0
  271. package/dist/tools/image-processor.d.ts +47 -0
  272. package/dist/tools/image-processor.d.ts.map +1 -0
  273. package/dist/tools/image-processor.js +30 -0
  274. package/dist/tools/image-processor.js.map +1 -0
  275. package/dist/tools/image.d.ts +20 -0
  276. package/dist/tools/image.d.ts.map +1 -0
  277. package/dist/tools/image.js +178 -0
  278. package/dist/tools/image.js.map +1 -0
  279. package/dist/tools/index.d.ts +46 -0
  280. package/dist/tools/index.d.ts.map +1 -0
  281. package/dist/tools/index.js +23 -0
  282. package/dist/tools/index.js.map +1 -0
  283. package/dist/tools/path-utils.d.ts +5 -0
  284. package/dist/tools/path-utils.d.ts.map +1 -0
  285. package/dist/tools/path-utils.js +26 -0
  286. package/dist/tools/path-utils.js.map +1 -0
  287. package/dist/tools/read.d.ts +25 -0
  288. package/dist/tools/read.d.ts.map +1 -0
  289. package/dist/tools/read.js +235 -0
  290. package/dist/tools/read.js.map +1 -0
  291. package/dist/tools/write.d.ts +10 -0
  292. package/dist/tools/write.d.ts.map +1 -0
  293. package/dist/tools/write.js +31 -0
  294. package/dist/tools/write.js.map +1 -0
  295. package/dist/truncate.d.ts +64 -0
  296. package/dist/truncate.d.ts.map +1 -0
  297. package/dist/truncate.js +154 -0
  298. package/dist/truncate.js.map +1 -0
  299. package/dist/types.d.ts +932 -0
  300. package/dist/types.d.ts.map +1 -0
  301. package/dist/types.js +3 -0
  302. package/dist/types.js.map +1 -0
  303. package/package.json +140 -5
@@ -0,0 +1,733 @@
1
+ import { applyImmutableBatches } from "@ny4n/chord/delta";
2
+ import { StorageRequestError } from "../errors.js";
3
+ import { idFromNumber, seqFromNumber } from "../ids.js";
4
+ import { nextCursor, scanStart } from "./scan.js";
5
+ function* documentDeltaBatches(id, version, revisions, start) {
6
+ for (let index = start; index < revisions.length; index++) {
7
+ const revision = revisions[index];
8
+ if (revision.kind !== "delta" || revision.version !== version) {
9
+ throw new Error(`Document ${id} crosses a stored version boundary without a base`);
10
+ }
11
+ yield revision.ops;
12
+ }
13
+ }
14
+ const clone = (value) => {
15
+ if (value === null || typeof value !== "object")
16
+ return value;
17
+ if (Array.isArray(value))
18
+ return value.map((item) => clone(item));
19
+ const source = value;
20
+ const nullPrototype = Object.getPrototypeOf(value) === null;
21
+ const result = (nullPrototype ? Object.create(null) : {});
22
+ for (const key of Object.keys(source)) {
23
+ const copied = clone(source[key]);
24
+ if (!nullPrototype && key in result) {
25
+ Object.defineProperty(result, key, {
26
+ value: copied,
27
+ writable: true,
28
+ enumerable: true,
29
+ configurable: true,
30
+ });
31
+ }
32
+ else {
33
+ result[key] = copied;
34
+ }
35
+ }
36
+ return result;
37
+ };
38
+ const freeze = (value) => {
39
+ if (value === null || typeof value !== "object" || Object.isFrozen(value))
40
+ return value;
41
+ for (const child of Object.values(value))
42
+ freeze(child);
43
+ return Object.freeze(value);
44
+ };
45
+ const cursorId = (cursor) => {
46
+ const after = cursor?.after;
47
+ if (after === undefined)
48
+ return undefined;
49
+ if (typeof after !== "number" || !Number.isSafeInteger(after))
50
+ throw new StorageRequestError("Invalid storage cursor");
51
+ return idFromNumber(after);
52
+ };
53
+ const lowerBound = (ids, target) => {
54
+ let low = 0;
55
+ let high = ids.length;
56
+ while (low < high) {
57
+ const middle = (low + high) >>> 1;
58
+ if (ids[middle] < target)
59
+ low = middle + 1;
60
+ else
61
+ high = middle;
62
+ }
63
+ return low;
64
+ };
65
+ const upperBound = (ids, target) => {
66
+ let low = 0;
67
+ let high = ids.length;
68
+ while (low < high) {
69
+ const middle = (low + high) >>> 1;
70
+ if (ids[middle] <= target)
71
+ low = middle + 1;
72
+ else
73
+ high = middle;
74
+ }
75
+ return low;
76
+ };
77
+ const insertSorted = (ids, id) => {
78
+ if (ids.length === 0 || ids[ids.length - 1] < id)
79
+ ids.push(id);
80
+ else
81
+ ids.splice(lowerBound(ids, id), 0, id);
82
+ };
83
+ const removeSorted = (ids, id) => {
84
+ const index = lowerBound(ids, id);
85
+ if (ids[index] === id)
86
+ ids.splice(index, 1);
87
+ };
88
+ const insertMapId = (index, key, id) => {
89
+ let ids = index.get(key);
90
+ if (ids === undefined) {
91
+ ids = [];
92
+ index.set(key, ids);
93
+ }
94
+ insertSorted(ids, id);
95
+ };
96
+ const scopeKey = (scope) => {
97
+ switch (scope.kind) {
98
+ case "session":
99
+ return JSON.stringify(["session"]);
100
+ case "conversation":
101
+ return JSON.stringify(["conversation", scope.conversationId]);
102
+ case "task":
103
+ return JSON.stringify(["task", scope.taskId]);
104
+ }
105
+ };
106
+ const addressKey = (address) => JSON.stringify([
107
+ address.kind,
108
+ scopeKey(address.scope),
109
+ address.key === undefined ? ["singleton"] : ["family", address.key],
110
+ ]);
111
+ const recordAddressKey = (record) => addressKey({ kind: record.kind, scope: record.scope, key: record.key });
112
+ const isAliveAt = (record, at) => {
113
+ if (at === "current")
114
+ return record.retiredAt === undefined;
115
+ return record.createdAt <= at && (record.retiredAt === undefined || at < record.retiredAt);
116
+ };
117
+ const isCurrentOnly = (record) => record.scope.kind !== "conversation" || record.history === "latest";
118
+ const tableContaining = (state, id) => state.recordTypes.get(id);
119
+ const page = (values, limit, order = "ascending") => {
120
+ const items = values.slice(0, limit);
121
+ if (values.length <= limit)
122
+ return { items: clone(items) };
123
+ return { items: clone(items), next: nextCursor(items.at(-1).id, order) };
124
+ };
125
+ /** Indexes of sorted `ids` in scan order, after the cursor's ID when there is one. */
126
+ function* scanIndexes(ids, start) {
127
+ if (start.order === "ascending") {
128
+ const first = start.after === undefined ? 0 : upperBound(ids, start.after);
129
+ for (let index = first; index < ids.length; index++)
130
+ yield index;
131
+ }
132
+ else {
133
+ const end = start.after === undefined ? ids.length : lowerBound(ids, start.after);
134
+ for (let index = end - 1; index >= 0; index--)
135
+ yield index;
136
+ }
137
+ }
138
+ /**
139
+ * Detached in-memory reference implementation of `Storage`.
140
+ *
141
+ * Reads and retained writes are cloned intentionally to match the ownership boundary
142
+ * of serialization-backed stores. This is backend conformance, not validation.
143
+ */
144
+ export class MemoryStorage {
145
+ state = {
146
+ recordTypes: new Map(),
147
+ conversations: new Map(),
148
+ conversationIds: [],
149
+ conversationIdsByOwnerConversation: new Map(),
150
+ conversationIdsByOwnerTask: new Map(),
151
+ entries: new Map(),
152
+ entryIds: new Map(),
153
+ headEntryIds: new Map(),
154
+ entryCommitSeqs: new Map(),
155
+ tasks: new Map(),
156
+ taskIds: [],
157
+ taskIdsByStatus: { pending: [], running: [], waiting: [], completing: [], terminal: [] },
158
+ submissions: new Map(),
159
+ submissionIds: [],
160
+ submissionIdsByStatus: { queued: [], placed: [], done: [], unanswered: [] },
161
+ submissionIdsByRequest: new Map(),
162
+ documents: new Map(),
163
+ documentAddresses: new Map(),
164
+ documentIdsByScope: new Map(),
165
+ };
166
+ nextId = 2;
167
+ nextSeq = 1;
168
+ closed = false;
169
+ async commit(writes, _context) {
170
+ return this.prepareCommit(writes).apply();
171
+ }
172
+ /** Validate and detach one commit without changing observable state. */
173
+ prepareCommit(writes, seq = seqFromNumber(this.nextSeq)) {
174
+ this.assertOpen();
175
+ if (!Number.isSafeInteger(seq) || seq < this.nextSeq) {
176
+ throw new Error(`Commit sequence ${seq} does not strictly increase`);
177
+ }
178
+ const detachedWrites = freeze(this.resolveDocumentCopies(writes.map((write) => clone(write))));
179
+ this.checkGlobalIds(detachedWrites);
180
+ const documentActions = this.prepareDocumentActions(detachedWrites);
181
+ this.checkDocumentActions(documentActions);
182
+ let applied = false;
183
+ return {
184
+ seq,
185
+ writes: detachedWrites,
186
+ apply: () => {
187
+ if (!applied) {
188
+ applied = true;
189
+ this.applyPreparedCommit(detachedWrites, documentActions, seq);
190
+ }
191
+ return seq;
192
+ },
193
+ };
194
+ }
195
+ resolveDocumentCopies(writes) {
196
+ if (!writes.some((write) => write.type === "document.copy"))
197
+ return writes;
198
+ const changedDocumentIds = new Set();
199
+ for (const write of writes) {
200
+ if (write.type === "document.create" || write.type === "document.copy") {
201
+ changedDocumentIds.add(write.record.id);
202
+ }
203
+ else if (write.type === "document.change" || write.type === "document.retire") {
204
+ changedDocumentIds.add(write.id);
205
+ }
206
+ }
207
+ return writes.map((write) => {
208
+ if (write.type !== "document.copy")
209
+ return write;
210
+ try {
211
+ if (changedDocumentIds.has(write.source.id)) {
212
+ throw new Error(`Fork source document ${write.source.id} is changed in the copy batch`);
213
+ }
214
+ const stored = this.materializeDocument(write.source.id, write.source.at);
215
+ if (stored === undefined)
216
+ throw new Error(`Fork source document ${write.source.id} cannot be read`);
217
+ if (stored.record.scope.kind !== "conversation" ||
218
+ write.record.scope.kind !== "conversation" ||
219
+ stored.record.kind !== write.record.kind ||
220
+ stored.record.key !== write.record.key ||
221
+ stored.record.history !== write.record.history ||
222
+ stored.record.fork !== write.record.fork) {
223
+ throw new Error(`Fork source document ${write.source.id} does not match the copied record`);
224
+ }
225
+ return {
226
+ type: "document.create",
227
+ record: write.record,
228
+ content: { kind: "base", version: stored.version, value: stored.value },
229
+ };
230
+ }
231
+ catch (error) {
232
+ throw new Error(`Document copy ${write.record.id} was rejected`, { cause: error });
233
+ }
234
+ });
235
+ }
236
+ applyPreparedCommit(prepared, documentActions, seq) {
237
+ for (const write of prepared) {
238
+ switch (write.type) {
239
+ case "conversation": {
240
+ this.state.recordTypes.set(write.value.id, "conversation");
241
+ this.state.conversations.set(write.value.id, write.value);
242
+ insertSorted(this.state.conversationIds, write.value.id);
243
+ const owner = write.value.owner;
244
+ if (owner !== undefined) {
245
+ insertMapId(this.state.conversationIdsByOwnerConversation, owner.conversationId, write.value.id);
246
+ insertMapId(this.state.conversationIdsByOwnerTask, owner.taskId, write.value.id);
247
+ }
248
+ this.nextId = Math.max(this.nextId, write.value.id + 1);
249
+ break;
250
+ }
251
+ case "entry": {
252
+ this.state.recordTypes.set(write.value.id, "entry");
253
+ this.state.entries.set(write.value.id, write.value);
254
+ this.state.entryCommitSeqs.set(write.value.id, seq);
255
+ let ids = this.state.entryIds.get(write.value.conversationId);
256
+ if (ids === undefined) {
257
+ ids = [];
258
+ this.state.entryIds.set(write.value.conversationId, ids);
259
+ }
260
+ insertSorted(ids, write.value.id);
261
+ if (write.value.head !== undefined) {
262
+ let headIds = this.state.headEntryIds.get(write.value.conversationId);
263
+ if (headIds === undefined) {
264
+ headIds = [];
265
+ this.state.headEntryIds.set(write.value.conversationId, headIds);
266
+ }
267
+ insertSorted(headIds, write.value.id);
268
+ }
269
+ this.nextId = Math.max(this.nextId, write.value.id + 1);
270
+ break;
271
+ }
272
+ case "task": {
273
+ this.state.recordTypes.set(write.value.id, "task");
274
+ const previous = this.state.tasks.get(write.value.id);
275
+ if (previous === undefined) {
276
+ insertSorted(this.state.taskIds, write.value.id);
277
+ insertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);
278
+ }
279
+ else if (previous.state.status !== write.value.state.status) {
280
+ removeSorted(this.state.taskIdsByStatus[previous.state.status], write.value.id);
281
+ insertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);
282
+ }
283
+ this.state.tasks.set(write.value.id, write.value);
284
+ this.nextId = Math.max(this.nextId, write.value.id + 1);
285
+ break;
286
+ }
287
+ case "submission": {
288
+ this.state.recordTypes.set(write.value.id, "submission");
289
+ const previous = this.state.submissions.get(write.value.id);
290
+ if (previous === undefined) {
291
+ insertSorted(this.state.submissionIds, write.value.id);
292
+ insertSorted(this.state.submissionIdsByStatus[write.value.status], write.value.id);
293
+ }
294
+ else if (previous.status !== write.value.status) {
295
+ removeSorted(this.state.submissionIdsByStatus[previous.status], write.value.id);
296
+ insertSorted(this.state.submissionIdsByStatus[write.value.status], write.value.id);
297
+ }
298
+ if (previous?.requestId !== undefined) {
299
+ const previousRequests = this.state.submissionIdsByRequest.get(previous.conversationId);
300
+ if (previousRequests?.get(previous.requestId) === write.value.id) {
301
+ previousRequests.delete(previous.requestId);
302
+ if (previousRequests.size === 0)
303
+ this.state.submissionIdsByRequest.delete(previous.conversationId);
304
+ }
305
+ }
306
+ this.state.submissions.set(write.value.id, write.value);
307
+ if (write.value.requestId !== undefined) {
308
+ let requests = this.state.submissionIdsByRequest.get(write.value.conversationId);
309
+ if (requests === undefined) {
310
+ requests = new Map();
311
+ this.state.submissionIdsByRequest.set(write.value.conversationId, requests);
312
+ }
313
+ requests.set(write.value.requestId, write.value.id);
314
+ }
315
+ this.nextId = Math.max(this.nextId, write.value.id + 1);
316
+ break;
317
+ }
318
+ case "document.copy":
319
+ throw new Error("Prepared document copy was not resolved");
320
+ case "document.create":
321
+ case "document.change":
322
+ case "document.retire":
323
+ break;
324
+ }
325
+ }
326
+ this.applyDocumentActions(documentActions, seq);
327
+ this.nextSeq = seq + 1;
328
+ return seq;
329
+ }
330
+ async mintId() {
331
+ this.assertOpen();
332
+ if (!Number.isSafeInteger(this.nextId))
333
+ throw new Error("ID space is exhausted");
334
+ return idFromNumber(this.nextId++);
335
+ }
336
+ async conversation(id, _context) {
337
+ this.assertOpen();
338
+ const value = this.state.conversations.get(id);
339
+ return value === undefined ? undefined : clone(value);
340
+ }
341
+ async scanConversations(query, limit, cursor, _context) {
342
+ this.assertOpen();
343
+ const ids = query.ownerTaskId !== undefined
344
+ ? (this.state.conversationIdsByOwnerTask.get(query.ownerTaskId) ?? [])
345
+ : query.ownerConversationId !== undefined
346
+ ? (this.state.conversationIdsByOwnerConversation.get(query.ownerConversationId) ?? [])
347
+ : this.state.conversationIds;
348
+ const start = scanStart(query.order, cursor, "ascending");
349
+ const values = [];
350
+ for (const index of scanIndexes(ids, start)) {
351
+ if (values.length > limit)
352
+ break;
353
+ const value = this.state.conversations.get(ids[index]);
354
+ if (query.ownerConversationId !== undefined && value.owner?.conversationId !== query.ownerConversationId) {
355
+ continue;
356
+ }
357
+ values.push(value);
358
+ }
359
+ return page(values, limit, start.order);
360
+ }
361
+ async entry(idOrConversationId, idOrContext, context) {
362
+ this.assertOpen();
363
+ if (context === undefined) {
364
+ const id = idFromNumber(idOrConversationId);
365
+ const entry = this.state.entries.get(id);
366
+ if (entry === undefined)
367
+ return undefined;
368
+ return { entry: clone(entry), commitSeq: this.state.entryCommitSeqs.get(id) };
369
+ }
370
+ if (typeof idOrContext !== "number")
371
+ throw new StorageRequestError("Storage.entry() requires an entry ID");
372
+ const conversationId = idFromNumber(idOrConversationId);
373
+ const id = idFromNumber(idOrContext);
374
+ const entry = this.visibleEntries(conversationId, id, id).next().value;
375
+ if (entry === undefined)
376
+ return undefined;
377
+ return { entry: clone(entry), commitSeq: this.state.entryCommitSeqs.get(id) };
378
+ }
379
+ async findLatestHeadMarker(conversationId, atOrBeforeEntryId, _context) {
380
+ this.assertOpen();
381
+ if (!this.state.conversations.has(conversationId)) {
382
+ throw new StorageRequestError(`Unknown conversation: ${conversationId}`);
383
+ }
384
+ let currentId = conversationId;
385
+ let upperEntryId = atOrBeforeEntryId ?? Number.POSITIVE_INFINITY;
386
+ while (true) {
387
+ const ids = this.state.headEntryIds.get(currentId) ?? [];
388
+ const index = upperBound(ids, upperEntryId) - 1;
389
+ if (index >= 0) {
390
+ const entry = this.state.entries.get(ids[index]);
391
+ return clone({ ...entry, head: entry.head });
392
+ }
393
+ const conversation = this.state.conversations.get(currentId);
394
+ if (conversation.parent === undefined)
395
+ return undefined;
396
+ upperEntryId = Math.min(upperEntryId, conversation.parent.at);
397
+ currentId = conversation.parent.conversationId;
398
+ }
399
+ }
400
+ async scanEntries(query, limit, cursor, _context) {
401
+ this.assertOpen();
402
+ const { order, after } = scanStart(query.order, cursor, "descending");
403
+ // The cursor narrows the bound on the side the scan moves away from.
404
+ let minEntryId = query.minEntryId;
405
+ let maxEntryId = query.maxEntryId;
406
+ if (after !== undefined && order === "descending")
407
+ maxEntryId = Math.min(maxEntryId ?? after, after - 1);
408
+ if (after !== undefined && order === "ascending")
409
+ minEntryId = Math.max(minEntryId ?? after, after + 1);
410
+ const visible = [];
411
+ const entries = order === "descending"
412
+ ? this.visibleEntries(query.conversationId, minEntryId, maxEntryId)
413
+ : this.visibleEntriesAscending(query.conversationId, minEntryId, maxEntryId);
414
+ for (const entry of entries) {
415
+ visible.push(entry);
416
+ if (visible.length > limit)
417
+ break;
418
+ }
419
+ return page(visible, limit, order);
420
+ }
421
+ async task(id, _context) {
422
+ this.assertOpen();
423
+ const value = this.state.tasks.get(id);
424
+ return value === undefined ? undefined : clone(value);
425
+ }
426
+ async scanTasks(query, limit, cursor, _context) {
427
+ this.assertOpen();
428
+ const start = scanStart(query.order, cursor, "ascending");
429
+ const ids = query.status === undefined ? this.state.taskIds : this.state.taskIdsByStatus[query.status];
430
+ const values = [];
431
+ for (const index of scanIndexes(ids, start)) {
432
+ if (values.length > limit)
433
+ break;
434
+ const value = this.state.tasks.get(ids[index]);
435
+ if (query.conversationId !== undefined && value.conversationId !== query.conversationId)
436
+ continue;
437
+ if (query.kind !== undefined && value.kind !== query.kind)
438
+ continue;
439
+ if (query.abortRequested !== undefined && value.abortRequested !== query.abortRequested)
440
+ continue;
441
+ if (query.background !== undefined && value.background !== query.background)
442
+ continue;
443
+ values.push(value);
444
+ }
445
+ return page(values, limit, start.order);
446
+ }
447
+ async submission(id, _context) {
448
+ this.assertOpen();
449
+ const value = this.state.submissions.get(id);
450
+ return value === undefined ? undefined : clone(value);
451
+ }
452
+ async scanSubmissions(query, limit, cursor, _context) {
453
+ this.assertOpen();
454
+ const start = scanStart(query.order, cursor, "ascending");
455
+ const ids = query.status === undefined ? this.state.submissionIds : this.state.submissionIdsByStatus[query.status];
456
+ const values = [];
457
+ for (const index of scanIndexes(ids, start)) {
458
+ if (values.length > limit)
459
+ break;
460
+ const value = this.state.submissions.get(ids[index]);
461
+ if (query.conversationId !== undefined && value.conversationId !== query.conversationId)
462
+ continue;
463
+ values.push(value);
464
+ }
465
+ return page(values, limit, start.order);
466
+ }
467
+ async submissionByRequest(conversationId, requestId, _context) {
468
+ this.assertOpen();
469
+ const id = this.state.submissionIdsByRequest.get(conversationId)?.get(requestId);
470
+ if (id === undefined)
471
+ return undefined;
472
+ return clone(this.state.submissions.get(id));
473
+ }
474
+ async findDocument(address, at, _context) {
475
+ this.assertOpen();
476
+ const index = this.state.documentAddresses.get(addressKey(address));
477
+ if (at === "current") {
478
+ return index?.currentId === undefined ? undefined : clone(this.state.documents.get(index.currentId).record);
479
+ }
480
+ for (const id of index?.ids ?? []) {
481
+ const record = this.state.documents.get(id).record;
482
+ if (isAliveAt(record, at))
483
+ return clone(record);
484
+ }
485
+ return undefined;
486
+ }
487
+ async document(id, at, _context) {
488
+ this.assertOpen();
489
+ const stored = this.materializeDocument(id, at);
490
+ return stored === undefined
491
+ ? undefined
492
+ : {
493
+ record: clone(stored.record),
494
+ version: stored.version,
495
+ value: clone(stored.value),
496
+ deltasSinceBase: stored.deltasSinceBase,
497
+ };
498
+ }
499
+ async scanDocuments(query, limit, cursor, _context) {
500
+ this.assertOpen();
501
+ const ids = this.state.documentIdsByScope.get(scopeKey(query.scope)) ?? [];
502
+ const after = cursorId(cursor);
503
+ const start = after === undefined ? 0 : upperBound(ids, after);
504
+ const values = [];
505
+ for (let index = start; index < ids.length && values.length <= limit; index++) {
506
+ const record = this.state.documents.get(ids[index]).record;
507
+ if (query.kind !== undefined && record.kind !== query.kind)
508
+ continue;
509
+ if (isAliveAt(record, query.at))
510
+ values.push(record);
511
+ }
512
+ return page(values, limit);
513
+ }
514
+ async close(_context) {
515
+ this.closed = true;
516
+ }
517
+ materializeDocument(id, at) {
518
+ const stored = this.state.documents.get(id);
519
+ if (stored === undefined)
520
+ return undefined;
521
+ if (at !== "current" && isCurrentOnly(stored.record)) {
522
+ throw new StorageRequestError(`Document ${id} does not retain historical content`);
523
+ }
524
+ if (!isAliveAt(stored.record, at))
525
+ return undefined;
526
+ const revisions = at === "current" ? stored.revisions : stored.revisions.filter((revision) => revision.seq <= at);
527
+ let baseIndex = revisions.length - 1;
528
+ while (baseIndex >= 0 && revisions[baseIndex].kind !== "base")
529
+ baseIndex--;
530
+ const base = revisions[baseIndex];
531
+ if (base?.kind !== "base")
532
+ throw new Error(`Document ${id} is missing a required base`);
533
+ const value = applyImmutableBatches(base.value, documentDeltaBatches(id, base.version, revisions, baseIndex + 1));
534
+ return { record: stored.record, version: base.version, value, deltasSinceBase: revisions.length - baseIndex - 1 };
535
+ }
536
+ *visibleEntries(conversationId, minEntryId = Number.NEGATIVE_INFINITY, maxEntryId = Number.POSITIVE_INFINITY) {
537
+ if (!this.state.conversations.has(conversationId)) {
538
+ throw new StorageRequestError(`Unknown conversation: ${conversationId}`);
539
+ }
540
+ let currentId = conversationId;
541
+ let upperEntryId = maxEntryId;
542
+ while (true) {
543
+ const ids = this.state.entryIds.get(currentId) ?? [];
544
+ for (let index = upperBound(ids, upperEntryId) - 1; index >= 0; index--) {
545
+ const id = ids[index];
546
+ if (id < minEntryId)
547
+ break;
548
+ yield this.state.entries.get(id);
549
+ }
550
+ const conversation = this.state.conversations.get(currentId);
551
+ if (conversation.parent === undefined)
552
+ break;
553
+ upperEntryId = Math.min(upperEntryId, conversation.parent.at);
554
+ if (upperEntryId < minEntryId)
555
+ break;
556
+ currentId = conversation.parent.conversationId;
557
+ }
558
+ }
559
+ /** Visible entries oldest first: the fork chain's segments from the root conversation forward. */
560
+ *visibleEntriesAscending(conversationId, minEntryId = Number.NEGATIVE_INFINITY, maxEntryId = Number.POSITIVE_INFINITY) {
561
+ if (!this.state.conversations.has(conversationId)) {
562
+ throw new StorageRequestError(`Unknown conversation: ${conversationId}`);
563
+ }
564
+ const segments = [];
565
+ let currentId = conversationId;
566
+ let upperEntryId = maxEntryId;
567
+ while (true) {
568
+ segments.push({ conversationId: currentId, upper: upperEntryId });
569
+ const conversation = this.state.conversations.get(currentId);
570
+ if (conversation.parent === undefined)
571
+ break;
572
+ upperEntryId = Math.min(upperEntryId, conversation.parent.at);
573
+ if (upperEntryId < minEntryId)
574
+ break;
575
+ currentId = conversation.parent.conversationId;
576
+ }
577
+ for (const segment of segments.reverse()) {
578
+ const ids = this.state.entryIds.get(segment.conversationId) ?? [];
579
+ for (let index = lowerBound(ids, minEntryId); index < ids.length; index++) {
580
+ const id = ids[index];
581
+ if (id > segment.upper)
582
+ break;
583
+ yield this.state.entries.get(id);
584
+ }
585
+ }
586
+ }
587
+ checkGlobalIds(writes) {
588
+ const claimed = new Map();
589
+ for (const write of writes) {
590
+ if (write.type === "document.change" || write.type === "document.retire")
591
+ continue;
592
+ const document = write.type === "document.create" || write.type === "document.copy";
593
+ const table = document ? "document" : write.type;
594
+ const id = document ? write.record.id : write.value.id;
595
+ const existing = tableContaining(this.state, id);
596
+ const earlier = claimed.get(id);
597
+ if (table === "conversation" || table === "entry" || table === "document") {
598
+ if (existing !== undefined)
599
+ throw new Error(`ID ${id} already belongs to ${existing}`);
600
+ if (earlier !== undefined)
601
+ throw new Error(`ID ${id} is written more than once`);
602
+ }
603
+ else {
604
+ if (existing !== undefined && existing !== table) {
605
+ throw new Error(`ID ${id} already belongs to ${existing}`);
606
+ }
607
+ if (earlier !== undefined && earlier !== table)
608
+ throw new Error(`ID ${id} is written as two record types`);
609
+ }
610
+ claimed.set(id, table);
611
+ }
612
+ }
613
+ prepareDocumentActions(writes) {
614
+ const actions = new Map();
615
+ for (const write of writes) {
616
+ if (write.type !== "document.create" && write.type !== "document.change" && write.type !== "document.retire") {
617
+ continue;
618
+ }
619
+ const id = write.type === "document.create" ? write.record.id : write.id;
620
+ let action = actions.get(id);
621
+ if (action === undefined) {
622
+ action = { retire: false };
623
+ actions.set(id, action);
624
+ }
625
+ switch (write.type) {
626
+ case "document.create":
627
+ if (action.create !== undefined || action.content !== undefined) {
628
+ throw new Error(`Document ${id} has more than one content command`);
629
+ }
630
+ action.create = write.record;
631
+ action.content = write.content;
632
+ break;
633
+ case "document.change":
634
+ if (action.content !== undefined)
635
+ throw new Error(`Document ${id} has more than one content command`);
636
+ action.content = write.content;
637
+ break;
638
+ case "document.retire":
639
+ if (action.retire)
640
+ throw new Error(`Document ${id} is retired more than once`);
641
+ action.retire = true;
642
+ break;
643
+ }
644
+ }
645
+ return actions;
646
+ }
647
+ checkDocumentActions(actions) {
648
+ const liveCounts = new Map();
649
+ for (const [id, action] of actions) {
650
+ const existing = this.state.documents.get(id);
651
+ if (action.create === undefined && existing === undefined)
652
+ throw new Error(`Unknown document: ${id}`);
653
+ if (action.create !== undefined && existing !== undefined)
654
+ throw new Error(`Document ${id} already exists`);
655
+ if (existing?.record.retiredAt !== undefined)
656
+ throw new Error(`Document ${id} is retired`);
657
+ const previous = existing?.revisions.at(-1);
658
+ if (action.content?.kind === "delta") {
659
+ if (previous === undefined)
660
+ throw new Error(`Document ${id} delta has no base`);
661
+ if (previous.version !== action.content.version) {
662
+ throw new Error(`Document ${id} version transition requires a base`);
663
+ }
664
+ }
665
+ const key = action.create === undefined ? recordAddressKey(existing.record) : recordAddressKey(action.create);
666
+ const currentId = this.state.documentAddresses.get(key)?.currentId;
667
+ let live = liveCounts.get(key);
668
+ if (live === undefined)
669
+ live = currentId === undefined ? 0 : 1;
670
+ if (action.retire && currentId === id)
671
+ live--;
672
+ if (action.create !== undefined && !action.retire)
673
+ live++;
674
+ liveCounts.set(key, live);
675
+ }
676
+ for (const live of liveCounts.values()) {
677
+ if (live > 1)
678
+ throw new Error(`Document address already has a current incarnation`);
679
+ }
680
+ }
681
+ applyDocumentActions(actions, seq) {
682
+ for (const [id, action] of actions) {
683
+ let stored = this.state.documents.get(id);
684
+ if (action.create !== undefined) {
685
+ const record = {
686
+ ...action.create,
687
+ createdAt: seq,
688
+ ...(action.retire ? { retiredAt: seq } : {}),
689
+ };
690
+ stored = { record, revisions: [{ ...action.content, seq }] };
691
+ this.state.recordTypes.set(id, "document");
692
+ this.state.documents.set(id, stored);
693
+ const key = recordAddressKey(record);
694
+ let address = this.state.documentAddresses.get(key);
695
+ if (address === undefined) {
696
+ address = { ids: [] };
697
+ this.state.documentAddresses.set(key, address);
698
+ }
699
+ insertSorted(address.ids, id);
700
+ let scopeIds = this.state.documentIdsByScope.get(scopeKey(record.scope));
701
+ if (scopeIds === undefined) {
702
+ scopeIds = [];
703
+ this.state.documentIdsByScope.set(scopeKey(record.scope), scopeIds);
704
+ }
705
+ insertSorted(scopeIds, id);
706
+ this.nextId = Math.max(this.nextId, id + 1);
707
+ }
708
+ else if (action.content !== undefined) {
709
+ const revision = { ...action.content, seq };
710
+ if (revision.kind === "base" && isCurrentOnly(stored.record))
711
+ stored.revisions = [revision];
712
+ else
713
+ stored.revisions.push(revision);
714
+ }
715
+ if (action.retire && action.create === undefined)
716
+ stored.record = { ...stored.record, retiredAt: seq };
717
+ if (action.retire && isCurrentOnly(stored.record))
718
+ stored.revisions = [];
719
+ if (action.create !== undefined || action.retire) {
720
+ const address = this.state.documentAddresses.get(recordAddressKey(stored.record));
721
+ if (action.retire && address.currentId === id)
722
+ delete address.currentId;
723
+ if (action.create !== undefined && !action.retire)
724
+ address.currentId = id;
725
+ }
726
+ }
727
+ }
728
+ assertOpen() {
729
+ if (this.closed)
730
+ throw new Error("MemoryStorage is closed");
731
+ }
732
+ }
733
+ //# sourceMappingURL=memory.js.map