@powerhousedao/reactor 6.0.0-dev.26 → 6.0.0-dev.27

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 (98) hide show
  1. package/dist/src/cache/document-meta-cache.js +3 -3
  2. package/dist/src/cache/document-meta-cache.js.map +1 -1
  3. package/dist/src/cache/kysely-operation-index.d.ts +2 -1
  4. package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
  5. package/dist/src/cache/kysely-operation-index.js +16 -4
  6. package/dist/src/cache/kysely-operation-index.js.map +1 -1
  7. package/dist/src/cache/kysely-write-cache.js +8 -8
  8. package/dist/src/cache/kysely-write-cache.js.map +1 -1
  9. package/dist/src/cache/operation-index-types.d.ts +2 -1
  10. package/dist/src/cache/operation-index-types.d.ts.map +1 -1
  11. package/dist/src/cache/operation-index-types.js.map +1 -1
  12. package/dist/src/client/reactor-client.d.ts +4 -6
  13. package/dist/src/client/reactor-client.d.ts.map +1 -1
  14. package/dist/src/client/reactor-client.js +20 -25
  15. package/dist/src/client/reactor-client.js.map +1 -1
  16. package/dist/src/client/types.d.ts +1 -4
  17. package/dist/src/client/types.d.ts.map +1 -1
  18. package/dist/src/core/reactor-builder.d.ts.map +1 -1
  19. package/dist/src/core/reactor-builder.js +2 -5
  20. package/dist/src/core/reactor-builder.js.map +1 -1
  21. package/dist/src/core/reactor-client-builder.d.ts +3 -2
  22. package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
  23. package/dist/src/core/reactor-client-builder.js +13 -4
  24. package/dist/src/core/reactor-client-builder.js.map +1 -1
  25. package/dist/src/core/reactor.d.ts +8 -75
  26. package/dist/src/core/reactor.d.ts.map +1 -1
  27. package/dist/src/core/reactor.js +72 -552
  28. package/dist/src/core/reactor.js.map +1 -1
  29. package/dist/src/core/types.d.ts +28 -19
  30. package/dist/src/core/types.d.ts.map +1 -1
  31. package/dist/src/core/utils.d.ts +6 -1
  32. package/dist/src/core/utils.d.ts.map +1 -1
  33. package/dist/src/core/utils.js +22 -6
  34. package/dist/src/core/utils.js.map +1 -1
  35. package/dist/src/executor/simple-job-executor.js +5 -5
  36. package/dist/src/executor/simple-job-executor.js.map +1 -1
  37. package/dist/src/index.d.ts +3 -4
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +2 -3
  40. package/dist/src/index.js.map +1 -1
  41. package/dist/src/read-models/base-read-model.js +4 -4
  42. package/dist/src/read-models/base-read-model.js.map +1 -1
  43. package/dist/src/read-models/document-view.d.ts +5 -2
  44. package/dist/src/read-models/document-view.d.ts.map +1 -1
  45. package/dist/src/read-models/document-view.js +128 -28
  46. package/dist/src/read-models/document-view.js.map +1 -1
  47. package/dist/src/shared/collect-all-pages.d.ts +7 -0
  48. package/dist/src/shared/collect-all-pages.d.ts.map +1 -0
  49. package/dist/src/shared/collect-all-pages.js +17 -0
  50. package/dist/src/shared/collect-all-pages.js.map +1 -0
  51. package/dist/src/signer/passthrough-signer.d.ts +1 -1
  52. package/dist/src/signer/passthrough-signer.d.ts.map +1 -1
  53. package/dist/src/signer/passthrough-signer.js +1 -3
  54. package/dist/src/signer/passthrough-signer.js.map +1 -1
  55. package/dist/src/storage/interfaces.d.ts +38 -94
  56. package/dist/src/storage/interfaces.d.ts.map +1 -1
  57. package/dist/src/storage/interfaces.js.map +1 -1
  58. package/dist/src/storage/kysely/document-indexer.d.ts +6 -6
  59. package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -1
  60. package/dist/src/storage/kysely/document-indexer.js +123 -52
  61. package/dist/src/storage/kysely/document-indexer.js.map +1 -1
  62. package/dist/src/storage/kysely/store.d.ts +2 -1
  63. package/dist/src/storage/kysely/store.d.ts.map +1 -1
  64. package/dist/src/storage/kysely/store.js +33 -15
  65. package/dist/src/storage/kysely/store.js.map +1 -1
  66. package/dist/src/sync/channels/composite-channel-factory.d.ts.map +1 -1
  67. package/dist/src/sync/channels/composite-channel-factory.js +5 -2
  68. package/dist/src/sync/channels/composite-channel-factory.js.map +1 -1
  69. package/dist/src/sync/channels/gql-channel-factory.d.ts.map +1 -1
  70. package/dist/src/sync/channels/gql-channel-factory.js +5 -2
  71. package/dist/src/sync/channels/gql-channel-factory.js.map +1 -1
  72. package/dist/src/sync/channels/gql-channel.d.ts +4 -9
  73. package/dist/src/sync/channels/gql-channel.d.ts.map +1 -1
  74. package/dist/src/sync/channels/gql-channel.js +11 -31
  75. package/dist/src/sync/channels/gql-channel.js.map +1 -1
  76. package/dist/src/sync/channels/index.d.ts +2 -0
  77. package/dist/src/sync/channels/index.d.ts.map +1 -1
  78. package/dist/src/sync/channels/index.js +2 -0
  79. package/dist/src/sync/channels/index.js.map +1 -1
  80. package/dist/src/sync/channels/interval-poll-timer.d.ts +18 -0
  81. package/dist/src/sync/channels/interval-poll-timer.d.ts.map +1 -0
  82. package/dist/src/sync/channels/interval-poll-timer.js +43 -0
  83. package/dist/src/sync/channels/interval-poll-timer.js.map +1 -0
  84. package/dist/src/sync/channels/poll-timer.d.ts +14 -0
  85. package/dist/src/sync/channels/poll-timer.d.ts.map +1 -0
  86. package/dist/src/sync/channels/poll-timer.js +2 -0
  87. package/dist/src/sync/channels/poll-timer.js.map +1 -0
  88. package/dist/src/sync/index.d.ts +1 -1
  89. package/dist/src/sync/index.d.ts.map +1 -1
  90. package/dist/src/sync/index.js +1 -1
  91. package/dist/src/sync/index.js.map +1 -1
  92. package/dist/src/sync/sync-manager.js +2 -2
  93. package/dist/src/sync/sync-manager.js.map +1 -1
  94. package/package.json +3 -3
  95. package/dist/src/storage/consistency-aware-legacy-storage.d.ts +0 -33
  96. package/dist/src/storage/consistency-aware-legacy-storage.d.ts.map +0 -1
  97. package/dist/src/storage/consistency-aware-legacy-storage.js +0 -65
  98. package/dist/src/storage/consistency-aware-legacy-storage.js.map +0 -1
@@ -5,7 +5,7 @@ import { ReactorEventTypes } from "../events/types.js";
5
5
  import { createMutableShutdownStatus } from "../shared/factories.js";
6
6
  import { JobStatus } from "../shared/types.js";
7
7
  import { matchesScope } from "../shared/utils.js";
8
- import { filterByType, getSharedScope, signAction, signActions, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
8
+ import { filterByType, getSharedActionScope, getSharedOperationScope, signAction, signActions, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
9
9
  /**
10
10
  * This class implements the IReactor interface and serves as the main entry point
11
11
  * for the new Reactor architecture.
@@ -13,7 +13,6 @@ import { filterByType, getSharedScope, signAction, signActions, toErrorInfo, top
13
13
  export class Reactor {
14
14
  logger;
15
15
  documentModelRegistry;
16
- documentStorage;
17
16
  shutdownStatus;
18
17
  setShutdown;
19
18
  queue;
@@ -21,59 +20,44 @@ export class Reactor {
21
20
  readModelCoordinator;
22
21
  features;
23
22
  documentView;
24
- _documentIndexer;
23
+ documentIndexer;
25
24
  operationStore;
26
25
  eventBus;
27
- constructor(logger, documentModelRegistry, documentStorage, queue, jobTracker, readModelCoordinator, features, documentView, documentIndexer, operationStore, eventBus) {
26
+ constructor(logger, documentModelRegistry, queue, jobTracker, readModelCoordinator, features, documentView, documentIndexer, operationStore, eventBus) {
28
27
  this.logger = logger;
29
28
  this.documentModelRegistry = documentModelRegistry;
30
- this.documentStorage = documentStorage;
31
29
  this.queue = queue;
32
30
  this.jobTracker = jobTracker;
33
31
  this.readModelCoordinator = readModelCoordinator;
34
32
  this.features = features;
35
33
  this.documentView = documentView;
36
- this._documentIndexer = documentIndexer;
34
+ this.documentIndexer = documentIndexer;
37
35
  this.operationStore = operationStore;
38
36
  this.eventBus = eventBus;
39
37
  const [status, setter] = createMutableShutdownStatus(false);
40
38
  this.shutdownStatus = status;
41
39
  this.setShutdown = setter;
42
- this.logger.verbose("Reactor({ legacyStorage: @legacy })", features.legacyStorageEnabled);
43
40
  this.readModelCoordinator.start();
44
41
  }
45
- /**
46
- * Signals that the reactor should shutdown.
47
- */
48
42
  kill() {
49
43
  this.logger.verbose("kill()");
50
- // Mark the reactor as shutdown
51
44
  this.setShutdown(true);
52
- // Stop the read model coordinator
53
45
  this.readModelCoordinator.stop();
54
- // Stop the job tracker
55
46
  this.jobTracker.shutdown();
56
47
  return this.shutdownStatus;
57
48
  }
58
- /**
59
- * Retrieves a list of document model specifications
60
- */
61
49
  getDocumentModels(namespace, paging, signal) {
62
50
  this.logger.verbose("getDocumentModels(@namespace, @paging)", namespace, paging);
63
- // Get document model modules from the registry + filter
51
+ if (signal?.aborted) {
52
+ throw new AbortError();
53
+ }
64
54
  const modules = this.documentModelRegistry.getAllModules();
65
55
  const filteredModels = modules.filter((module) => !namespace || module.documentModel.global.id.startsWith(namespace));
66
- // Apply paging
67
56
  const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
68
57
  const limit = paging?.limit || filteredModels.length;
69
58
  const pagedModels = filteredModels.slice(startIndex, startIndex + limit);
70
- // Create paged results
71
59
  const hasMore = startIndex + limit < filteredModels.length;
72
60
  const nextCursor = hasMore ? String(startIndex + limit) : undefined;
73
- // even thought this is currently synchronous, they could have passed in an already-aborted signal
74
- if (signal?.aborted) {
75
- throw new AbortError();
76
- }
77
61
  return Promise.resolve({
78
62
  results: pagedModels,
79
63
  options: paging || { cursor: "0", limit: filteredModels.length },
@@ -83,197 +67,70 @@ export class Reactor {
83
67
  : undefined,
84
68
  });
85
69
  }
86
- /**
87
- * Retrieves a specific PHDocument by id
88
- */
89
70
  async get(id, view, consistencyToken, signal) {
90
71
  this.logger.verbose("get(@id, @view)", id, view);
91
- if (this.features.legacyStorageEnabled) {
92
- const document = await this.documentStorage.get(id, consistencyToken, signal);
93
- if (signal?.aborted) {
94
- throw new AbortError();
95
- }
96
- const childIds = await this.documentStorage.getChildren(id, consistencyToken, signal);
97
- if (signal?.aborted) {
98
- throw new AbortError();
99
- }
100
- for (const scope in document.state) {
101
- if (!matchesScope(view, scope)) {
102
- delete document.state[scope];
103
- }
104
- }
105
- return {
106
- document,
107
- childIds,
108
- };
109
- }
110
- else {
111
- const document = await this.documentView.get(id, view, consistencyToken, signal);
112
- if (signal?.aborted) {
113
- throw new AbortError();
114
- }
115
- const relationships = await this._documentIndexer.getOutgoing(id, ["child"], consistencyToken, signal);
116
- if (signal?.aborted) {
117
- throw new AbortError();
118
- }
119
- const childIds = relationships.map((rel) => rel.targetId);
120
- return {
121
- document,
122
- childIds,
123
- };
124
- }
72
+ return await this.documentView.get(id, view, consistencyToken, signal);
125
73
  }
126
- /**
127
- * Retrieves a specific PHDocument by slug
128
- */
129
74
  async getBySlug(slug, view, consistencyToken, signal) {
130
75
  this.logger.verbose("getBySlug(@slug, @view)", slug, view);
131
- if (this.features.legacyStorageEnabled) {
132
- let ids;
133
- try {
134
- ids = await this.documentStorage.resolveIds([slug], consistencyToken, signal);
135
- }
136
- catch (error) {
137
- if (error instanceof Error && error.message.includes("not found")) {
138
- throw new Error(`Document not found with slug: ${slug}`);
139
- }
140
- throw error;
141
- }
142
- if (ids.length === 0 || !ids[0]) {
143
- throw new Error(`Document not found with slug: ${slug}`);
144
- }
145
- return await this.get(ids[0], view, consistencyToken, signal);
146
- }
147
- else {
148
- const documentId = await this.documentView.resolveSlug(slug, view, consistencyToken, signal);
149
- if (!documentId) {
150
- throw new Error(`Document not found with slug: ${slug}`);
151
- }
152
- return await this.get(documentId, view, consistencyToken, signal);
76
+ const documentId = await this.documentView.resolveSlug(slug, view, consistencyToken, signal);
77
+ if (!documentId) {
78
+ throw new Error(`Document not found with slug: ${slug}`);
153
79
  }
80
+ return await this.get(documentId, view, consistencyToken, signal);
154
81
  }
155
- /**
156
- * Retrieves a specific PHDocument by identifier (either id or slug)
157
- */
158
82
  async getByIdOrSlug(identifier, view, consistencyToken, signal) {
159
83
  this.logger.verbose("getByIdOrSlug(@identifier, @view)", identifier, view);
160
- if (this.features.legacyStorageEnabled) {
161
- try {
162
- return await this.get(identifier, view, consistencyToken, signal);
163
- }
164
- catch {
165
- try {
166
- const ids = await this.documentStorage.resolveIds([identifier], consistencyToken, signal);
167
- if (ids.length === 0 || !ids[0]) {
168
- throw new Error(`Document not found: ${identifier}`);
169
- }
170
- return await this.get(ids[0], view, consistencyToken, signal);
171
- }
172
- catch {
173
- throw new Error(`Document not found: ${identifier}`);
174
- }
175
- }
84
+ return await this.documentView.getByIdOrSlug(identifier, view, consistencyToken, signal);
85
+ }
86
+ async getChildren(documentId, consistencyToken, signal) {
87
+ const relationships = await this.documentIndexer.getOutgoing(documentId, ["child"], undefined, consistencyToken, signal);
88
+ if (signal?.aborted) {
89
+ throw new AbortError();
176
90
  }
177
- else {
178
- const document = await this.documentView.getByIdOrSlug(identifier, view, consistencyToken, signal);
179
- if (signal?.aborted) {
180
- throw new AbortError();
181
- }
182
- const relationships = await this._documentIndexer.getOutgoing(document.header.id, ["child"], consistencyToken, signal);
183
- if (signal?.aborted) {
184
- throw new AbortError();
185
- }
186
- const childIds = relationships.map((rel) => rel.targetId);
187
- return {
188
- document,
189
- childIds,
190
- };
91
+ return relationships.results.map((rel) => rel.targetId);
92
+ }
93
+ async getParents(childId, consistencyToken, signal) {
94
+ const relationships = await this.documentIndexer.getIncoming(childId, ["parent"], undefined, consistencyToken, signal);
95
+ if (signal?.aborted) {
96
+ throw new AbortError();
191
97
  }
98
+ return relationships.results.map((rel) => rel.sourceId);
192
99
  }
193
- /**
194
- * Retrieves the operations for a document
195
- */
196
100
  async getOperations(documentId, view, filter, paging, consistencyToken, signal) {
197
101
  this.logger.verbose("getOperations(@documentId, @view, @filter, @paging)", documentId, view, filter, paging);
198
- if (this.features.legacyStorageEnabled) {
199
- const document = await this.documentStorage.get(documentId, consistencyToken, signal);
200
- if (signal?.aborted) {
201
- throw new AbortError();
202
- }
203
- const operations = document.operations;
204
- const result = {};
205
- for (const scope in operations) {
206
- if (matchesScope(view, scope)) {
207
- let scopeOperations = operations[scope];
208
- if (filter) {
209
- if (filter.actionTypes && filter.actionTypes.length > 0) {
210
- scopeOperations = scopeOperations.filter((op) => filter.actionTypes.includes(op.action.type));
211
- }
212
- if (filter.sinceRevision !== undefined) {
213
- scopeOperations = scopeOperations.filter((op) => op.index >= filter.sinceRevision);
214
- }
215
- if (filter.timestampFrom) {
216
- const fromMs = new Date(filter.timestampFrom).getTime();
217
- scopeOperations = scopeOperations.filter((op) => new Date(op.timestampUtcMs).getTime() >= fromMs);
218
- }
219
- if (filter.timestampTo) {
220
- const toMs = new Date(filter.timestampTo).getTime();
221
- scopeOperations = scopeOperations.filter((op) => new Date(op.timestampUtcMs).getTime() <= toMs);
222
- }
223
- }
224
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
225
- const limit = paging?.limit || scopeOperations.length;
226
- const pagedOperations = scopeOperations.slice(startIndex, startIndex + limit);
227
- result[scope] = {
228
- results: pagedOperations,
229
- options: { cursor: String(startIndex + limit), limit },
230
- };
231
- }
232
- }
233
- return Promise.resolve(result);
102
+ const branch = view?.branch || "main";
103
+ const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
104
+ if (signal?.aborted) {
105
+ throw new AbortError();
234
106
  }
235
- else {
236
- const branch = view?.branch || "main";
237
- const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
107
+ const allScopes = Object.keys(revisions.revision);
108
+ const result = {};
109
+ for (const scope of allScopes) {
110
+ if (!matchesScope(view, scope)) {
111
+ continue;
112
+ }
238
113
  if (signal?.aborted) {
239
114
  throw new AbortError();
240
115
  }
241
- const allScopes = Object.keys(revisions.revision);
242
- const result = {};
243
- for (const scope of allScopes) {
244
- if (!matchesScope(view, scope)) {
245
- continue;
246
- }
247
- if (signal?.aborted) {
248
- throw new AbortError();
249
- }
250
- const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, filter, paging, signal);
251
- const currentCursor = paging?.cursor ? parseInt(paging.cursor) : 0;
252
- const currentLimit = paging?.limit || 100;
253
- result[scope] = {
254
- results: scopeResult.items,
255
- options: {
256
- cursor: scopeResult.nextCursor || String(currentCursor),
257
- limit: currentLimit,
258
- },
259
- nextCursor: scopeResult.nextCursor,
260
- next: scopeResult.hasMore
261
- ? async () => {
262
- const nextPage = await this.getOperations(documentId, view, filter, {
263
- cursor: scopeResult.nextCursor,
264
- limit: currentLimit,
265
- }, consistencyToken, signal);
266
- return nextPage[scope];
267
- }
268
- : undefined,
269
- };
270
- }
271
- return result;
116
+ const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, filter, paging, signal);
117
+ result[scope] = {
118
+ results: scopeResult.results,
119
+ options: scopeResult.options,
120
+ nextCursor: scopeResult.nextCursor,
121
+ next: scopeResult.next
122
+ ? async () => {
123
+ const nextPage = await this.getOperations(documentId, view, filter, {
124
+ cursor: scopeResult.nextCursor,
125
+ limit: scopeResult.options.limit,
126
+ }, consistencyToken, signal);
127
+ return nextPage[scope];
128
+ }
129
+ : undefined,
130
+ };
272
131
  }
132
+ return result;
273
133
  }
274
- /**
275
- * Filters documents by criteria and returns a list of them
276
- */
277
134
  async find(search, view, paging, consistencyToken, signal) {
278
135
  this.logger.verbose("find(@search, @view, @paging)", search, view, paging);
279
136
  let results;
@@ -293,7 +150,7 @@ export class Reactor {
293
150
  }
294
151
  }
295
152
  else if (search.parentId) {
296
- results = await this.findByParentId(search.parentId, view, paging, signal);
153
+ results = await this.findByParentId(search.parentId, view, paging, consistencyToken, signal);
297
154
  if (search.type) {
298
155
  results = filterByType(results, search.type);
299
156
  }
@@ -309,9 +166,6 @@ export class Reactor {
309
166
  }
310
167
  return results;
311
168
  }
312
- /**
313
- * Creates a document
314
- */
315
169
  async create(document, signer, signal, meta) {
316
170
  this.logger.verbose("create(@id, @type, @slug)", document.header.id, document.header.documentType, document.header.slug);
317
171
  const createdAtUtcIso = new Date().toISOString();
@@ -345,12 +199,10 @@ export class Reactor {
345
199
  toVersion: document.state.document.version,
346
200
  initialState: document.state,
347
201
  });
348
- // Sign actions if signer is provided
349
202
  let actions = [createAction, upgradeAction];
350
203
  if (signer) {
351
204
  actions = await signActions(actions, signer, signal);
352
205
  }
353
- // Create a single job with both CREATE_DOCUMENT and UPGRADE_DOCUMENT actions
354
206
  const job = {
355
207
  id: uuidv4(),
356
208
  kind: "mutation",
@@ -365,7 +217,6 @@ export class Reactor {
365
217
  errorHistory: [],
366
218
  meta,
367
219
  };
368
- // Create job info and register with tracker
369
220
  const jobInfo = {
370
221
  id: job.id,
371
222
  status: JobStatus.PENDING,
@@ -379,13 +230,9 @@ export class Reactor {
379
230
  };
380
231
  this.jobTracker.registerJob(jobInfo);
381
232
  this.emitJobPending(jobInfo.id, meta);
382
- // Enqueue the job
383
233
  await this.queue.enqueue(job);
384
234
  return jobInfo;
385
235
  }
386
- /**
387
- * Deletes a document
388
- */
389
236
  async deleteDocument(id, signer, signal, meta) {
390
237
  this.logger.verbose("deleteDocument(@id)", id);
391
238
  const createdAtUtcIso = new Date().toISOString();
@@ -393,7 +240,6 @@ export class Reactor {
393
240
  throw new AbortError();
394
241
  }
395
242
  let action = deleteDocumentAction(id);
396
- // Sign action if signer is provided
397
243
  if (signer) {
398
244
  action = await signAction(action, signer, signal);
399
245
  }
@@ -427,18 +273,13 @@ export class Reactor {
427
273
  await this.queue.enqueue(job);
428
274
  return jobInfo;
429
275
  }
430
- /**
431
- * Applies a list of actions to a document
432
- */
433
276
  async execute(docId, branch, actions, signal, meta) {
434
277
  this.logger.verbose("execute(@docId, @branch, @actions)", docId, branch, actions);
435
278
  if (signal?.aborted) {
436
279
  throw new AbortError();
437
280
  }
438
281
  const createdAtUtcIso = new Date().toISOString();
439
- // Determine scope from first action (all actions should have the same scope)
440
- const scope = actions.length > 0 ? actions[0].scope || "global" : "global";
441
- // Create a single job with all actions
282
+ const scope = getSharedActionScope(actions);
442
283
  const job = {
443
284
  id: uuidv4(),
444
285
  kind: "mutation",
@@ -453,7 +294,6 @@ export class Reactor {
453
294
  errorHistory: [],
454
295
  meta,
455
296
  };
456
- // Create job info and register with tracker
457
297
  const jobInfo = {
458
298
  id: job.id,
459
299
  status: JobStatus.PENDING,
@@ -467,18 +307,12 @@ export class Reactor {
467
307
  };
468
308
  this.jobTracker.registerJob(jobInfo);
469
309
  this.emitJobPending(jobInfo.id, meta);
470
- // Enqueue the job
471
310
  await this.queue.enqueue(job);
472
311
  if (signal?.aborted) {
473
312
  throw new AbortError();
474
313
  }
475
314
  return jobInfo;
476
315
  }
477
- /**
478
- * Imports pre-existing operations that were produced by another reactor.
479
- * This function may cause a reshuffle, which will generate additional
480
- * operations.
481
- */
482
316
  async load(docId, branch, operations, signal, meta) {
483
317
  this.logger.verbose("load(@docId, @branch, @count, @operations)", docId, branch, operations.length, operations);
484
318
  if (signal?.aborted) {
@@ -487,13 +321,7 @@ export class Reactor {
487
321
  if (operations.length === 0) {
488
322
  throw new Error("load requires at least one operation");
489
323
  }
490
- // validate the operations
491
- const scope = getSharedScope(operations);
492
- operations.forEach((operation, index) => {
493
- if (!operation.timestampUtcMs) {
494
- throw new Error(`Operation at position ${index} is missing timestampUtcMs`);
495
- }
496
- });
324
+ const scope = getSharedOperationScope(operations);
497
325
  const createdAtUtcIso = new Date().toISOString();
498
326
  const job = {
499
327
  id: uuidv4(),
@@ -528,9 +356,6 @@ export class Reactor {
528
356
  }
529
357
  return jobInfo;
530
358
  }
531
- /**
532
- * Applies multiple mutations across documents with dependency management
533
- */
534
359
  async executeBatch(request, signal, meta) {
535
360
  this.logger.verbose("executeBatch(@count jobs)", request.jobs.length);
536
361
  if (signal?.aborted) {
@@ -611,39 +436,28 @@ export class Reactor {
611
436
  };
612
437
  return result;
613
438
  }
614
- /**
615
- * Adds multiple documents as children to another
616
- */
617
439
  async addChildren(parentId, documentIds, branch = "main", signer, signal) {
618
440
  this.logger.verbose("addChildren(@parentId, @count children, @branch)", parentId, documentIds.length, branch);
619
441
  if (signal?.aborted) {
620
442
  throw new AbortError();
621
443
  }
622
444
  let actions = documentIds.map((childId) => addRelationshipAction(parentId, childId, "child"));
623
- // Sign actions if signer is provided
624
445
  if (signer) {
625
446
  actions = await signActions(actions, signer, signal);
626
447
  }
627
448
  return await this.execute(parentId, branch, actions, signal);
628
449
  }
629
- /**
630
- * Removes multiple documents as children from another
631
- */
632
450
  async removeChildren(parentId, documentIds, branch = "main", signer, signal) {
633
451
  this.logger.verbose("removeChildren(@parentId, @count children, @branch)", parentId, documentIds.length, branch);
634
452
  if (signal?.aborted) {
635
453
  throw new AbortError();
636
454
  }
637
455
  let actions = documentIds.map((childId) => removeRelationshipAction(parentId, childId, "child"));
638
- // Sign actions if signer is provided
639
456
  if (signer) {
640
457
  actions = await signActions(actions, signer, signal);
641
458
  }
642
459
  return await this.execute(parentId, branch, actions, signal);
643
460
  }
644
- /**
645
- * Retrieves the status of a job
646
- */
647
461
  getJobStatus(jobId, signal) {
648
462
  this.logger.verbose("getJobStatus(@jobId)", jobId);
649
463
  if (signal?.aborted) {
@@ -651,7 +465,6 @@ export class Reactor {
651
465
  }
652
466
  const jobInfo = this.jobTracker.getJobStatus(jobId);
653
467
  if (!jobInfo) {
654
- // Job not found - return FAILED status with appropriate error
655
468
  const now = new Date().toISOString();
656
469
  return Promise.resolve({
657
470
  id: jobId,
@@ -668,327 +481,34 @@ export class Reactor {
668
481
  }
669
482
  return Promise.resolve(jobInfo);
670
483
  }
671
- /**
672
- * Finds documents by their IDs
673
- */
674
484
  async findByIds(ids, view, paging, consistencyToken, signal) {
675
485
  this.logger.verbose("findByIds(@count ids)", ids.length);
676
- if (consistencyToken) {
677
- await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
678
- }
679
- if (this.features.legacyStorageEnabled) {
680
- const documents = [];
681
- // Fetch each document by ID using storage directly
682
- for (const id of ids) {
683
- if (signal?.aborted) {
684
- throw new AbortError();
685
- }
686
- let document;
687
- try {
688
- document = await this.documentStorage.get(id, consistencyToken, signal);
689
- }
690
- catch {
691
- // Skip documents that don't exist or can't be accessed
692
- // This matches the behavior expected from a search operation
693
- continue;
694
- }
695
- // Apply view filter - This will be removed when we pass the viewfilter along
696
- // to the underlying store, but is here now for the interface.
697
- for (const scope in document.state) {
698
- if (!matchesScope(view, scope)) {
699
- delete document.state[scope];
700
- }
701
- }
702
- documents.push(document);
703
- }
704
- if (signal?.aborted) {
705
- throw new AbortError();
706
- }
707
- // Apply paging
708
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
709
- const limit = paging?.limit || documents.length;
710
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
711
- // Create paged results
712
- const hasMore = startIndex + limit < documents.length;
713
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
714
- return {
715
- results: pagedDocuments,
716
- options: paging || { cursor: "0", limit: documents.length },
717
- nextCursor,
718
- next: hasMore
719
- ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, consistencyToken, signal)
720
- : undefined,
721
- };
722
- }
723
- else {
724
- const documents = [];
725
- // Fetch each document by ID using documentView
726
- for (const id of ids) {
727
- if (signal?.aborted) {
728
- throw new AbortError();
729
- }
730
- try {
731
- const document = await this.documentView.get(id, view, undefined, signal);
732
- documents.push(document);
733
- }
734
- catch {
735
- // Skip documents that don't exist or can't be accessed
736
- continue;
737
- }
738
- }
739
- if (signal?.aborted) {
740
- throw new AbortError();
741
- }
742
- // Apply paging
743
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
744
- const limit = paging?.limit || documents.length;
745
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
746
- // Create paged results
747
- const hasMore = startIndex + limit < documents.length;
748
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
749
- return {
750
- results: pagedDocuments,
751
- options: paging || { cursor: "0", limit: documents.length },
752
- nextCursor,
753
- next: hasMore
754
- ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, consistencyToken, signal)
755
- : undefined,
756
- };
757
- }
486
+ const startIndex = paging?.cursor ? parseInt(paging.cursor) || 0 : 0;
487
+ const limit = paging?.limit || ids.length;
488
+ const pagedIds = ids.slice(startIndex, startIndex + limit);
489
+ const results = await this.documentView.getMany(pagedIds, view, consistencyToken, signal);
490
+ const hasMore = startIndex + limit < ids.length;
491
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
492
+ return {
493
+ results,
494
+ options: paging || { cursor: "0", limit: ids.length },
495
+ nextCursor,
496
+ };
758
497
  }
759
- /**
760
- * Finds documents by their slugs
761
- */
762
498
  async findBySlugs(slugs, view, paging, consistencyToken, signal) {
763
499
  this.logger.verbose("findBySlugs(@count slugs)", slugs.length);
764
- if (consistencyToken) {
765
- await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
766
- }
767
- if (this.features.legacyStorageEnabled) {
768
- const documents = [];
769
- // Use storage to resolve slugs to IDs
770
- let ids;
771
- try {
772
- ids = await this.documentStorage.resolveIds(slugs, consistencyToken, signal);
773
- }
774
- catch {
775
- // If slug resolution fails, return empty results
776
- // This matches the behavior expected from a search operation
777
- ids = [];
778
- }
779
- // Fetch each document by resolved ID
780
- for (const id of ids) {
781
- if (signal?.aborted) {
782
- throw new AbortError();
783
- }
784
- let document;
785
- try {
786
- document = await this.documentStorage.get(id, consistencyToken, signal);
787
- }
788
- catch {
789
- // Skip documents that don't exist or can't be accessed
790
- continue;
791
- }
792
- // Apply view filter - This will be removed when we pass the viewfilter along
793
- // to the underlying store, but is here now for the interface.
794
- for (const scope in document.state) {
795
- if (!matchesScope(view, scope)) {
796
- delete document.state[scope];
797
- }
798
- }
799
- documents.push(document);
800
- }
801
- if (signal?.aborted) {
802
- throw new AbortError();
803
- }
804
- // Apply paging - this will be removed when we pass the paging along
805
- // to the underlying store, but is here now for the interface.
806
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
807
- const limit = paging?.limit || documents.length;
808
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
809
- // Create paged results
810
- const hasMore = startIndex + limit < documents.length;
811
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
812
- return {
813
- results: pagedDocuments,
814
- options: paging || { cursor: "0", limit: documents.length },
815
- nextCursor,
816
- next: hasMore
817
- ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, consistencyToken, signal)
818
- : undefined,
819
- };
820
- }
821
- else {
822
- const documents = [];
823
- // Resolve each slug to a document ID
824
- const documentIds = [];
825
- for (const slug of slugs) {
826
- if (signal?.aborted) {
827
- throw new AbortError();
828
- }
829
- const documentId = await this.documentView.resolveSlug(slug, view, undefined, signal);
830
- if (documentId) {
831
- documentIds.push(documentId);
832
- }
833
- }
834
- // Fetch each document
835
- for (const documentId of documentIds) {
836
- if (signal?.aborted) {
837
- throw new AbortError();
838
- }
839
- try {
840
- const document = await this.documentView.get(documentId, view, undefined, signal);
841
- documents.push(document);
842
- }
843
- catch {
844
- // Skip documents that don't exist or can't be accessed
845
- continue;
846
- }
847
- }
848
- if (signal?.aborted) {
849
- throw new AbortError();
850
- }
851
- // Apply paging
852
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
853
- const limit = paging?.limit || documents.length;
854
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
855
- // Create paged results
856
- const hasMore = startIndex + limit < documents.length;
857
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
858
- return {
859
- results: pagedDocuments,
860
- options: paging || { cursor: "0", limit: documents.length },
861
- nextCursor,
862
- next: hasMore
863
- ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, consistencyToken, signal)
864
- : undefined,
865
- };
866
- }
500
+ const ids = await this.documentView.resolveSlugs(slugs, view, consistencyToken, signal);
501
+ return await this.findByIds(ids, view, paging, consistencyToken, signal);
867
502
  }
868
- /**
869
- * Finds documents by parent ID
870
- */
871
- async findByParentId(parentId, view, paging, signal) {
503
+ async findByParentId(parentId, view, paging, consistencyToken, signal) {
872
504
  this.logger.verbose("findByParentId(@parentId)", parentId);
873
- // Get child relationships from indexer
874
- const relationships = await this._documentIndexer.getOutgoing(parentId, ["child"], undefined, signal);
875
- if (signal?.aborted) {
876
- throw new AbortError();
877
- }
878
- const documents = [];
879
- // Fetch each child document using the appropriate storage method
880
- for (const relationship of relationships) {
881
- if (signal?.aborted) {
882
- throw new AbortError();
883
- }
884
- try {
885
- let document;
886
- if (this.features.legacyStorageEnabled) {
887
- document = await this.documentStorage.get(relationship.targetId);
888
- // Apply view filter for legacy storage
889
- for (const scope in document.state) {
890
- if (!matchesScope(view, scope)) {
891
- delete document.state[scope];
892
- }
893
- }
894
- }
895
- else {
896
- document = await this.documentView.get(relationship.targetId, view, undefined, signal);
897
- }
898
- documents.push(document);
899
- }
900
- catch {
901
- // Skip documents that don't exist or can't be accessed
902
- continue;
903
- }
904
- }
905
- if (signal?.aborted) {
906
- throw new AbortError();
907
- }
908
- // Apply paging
909
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
910
- const limit = paging?.limit || documents.length;
911
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
912
- // Create paged results
913
- const hasMore = startIndex + limit < documents.length;
914
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
915
- return {
916
- results: pagedDocuments,
917
- options: paging || { cursor: "0", limit: documents.length },
918
- nextCursor,
919
- next: hasMore
920
- ? () => this.findByParentId(parentId, view, { cursor: nextCursor, limit }, signal)
921
- : undefined,
922
- };
505
+ const relationships = await this.documentIndexer.getOutgoing(parentId, ["child"], paging, consistencyToken, signal);
506
+ const ids = relationships.results.map((rel) => rel.targetId);
507
+ return await this.findByIds(ids, view, paging, undefined, signal);
923
508
  }
924
- /**
925
- * Finds documents by type
926
- */
927
509
  async findByType(type, view, paging, consistencyToken, signal) {
928
510
  this.logger.verbose("findByType(@type)", type);
929
- if (consistencyToken) {
930
- await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
931
- }
932
- if (this.features.legacyStorageEnabled) {
933
- const documents = [];
934
- // Use storage's findByType method directly
935
- const cursor = paging?.cursor;
936
- const limit = paging?.limit || 100;
937
- // Get document IDs of the specified type
938
- const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor, consistencyToken, signal);
939
- if (signal?.aborted) {
940
- throw new AbortError();
941
- }
942
- // Fetch each document by its ID
943
- for (const documentId of documentIds) {
944
- if (signal?.aborted) {
945
- throw new AbortError();
946
- }
947
- let document;
948
- try {
949
- document = await this.documentStorage.get(documentId, consistencyToken, signal);
950
- }
951
- catch {
952
- // Skip documents that can't be retrieved
953
- continue;
954
- }
955
- // Apply view filter
956
- for (const scope in document.state) {
957
- if (!matchesScope(view, scope)) {
958
- delete document.state[scope];
959
- }
960
- }
961
- documents.push(document);
962
- }
963
- if (signal?.aborted) {
964
- throw new AbortError();
965
- }
966
- // Results are already paged from the storage layer
967
- return {
968
- results: documents,
969
- options: paging || { cursor: cursor || "0", limit },
970
- nextCursor,
971
- next: nextCursor
972
- ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, consistencyToken, signal)
973
- : undefined,
974
- };
975
- }
976
- else {
977
- const result = await this.documentView.findByType(type, view, paging, consistencyToken, signal);
978
- if (signal?.aborted) {
979
- throw new AbortError();
980
- }
981
- const cursor = paging?.cursor;
982
- const limit = paging?.limit || 100;
983
- return {
984
- results: result.items,
985
- options: paging || { cursor: cursor || "0", limit },
986
- nextCursor: result.nextCursor,
987
- next: result.nextCursor
988
- ? async () => this.findByType(type, view, { cursor: result.nextCursor, limit }, consistencyToken, signal)
989
- : undefined,
990
- };
991
- }
511
+ return await this.documentView.findByType(type, view, paging, consistencyToken, signal);
992
512
  }
993
513
  emitJobPending(jobId, meta) {
994
514
  const event = {