@powerhousedao/reactor 4.1.0-dev.10 → 4.1.0-dev.101

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 (234) hide show
  1. package/dist/src/cache/buffer/ring-buffer.d.ts +37 -0
  2. package/dist/src/cache/buffer/ring-buffer.d.ts.map +1 -0
  3. package/dist/src/cache/buffer/ring-buffer.js +69 -0
  4. package/dist/src/cache/buffer/ring-buffer.js.map +1 -0
  5. package/dist/src/cache/kysely-write-cache.d.ts +133 -0
  6. package/dist/src/cache/kysely-write-cache.d.ts.map +1 -0
  7. package/dist/src/cache/kysely-write-cache.js +375 -0
  8. package/dist/src/cache/kysely-write-cache.js.map +1 -0
  9. package/dist/src/cache/lru/lru-tracker.d.ts +15 -0
  10. package/dist/src/cache/lru/lru-tracker.d.ts.map +1 -0
  11. package/dist/src/cache/lru/lru-tracker.js +96 -0
  12. package/dist/src/cache/lru/lru-tracker.js.map +1 -0
  13. package/dist/src/cache/types.d.ts +42 -0
  14. package/dist/src/cache/types.d.ts.map +1 -0
  15. package/dist/src/cache/types.js +2 -0
  16. package/dist/src/cache/types.js.map +1 -0
  17. package/dist/src/cache/write/interfaces.d.ts +83 -0
  18. package/dist/src/cache/write/interfaces.d.ts.map +1 -0
  19. package/dist/src/cache/write/interfaces.js +2 -0
  20. package/dist/src/cache/write/interfaces.js.map +1 -0
  21. package/dist/src/client/reactor-client.d.ts +103 -0
  22. package/dist/src/client/reactor-client.d.ts.map +1 -0
  23. package/dist/src/client/reactor-client.js +184 -0
  24. package/dist/src/client/reactor-client.js.map +1 -0
  25. package/dist/src/client/types.d.ts +213 -0
  26. package/dist/src/client/types.d.ts.map +1 -0
  27. package/dist/src/client/types.js +14 -0
  28. package/dist/src/client/types.js.map +1 -0
  29. package/dist/src/core/builder.d.ts +20 -0
  30. package/dist/src/core/builder.d.ts.map +1 -0
  31. package/dist/src/core/builder.js +47 -0
  32. package/dist/src/core/builder.js.map +1 -0
  33. package/dist/src/core/reactor-builder.d.ts +24 -0
  34. package/dist/src/core/reactor-builder.d.ts.map +1 -0
  35. package/dist/src/core/reactor-builder.js +152 -0
  36. package/dist/src/core/reactor-builder.js.map +1 -0
  37. package/dist/src/core/reactor.d.ts +96 -0
  38. package/dist/src/core/reactor.d.ts.map +1 -0
  39. package/dist/src/core/reactor.js +666 -0
  40. package/dist/src/core/reactor.js.map +1 -0
  41. package/dist/src/core/types.d.ts +168 -0
  42. package/dist/src/core/types.d.ts.map +1 -0
  43. package/dist/src/core/types.js +2 -0
  44. package/dist/src/core/types.js.map +1 -0
  45. package/dist/src/core/utils.d.ts +50 -0
  46. package/dist/src/core/utils.d.ts.map +1 -0
  47. package/dist/src/core/utils.js +133 -0
  48. package/dist/src/core/utils.js.map +1 -0
  49. package/dist/src/events/event-bus.d.ts +3 -3
  50. package/dist/src/events/event-bus.d.ts.map +1 -1
  51. package/dist/src/events/event-bus.js.map +1 -1
  52. package/dist/src/events/interfaces.d.ts +1 -1
  53. package/dist/src/events/interfaces.d.ts.map +1 -1
  54. package/dist/src/events/types.d.ts +15 -1
  55. package/dist/src/events/types.d.ts.map +1 -1
  56. package/dist/src/events/types.js +6 -0
  57. package/dist/src/events/types.js.map +1 -1
  58. package/dist/src/executor/interfaces.d.ts +31 -54
  59. package/dist/src/executor/interfaces.d.ts.map +1 -1
  60. package/dist/src/executor/simple-job-executor-manager.d.ts +32 -0
  61. package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -0
  62. package/dist/src/executor/simple-job-executor-manager.js +212 -0
  63. package/dist/src/executor/simple-job-executor-manager.js.map +1 -0
  64. package/dist/src/executor/simple-job-executor.d.ts +58 -0
  65. package/dist/src/executor/simple-job-executor.d.ts.map +1 -0
  66. package/dist/src/executor/simple-job-executor.js +516 -0
  67. package/dist/src/executor/simple-job-executor.js.map +1 -0
  68. package/dist/src/executor/types.d.ts +32 -8
  69. package/dist/src/executor/types.d.ts.map +1 -1
  70. package/dist/src/executor/types.js.map +1 -1
  71. package/dist/src/executor/util.d.ts +47 -0
  72. package/dist/src/executor/util.d.ts.map +1 -0
  73. package/dist/src/executor/util.js +113 -0
  74. package/dist/src/executor/util.js.map +1 -0
  75. package/dist/src/index.d.ts +32 -3
  76. package/dist/src/index.d.ts.map +1 -1
  77. package/dist/src/index.js +38 -2
  78. package/dist/src/index.js.map +1 -1
  79. package/dist/src/job-tracker/in-memory-job-tracker.d.ts +16 -0
  80. package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -0
  81. package/dist/src/job-tracker/in-memory-job-tracker.js +78 -0
  82. package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -0
  83. package/dist/src/job-tracker/index.d.ts +3 -0
  84. package/dist/src/job-tracker/index.d.ts.map +1 -0
  85. package/dist/src/job-tracker/index.js +2 -0
  86. package/dist/src/job-tracker/index.js.map +1 -0
  87. package/dist/src/job-tracker/interfaces.d.ts +41 -0
  88. package/dist/src/job-tracker/interfaces.d.ts.map +1 -0
  89. package/dist/src/job-tracker/interfaces.js +2 -0
  90. package/dist/src/job-tracker/interfaces.js.map +1 -0
  91. package/dist/src/queue/interfaces.d.ts +46 -5
  92. package/dist/src/queue/interfaces.d.ts.map +1 -1
  93. package/dist/src/queue/job-execution-handle.d.ts +25 -0
  94. package/dist/src/queue/job-execution-handle.d.ts.map +1 -0
  95. package/dist/src/queue/job-execution-handle.js +62 -0
  96. package/dist/src/queue/job-execution-handle.js.map +1 -0
  97. package/dist/src/queue/queue.d.ts +56 -5
  98. package/dist/src/queue/queue.d.ts.map +1 -1
  99. package/dist/src/queue/queue.js +284 -36
  100. package/dist/src/queue/queue.js.map +1 -1
  101. package/dist/src/queue/types.d.ts +33 -5
  102. package/dist/src/queue/types.d.ts.map +1 -1
  103. package/dist/src/queue/types.js +12 -0
  104. package/dist/src/queue/types.js.map +1 -1
  105. package/dist/src/read-models/coordinator.d.ts +38 -0
  106. package/dist/src/read-models/coordinator.d.ts.map +1 -0
  107. package/dist/src/read-models/coordinator.js +62 -0
  108. package/dist/src/read-models/coordinator.js.map +1 -0
  109. package/dist/src/read-models/document-view.d.ts +20 -0
  110. package/dist/src/read-models/document-view.d.ts.map +1 -0
  111. package/dist/src/read-models/document-view.js +365 -0
  112. package/dist/src/read-models/document-view.js.map +1 -0
  113. package/dist/src/read-models/interfaces.d.ts +29 -0
  114. package/dist/src/read-models/interfaces.d.ts.map +1 -0
  115. package/dist/src/read-models/interfaces.js +2 -0
  116. package/dist/src/read-models/interfaces.js.map +1 -0
  117. package/dist/src/read-models/types.d.ts +46 -0
  118. package/dist/src/read-models/types.d.ts.map +1 -0
  119. package/dist/src/read-models/types.js +2 -0
  120. package/dist/src/read-models/types.js.map +1 -0
  121. package/dist/src/registry/implementation.d.ts +62 -0
  122. package/dist/src/registry/implementation.d.ts.map +1 -0
  123. package/dist/src/registry/implementation.js +96 -0
  124. package/dist/src/registry/implementation.js.map +1 -0
  125. package/dist/src/registry/index.d.ts +3 -0
  126. package/dist/src/registry/index.d.ts.map +1 -0
  127. package/dist/src/registry/index.js +2 -0
  128. package/dist/src/registry/index.js.map +1 -0
  129. package/dist/src/registry/interfaces.d.ts +39 -0
  130. package/dist/src/registry/interfaces.d.ts.map +1 -0
  131. package/dist/src/registry/interfaces.js +2 -0
  132. package/dist/src/registry/interfaces.js.map +1 -0
  133. package/dist/src/shared/awaiter.d.ts +32 -0
  134. package/dist/src/shared/awaiter.d.ts.map +1 -0
  135. package/dist/src/shared/awaiter.js +132 -0
  136. package/dist/src/shared/awaiter.js.map +1 -0
  137. package/dist/src/shared/errors.d.ts +17 -0
  138. package/dist/src/shared/errors.d.ts.map +1 -0
  139. package/dist/src/shared/errors.js +33 -0
  140. package/dist/src/shared/errors.js.map +1 -0
  141. package/dist/src/shared/factories.d.ts +16 -0
  142. package/dist/src/shared/factories.d.ts.map +1 -0
  143. package/dist/src/shared/factories.js +33 -0
  144. package/dist/src/shared/factories.js.map +1 -0
  145. package/dist/src/shared/types.d.ts +100 -20
  146. package/dist/src/shared/types.d.ts.map +1 -1
  147. package/dist/src/shared/types.js +35 -1
  148. package/dist/src/shared/types.js.map +1 -1
  149. package/dist/src/shared/utils.d.ts +3 -0
  150. package/dist/src/shared/utils.d.ts.map +1 -0
  151. package/dist/src/shared/utils.js +8 -0
  152. package/dist/src/shared/utils.js.map +1 -0
  153. package/dist/src/signer/passthrough-signer.d.ts +6 -0
  154. package/dist/src/signer/passthrough-signer.d.ts.map +1 -0
  155. package/dist/src/signer/passthrough-signer.js +6 -0
  156. package/dist/src/signer/passthrough-signer.js.map +1 -0
  157. package/dist/src/signer/types.d.ts +15 -0
  158. package/dist/src/signer/types.d.ts.map +1 -0
  159. package/dist/src/signer/types.js +2 -0
  160. package/dist/src/signer/types.js.map +1 -0
  161. package/dist/src/storage/interfaces.d.ts +209 -0
  162. package/dist/src/storage/interfaces.d.ts.map +1 -0
  163. package/dist/src/storage/interfaces.js +19 -0
  164. package/dist/src/storage/interfaces.js.map +1 -0
  165. package/dist/src/storage/kysely/document-indexer.d.ts +25 -0
  166. package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -0
  167. package/dist/src/storage/kysely/document-indexer.js +345 -0
  168. package/dist/src/storage/kysely/document-indexer.js.map +1 -0
  169. package/dist/src/storage/kysely/keyframe-store.d.ts +15 -0
  170. package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -0
  171. package/dist/src/storage/kysely/keyframe-store.js +71 -0
  172. package/dist/src/storage/kysely/keyframe-store.js.map +1 -0
  173. package/dist/src/storage/kysely/store.d.ts +15 -0
  174. package/dist/src/storage/kysely/store.d.ts.map +1 -0
  175. package/dist/src/storage/kysely/store.js +196 -0
  176. package/dist/src/storage/kysely/store.js.map +1 -0
  177. package/dist/src/storage/kysely/types.d.ts +72 -0
  178. package/dist/src/storage/kysely/types.d.ts.map +1 -0
  179. package/dist/src/storage/kysely/types.js +2 -0
  180. package/dist/src/storage/kysely/types.js.map +1 -0
  181. package/dist/src/storage/txn.d.ts +15 -0
  182. package/dist/src/storage/txn.d.ts.map +1 -0
  183. package/dist/src/storage/txn.js +43 -0
  184. package/dist/src/storage/txn.js.map +1 -0
  185. package/dist/src/subs/default-error-handler.d.ts +13 -0
  186. package/dist/src/subs/default-error-handler.d.ts.map +1 -0
  187. package/dist/src/subs/default-error-handler.js +27 -0
  188. package/dist/src/subs/default-error-handler.js.map +1 -0
  189. package/dist/src/subs/react-subscription-manager.d.ts +45 -0
  190. package/dist/src/subs/react-subscription-manager.d.ts.map +1 -0
  191. package/dist/src/subs/react-subscription-manager.js +185 -0
  192. package/dist/src/subs/react-subscription-manager.js.map +1 -0
  193. package/dist/src/subs/types.d.ts +64 -0
  194. package/dist/src/subs/types.d.ts.map +1 -0
  195. package/dist/src/subs/types.js +2 -0
  196. package/dist/src/subs/types.js.map +1 -0
  197. package/dist/src/utils/reshuffle.d.ts +30 -0
  198. package/dist/src/utils/reshuffle.d.ts.map +1 -0
  199. package/dist/src/utils/reshuffle.js +47 -0
  200. package/dist/src/utils/reshuffle.js.map +1 -0
  201. package/package.json +18 -7
  202. package/dist/bench/end-to-end-flow.bench.d.ts +0 -2
  203. package/dist/bench/end-to-end-flow.bench.d.ts.map +0 -1
  204. package/dist/bench/end-to-end-flow.bench.js +0 -256
  205. package/dist/bench/end-to-end-flow.bench.js.map +0 -1
  206. package/dist/bench/event-bus.bench.d.ts +0 -2
  207. package/dist/bench/event-bus.bench.d.ts.map +0 -1
  208. package/dist/bench/event-bus.bench.js +0 -238
  209. package/dist/bench/event-bus.bench.js.map +0 -1
  210. package/dist/bench/queue-only.bench.d.ts +0 -2
  211. package/dist/bench/queue-only.bench.d.ts.map +0 -1
  212. package/dist/bench/queue-only.bench.js +0 -40
  213. package/dist/bench/queue-only.bench.js.map +0 -1
  214. package/dist/bench/reactor-throughput.bench.d.ts +0 -2
  215. package/dist/bench/reactor-throughput.bench.d.ts.map +0 -1
  216. package/dist/bench/reactor-throughput.bench.js +0 -137
  217. package/dist/bench/reactor-throughput.bench.js.map +0 -1
  218. package/dist/src/executor/job-executor.d.ts +0 -62
  219. package/dist/src/executor/job-executor.d.ts.map +0 -1
  220. package/dist/src/executor/job-executor.js +0 -325
  221. package/dist/src/executor/job-executor.js.map +0 -1
  222. package/dist/test/event-bus.test.d.ts +0 -2
  223. package/dist/test/event-bus.test.d.ts.map +0 -1
  224. package/dist/test/event-bus.test.js +0 -532
  225. package/dist/test/event-bus.test.js.map +0 -1
  226. package/dist/test/job-executor.test.d.ts +0 -2
  227. package/dist/test/job-executor.test.d.ts.map +0 -1
  228. package/dist/test/job-executor.test.js +0 -581
  229. package/dist/test/job-executor.test.js.map +0 -1
  230. package/dist/test/queue.test.d.ts +0 -2
  231. package/dist/test/queue.test.d.ts.map +0 -1
  232. package/dist/test/queue.test.js +0 -396
  233. package/dist/test/queue.test.js.map +0 -1
  234. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,666 @@
1
+ import { AbortError } from "document-drive";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { createMutableShutdownStatus } from "../shared/factories.js";
4
+ import { JobStatus } from "../shared/types.js";
5
+ import { matchesScope } from "../shared/utils.js";
6
+ import { filterByParentId, filterByType, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
7
+ /**
8
+ * This class implements the IReactor interface and serves as the main entry point
9
+ * for the new Reactor architecture.
10
+ */
11
+ export class Reactor {
12
+ driveServer;
13
+ documentStorage;
14
+ shutdownStatus;
15
+ setShutdown;
16
+ queue;
17
+ jobTracker;
18
+ readModelCoordinator;
19
+ constructor(driveServer, documentStorage, queue, jobTracker, readModelCoordinator) {
20
+ // Store required dependencies
21
+ this.driveServer = driveServer;
22
+ this.documentStorage = documentStorage;
23
+ this.queue = queue;
24
+ this.jobTracker = jobTracker;
25
+ this.readModelCoordinator = readModelCoordinator;
26
+ // Start the read model coordinator
27
+ this.readModelCoordinator.start();
28
+ // Create mutable shutdown status using factory method
29
+ const [status, setter] = createMutableShutdownStatus(false);
30
+ this.shutdownStatus = status;
31
+ this.setShutdown = setter;
32
+ }
33
+ /**
34
+ * Signals that the reactor should shutdown.
35
+ */
36
+ kill() {
37
+ // Mark the reactor as shutdown
38
+ this.setShutdown(true);
39
+ // Stop the read model coordinator
40
+ this.readModelCoordinator.stop();
41
+ // TODO: Phase 3+ - Implement graceful shutdown for queue, executors, etc.
42
+ // For now, we just mark as shutdown and return status
43
+ return this.shutdownStatus;
44
+ }
45
+ /**
46
+ * Retrieves a list of document model specifications
47
+ */
48
+ getDocumentModels(namespace, paging, signal) {
49
+ // Get document model modules from the drive server + filter
50
+ const modules = this.driveServer.getDocumentModelModules();
51
+ const filteredModels = modules.filter((module) => !namespace || module.documentModel.global.name.startsWith(namespace));
52
+ // Apply paging
53
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
54
+ const limit = paging?.limit || filteredModels.length;
55
+ const pagedModels = filteredModels.slice(startIndex, startIndex + limit);
56
+ // Create paged results
57
+ const hasMore = startIndex + limit < filteredModels.length;
58
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
59
+ // even thought this is currently synchronous, they could have passed in an already-aborted signal
60
+ if (signal?.aborted) {
61
+ throw new AbortError();
62
+ }
63
+ return Promise.resolve({
64
+ results: pagedModels,
65
+ options: paging || { cursor: "0", limit: filteredModels.length },
66
+ nextCursor,
67
+ next: hasMore
68
+ ? () => this.getDocumentModels(namespace, { cursor: nextCursor, limit }, signal)
69
+ : undefined,
70
+ });
71
+ }
72
+ /**
73
+ * Retrieves a specific PHDocument by id
74
+ */
75
+ async get(id, view, signal) {
76
+ const document = await this.documentStorage.get(id);
77
+ if (signal?.aborted) {
78
+ throw new AbortError();
79
+ }
80
+ const childIds = await this.documentStorage.getChildren(id);
81
+ if (signal?.aborted) {
82
+ throw new AbortError();
83
+ }
84
+ // Apply view filter - This will be removed when we pass the viewfilter along
85
+ // to the underlying store, but is here now for the interface.
86
+ for (const scope in document.state) {
87
+ if (!matchesScope(view, scope)) {
88
+ delete document.state[scope];
89
+ }
90
+ }
91
+ return {
92
+ document,
93
+ childIds,
94
+ };
95
+ }
96
+ /**
97
+ * Retrieves a specific PHDocument by slug
98
+ */
99
+ async getBySlug(slug, view, signal) {
100
+ // Use the storage layer to resolve slug to ID
101
+ let ids;
102
+ try {
103
+ ids = await this.documentStorage.resolveIds([slug], signal);
104
+ }
105
+ catch (error) {
106
+ // If the error is from resolveIds (document not found), wrap it with our message
107
+ if (error instanceof Error && error.message.includes("not found")) {
108
+ throw new Error(`Document not found with slug: ${slug}`);
109
+ }
110
+ throw error;
111
+ }
112
+ if (ids.length === 0 || !ids[0]) {
113
+ throw new Error(`Document not found with slug: ${slug}`);
114
+ }
115
+ // Now get the document by its resolved ID
116
+ return await this.get(ids[0], view, signal);
117
+ }
118
+ /**
119
+ * Retrieves the operations for a document
120
+ */
121
+ async getOperations(documentId, view, paging, signal) {
122
+ // Use storage directly to get the document
123
+ const document = await this.documentStorage.get(documentId);
124
+ if (signal?.aborted) {
125
+ throw new AbortError();
126
+ }
127
+ const operations = document.operations;
128
+ const result = {};
129
+ // apply view filter, per scope -- this will be removed when we pass the viewfilter along
130
+ // to the underlying store, but is here now for the interface.
131
+ for (const scope in operations) {
132
+ if (matchesScope(view, scope)) {
133
+ const scopeOperations = operations[scope];
134
+ // apply paging too
135
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
136
+ const limit = paging?.limit || scopeOperations.length;
137
+ const pagedOperations = scopeOperations.slice(startIndex, startIndex + limit);
138
+ result[scope] = {
139
+ results: pagedOperations,
140
+ options: { cursor: String(startIndex + limit), limit },
141
+ };
142
+ }
143
+ }
144
+ return Promise.resolve(result);
145
+ }
146
+ /**
147
+ * Filters documents by criteria and returns a list of them
148
+ */
149
+ async find(search, view, paging, signal) {
150
+ let results;
151
+ if (search.ids) {
152
+ if (search.slugs && search.slugs.length > 0) {
153
+ throw new Error("Cannot use both ids and slugs in the same search");
154
+ }
155
+ results = await this.findByIds(search.ids, view, paging, signal);
156
+ if (search.parentId) {
157
+ results = filterByParentId(results, search.parentId);
158
+ }
159
+ if (search.type) {
160
+ results = filterByType(results, search.type);
161
+ }
162
+ }
163
+ else if (search.slugs) {
164
+ results = await this.findBySlugs(search.slugs, view, paging, signal);
165
+ if (search.parentId) {
166
+ results = filterByParentId(results, search.parentId);
167
+ }
168
+ if (search.type) {
169
+ results = filterByType(results, search.type);
170
+ }
171
+ }
172
+ else if (search.parentId) {
173
+ results = await this.findByParentId(search.parentId, view, paging, signal);
174
+ if (search.type) {
175
+ results = filterByType(results, search.type);
176
+ }
177
+ }
178
+ else if (search.type) {
179
+ results = await this.findByType(search.type, view, paging, signal);
180
+ }
181
+ else {
182
+ throw new Error("No search criteria provided");
183
+ }
184
+ if (signal?.aborted) {
185
+ throw new AbortError();
186
+ }
187
+ return results;
188
+ }
189
+ /**
190
+ * Creates a document
191
+ */
192
+ async create(document, signal) {
193
+ const createdAtUtcIso = new Date().toISOString();
194
+ if (signal?.aborted) {
195
+ throw new AbortError();
196
+ }
197
+ // Create a CREATE_DOCUMENT action with proper CreateDocumentActionInput
198
+ const input = {
199
+ model: document.header.documentType,
200
+ version: "0.0.0",
201
+ documentId: document.header.id,
202
+ };
203
+ // Add signing info
204
+ input.signing = {
205
+ signature: document.header.id,
206
+ publicKey: document.header.sig.publicKey,
207
+ nonce: document.header.sig.nonce,
208
+ createdAtUtcIso: document.header.createdAtUtcIso,
209
+ documentType: document.header.documentType,
210
+ };
211
+ // Add optional mutable header fields (always include even if empty/undefined)
212
+ input.slug = document.header.slug;
213
+ input.name = document.header.name;
214
+ input.branch = document.header.branch;
215
+ input.meta = document.header.meta;
216
+ const createAction = {
217
+ id: `${document.header.id}-create`,
218
+ type: "CREATE_DOCUMENT",
219
+ scope: "document",
220
+ timestampUtcMs: new Date().toISOString(),
221
+ input,
222
+ };
223
+ // Create an UPGRADE_DOCUMENT action to set the initial state
224
+ const upgradeInput = {
225
+ model: document.header.documentType,
226
+ fromVersion: "0.0.0",
227
+ toVersion: "0.0.0", // Same version since we're just setting initial state
228
+ documentId: document.header.id,
229
+ initialState: document.state,
230
+ };
231
+ const upgradeAction = {
232
+ id: `${document.header.id}-upgrade`,
233
+ type: "UPGRADE_DOCUMENT",
234
+ scope: "document",
235
+ timestampUtcMs: new Date().toISOString(),
236
+ input: upgradeInput,
237
+ };
238
+ // Create a single job with both CREATE_DOCUMENT and UPGRADE_DOCUMENT actions
239
+ const job = {
240
+ id: uuidv4(),
241
+ documentId: document.header.id,
242
+ scope: "document",
243
+ branch: "main",
244
+ actions: [createAction, upgradeAction],
245
+ createdAt: new Date().toISOString(),
246
+ queueHint: [],
247
+ maxRetries: 3,
248
+ errorHistory: [],
249
+ };
250
+ // Create job info and register with tracker
251
+ const jobInfo = {
252
+ id: job.id,
253
+ status: JobStatus.PENDING,
254
+ createdAtUtcIso,
255
+ };
256
+ this.jobTracker.registerJob(jobInfo);
257
+ // Enqueue the job
258
+ await this.queue.enqueue(job);
259
+ return jobInfo;
260
+ }
261
+ /**
262
+ * Deletes a document
263
+ */
264
+ async deleteDocument(id, propagate, signal) {
265
+ const createdAtUtcIso = new Date().toISOString();
266
+ if (signal?.aborted) {
267
+ throw new AbortError();
268
+ }
269
+ const deleteInput = {
270
+ documentId: id,
271
+ propagate,
272
+ };
273
+ const action = {
274
+ id: `${id}-delete`,
275
+ type: "DELETE_DOCUMENT",
276
+ scope: "document",
277
+ timestampUtcMs: new Date().toISOString(),
278
+ input: deleteInput,
279
+ };
280
+ const job = {
281
+ id: uuidv4(),
282
+ documentId: id,
283
+ scope: "document",
284
+ branch: "main",
285
+ actions: [action],
286
+ createdAt: new Date().toISOString(),
287
+ queueHint: [],
288
+ maxRetries: 3,
289
+ errorHistory: [],
290
+ };
291
+ const jobInfo = {
292
+ id: job.id,
293
+ status: JobStatus.PENDING,
294
+ createdAtUtcIso,
295
+ };
296
+ this.jobTracker.registerJob(jobInfo);
297
+ await this.queue.enqueue(job);
298
+ return jobInfo;
299
+ }
300
+ /**
301
+ * Applies a list of actions to a document
302
+ */
303
+ async mutate(id, actions) {
304
+ const createdAtUtcIso = new Date().toISOString();
305
+ // Determine scope from first action (all actions should have the same scope)
306
+ const scope = actions.length > 0 ? actions[0].scope || "global" : "global";
307
+ // Create a single job with all actions
308
+ const job = {
309
+ id: uuidv4(),
310
+ documentId: id,
311
+ scope: scope,
312
+ branch: "main", // Default to main branch
313
+ actions: actions,
314
+ createdAt: new Date().toISOString(),
315
+ queueHint: [],
316
+ maxRetries: 3,
317
+ errorHistory: [],
318
+ };
319
+ // Create job info and register with tracker
320
+ const jobInfo = {
321
+ id: job.id,
322
+ status: JobStatus.PENDING,
323
+ createdAtUtcIso,
324
+ };
325
+ this.jobTracker.registerJob(jobInfo);
326
+ // Enqueue the job
327
+ await this.queue.enqueue(job);
328
+ return jobInfo;
329
+ }
330
+ /**
331
+ * Applies multiple mutations across documents with dependency management
332
+ */
333
+ async mutateBatch(request, signal) {
334
+ if (signal?.aborted) {
335
+ throw new AbortError();
336
+ }
337
+ validateBatchRequest(request.jobs);
338
+ for (const jobPlan of request.jobs) {
339
+ validateActionScopes(jobPlan);
340
+ }
341
+ const createdAtUtcIso = new Date().toISOString();
342
+ const planKeyToJobId = new Map();
343
+ for (const jobPlan of request.jobs) {
344
+ planKeyToJobId.set(jobPlan.key, uuidv4());
345
+ }
346
+ const jobInfos = new Map();
347
+ for (const jobPlan of request.jobs) {
348
+ const jobId = planKeyToJobId.get(jobPlan.key);
349
+ const jobInfo = {
350
+ id: jobId,
351
+ status: JobStatus.PENDING,
352
+ createdAtUtcIso,
353
+ };
354
+ this.jobTracker.registerJob(jobInfo);
355
+ jobInfos.set(jobPlan.key, jobInfo);
356
+ }
357
+ const sortedKeys = topologicalSort(request.jobs);
358
+ const enqueuedKeys = [];
359
+ try {
360
+ for (const key of sortedKeys) {
361
+ if (signal?.aborted) {
362
+ throw new AbortError();
363
+ }
364
+ const jobPlan = request.jobs.find((j) => j.key === key);
365
+ const jobId = planKeyToJobId.get(key);
366
+ const queueHint = jobPlan.dependsOn.map((depKey) => planKeyToJobId.get(depKey));
367
+ const job = {
368
+ id: jobId,
369
+ documentId: jobPlan.documentId,
370
+ scope: jobPlan.scope,
371
+ branch: jobPlan.branch,
372
+ actions: jobPlan.actions,
373
+ createdAt: createdAtUtcIso,
374
+ queueHint,
375
+ maxRetries: 3,
376
+ errorHistory: [],
377
+ };
378
+ await this.queue.enqueue(job);
379
+ enqueuedKeys.push(key);
380
+ }
381
+ }
382
+ catch (error) {
383
+ for (const key of enqueuedKeys) {
384
+ const jobId = planKeyToJobId.get(key);
385
+ try {
386
+ await this.queue.remove(jobId);
387
+ }
388
+ catch (removeError) {
389
+ // Ignore removal errors during cleanup
390
+ }
391
+ }
392
+ for (const jobInfo of jobInfos.values()) {
393
+ this.jobTracker.markFailed(jobInfo.id, toErrorInfo("Batch enqueue failed"));
394
+ }
395
+ throw error;
396
+ }
397
+ const result = {
398
+ jobs: Object.fromEntries(jobInfos),
399
+ };
400
+ return result;
401
+ }
402
+ /**
403
+ * Adds multiple documents as children to another
404
+ */
405
+ async addChildren(parentId, documentIds, view, signal) {
406
+ if (signal?.aborted) {
407
+ throw new AbortError();
408
+ }
409
+ const actions = documentIds.map((childId) => ({
410
+ id: uuidv4(),
411
+ type: "ADD_RELATIONSHIP",
412
+ scope: "document",
413
+ timestampUtcMs: new Date().toISOString(),
414
+ input: {
415
+ sourceId: parentId,
416
+ targetId: childId,
417
+ relationshipType: "child",
418
+ },
419
+ }));
420
+ return await this.mutate(parentId, actions);
421
+ }
422
+ /**
423
+ * Removes multiple documents as children from another
424
+ */
425
+ async removeChildren(parentId, documentIds, view, signal) {
426
+ if (signal?.aborted) {
427
+ throw new AbortError();
428
+ }
429
+ const actions = documentIds.map((childId) => ({
430
+ id: uuidv4(),
431
+ type: "REMOVE_RELATIONSHIP",
432
+ scope: "document",
433
+ timestampUtcMs: new Date().toISOString(),
434
+ input: {
435
+ sourceId: parentId,
436
+ targetId: childId,
437
+ relationshipType: "child",
438
+ },
439
+ }));
440
+ return await this.mutate(parentId, actions);
441
+ }
442
+ /**
443
+ * Retrieves the status of a job
444
+ */
445
+ getJobStatus(jobId, signal) {
446
+ if (signal?.aborted) {
447
+ throw new AbortError();
448
+ }
449
+ const jobInfo = this.jobTracker.getJobStatus(jobId);
450
+ if (!jobInfo) {
451
+ // Job not found - return FAILED status with appropriate error
452
+ return Promise.resolve({
453
+ id: jobId,
454
+ status: JobStatus.FAILED,
455
+ createdAtUtcIso: new Date().toISOString(),
456
+ completedAtUtcIso: new Date().toISOString(),
457
+ error: toErrorInfo("Job not found"),
458
+ });
459
+ }
460
+ return Promise.resolve(jobInfo);
461
+ }
462
+ /**
463
+ * Finds documents by their IDs
464
+ */
465
+ async findByIds(ids, view, paging, signal) {
466
+ const documents = [];
467
+ // Fetch each document by ID using storage directly
468
+ for (const id of ids) {
469
+ if (signal?.aborted) {
470
+ throw new AbortError();
471
+ }
472
+ let document;
473
+ try {
474
+ document = await this.documentStorage.get(id);
475
+ }
476
+ catch {
477
+ // Skip documents that don't exist or can't be accessed
478
+ // This matches the behavior expected from a search operation
479
+ continue;
480
+ }
481
+ // Apply view filter - This will be removed when we pass the viewfilter along
482
+ // to the underlying store, but is here now for the interface.
483
+ for (const scope in document.state) {
484
+ if (!matchesScope(view, scope)) {
485
+ delete document.state[scope];
486
+ }
487
+ }
488
+ documents.push(document);
489
+ }
490
+ if (signal?.aborted) {
491
+ throw new AbortError();
492
+ }
493
+ // Apply paging
494
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
495
+ const limit = paging?.limit || documents.length;
496
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
497
+ // Create paged results
498
+ const hasMore = startIndex + limit < documents.length;
499
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
500
+ return {
501
+ results: pagedDocuments,
502
+ options: paging || { cursor: "0", limit: documents.length },
503
+ nextCursor,
504
+ next: hasMore
505
+ ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, signal)
506
+ : undefined,
507
+ };
508
+ }
509
+ /**
510
+ * Finds documents by their slugs
511
+ */
512
+ async findBySlugs(slugs, view, paging, signal) {
513
+ const documents = [];
514
+ // Use storage to resolve slugs to IDs
515
+ let ids;
516
+ try {
517
+ ids = await this.documentStorage.resolveIds(slugs, signal);
518
+ }
519
+ catch {
520
+ // If slug resolution fails, return empty results
521
+ // This matches the behavior expected from a search operation
522
+ ids = [];
523
+ }
524
+ // Fetch each document by resolved ID
525
+ for (const id of ids) {
526
+ if (signal?.aborted) {
527
+ throw new AbortError();
528
+ }
529
+ let document;
530
+ try {
531
+ document = await this.documentStorage.get(id);
532
+ }
533
+ catch {
534
+ // Skip documents that don't exist or can't be accessed
535
+ continue;
536
+ }
537
+ // Apply view filter - This will be removed when we pass the viewfilter along
538
+ // to the underlying store, but is here now for the interface.
539
+ for (const scope in document.state) {
540
+ if (!matchesScope(view, scope)) {
541
+ delete document.state[scope];
542
+ }
543
+ }
544
+ documents.push(document);
545
+ }
546
+ if (signal?.aborted) {
547
+ throw new AbortError();
548
+ }
549
+ // Apply paging - this will be removed when we pass the paging along
550
+ // to the underlying store, but is here now for the interface.
551
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
552
+ const limit = paging?.limit || documents.length;
553
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
554
+ // Create paged results
555
+ const hasMore = startIndex + limit < documents.length;
556
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
557
+ return {
558
+ results: pagedDocuments,
559
+ options: paging || { cursor: "0", limit: documents.length },
560
+ nextCursor,
561
+ next: hasMore
562
+ ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, signal)
563
+ : undefined,
564
+ };
565
+ }
566
+ /**
567
+ * Finds documents by parent ID
568
+ */
569
+ async findByParentId(parentId, view, paging, signal) {
570
+ // Get child document IDs from storage
571
+ const childIds = await this.documentStorage.getChildren(parentId);
572
+ if (signal?.aborted) {
573
+ throw new AbortError();
574
+ }
575
+ const documents = [];
576
+ // Fetch each child document
577
+ for (const childId of childIds) {
578
+ if (signal?.aborted) {
579
+ throw new AbortError();
580
+ }
581
+ let document;
582
+ try {
583
+ document = await this.documentStorage.get(childId);
584
+ }
585
+ catch {
586
+ // Skip documents that don't exist or can't be accessed
587
+ // This matches the behavior expected from a search operation
588
+ continue;
589
+ }
590
+ // Apply view filter - This will be removed when we pass the viewfilter along
591
+ // to the underlying store, but is here now for the interface.
592
+ for (const scope in document.state) {
593
+ if (!matchesScope(view, scope)) {
594
+ delete document.state[scope];
595
+ }
596
+ }
597
+ documents.push(document);
598
+ }
599
+ if (signal?.aborted) {
600
+ throw new AbortError();
601
+ }
602
+ // Apply paging
603
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
604
+ const limit = paging?.limit || documents.length;
605
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
606
+ // Create paged results
607
+ const hasMore = startIndex + limit < documents.length;
608
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
609
+ return {
610
+ results: pagedDocuments,
611
+ options: paging || { cursor: "0", limit: documents.length },
612
+ nextCursor,
613
+ next: hasMore
614
+ ? () => this.findByParentId(parentId, view, { cursor: nextCursor, limit }, signal)
615
+ : undefined,
616
+ };
617
+ }
618
+ /**
619
+ * Finds documents by type
620
+ */
621
+ async findByType(type, view, paging, signal) {
622
+ const documents = [];
623
+ // Use storage's findByType method directly
624
+ const cursor = paging?.cursor;
625
+ const limit = paging?.limit || 100;
626
+ // Get document IDs of the specified type
627
+ const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor);
628
+ if (signal?.aborted) {
629
+ throw new AbortError();
630
+ }
631
+ // Fetch each document by its ID
632
+ for (const documentId of documentIds) {
633
+ if (signal?.aborted) {
634
+ throw new AbortError();
635
+ }
636
+ let document;
637
+ try {
638
+ document = await this.documentStorage.get(documentId);
639
+ }
640
+ catch {
641
+ // Skip documents that can't be retrieved
642
+ continue;
643
+ }
644
+ // Apply view filter
645
+ for (const scope in document.state) {
646
+ if (!matchesScope(view, scope)) {
647
+ delete document.state[scope];
648
+ }
649
+ }
650
+ documents.push(document);
651
+ }
652
+ if (signal?.aborted) {
653
+ throw new AbortError();
654
+ }
655
+ // Results are already paged from the storage layer
656
+ return {
657
+ results: documents,
658
+ options: paging || { cursor: cursor || "0", limit },
659
+ nextCursor,
660
+ next: nextCursor
661
+ ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, signal)
662
+ : undefined,
663
+ };
664
+ }
665
+ }
666
+ //# sourceMappingURL=reactor.js.map