@powerhousedao/reactor 5.0.1-staging.1 → 5.0.1-staging.11

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 (141) hide show
  1. package/dist/src/cache/kysely-write-cache.d.ts +4 -3
  2. package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
  3. package/dist/src/cache/kysely-write-cache.js +12 -12
  4. package/dist/src/cache/kysely-write-cache.js.map +1 -1
  5. package/dist/src/client/reactor-client.d.ts +2 -2
  6. package/dist/src/client/reactor-client.d.ts.map +1 -1
  7. package/dist/src/client/reactor-client.js +8 -8
  8. package/dist/src/client/reactor-client.js.map +1 -1
  9. package/dist/src/client/types.d.ts +4 -4
  10. package/dist/src/client/types.d.ts.map +1 -1
  11. package/dist/src/core/reactor-builder.d.ts +32 -0
  12. package/dist/src/core/reactor-builder.d.ts.map +1 -0
  13. package/dist/src/core/reactor-builder.js +120 -0
  14. package/dist/src/core/reactor-builder.js.map +1 -0
  15. package/dist/src/core/reactor.d.ts +25 -10
  16. package/dist/src/core/reactor.d.ts.map +1 -1
  17. package/dist/src/core/reactor.js +627 -284
  18. package/dist/src/core/reactor.js.map +1 -1
  19. package/dist/src/core/types.d.ts +64 -7
  20. package/dist/src/core/types.d.ts.map +1 -1
  21. package/dist/src/core/utils.d.ts +46 -2
  22. package/dist/src/core/utils.d.ts.map +1 -1
  23. package/dist/src/core/utils.js +119 -0
  24. package/dist/src/core/utils.js.map +1 -1
  25. package/dist/src/executor/simple-job-executor-manager.d.ts +1 -0
  26. package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
  27. package/dist/src/executor/simple-job-executor-manager.js +41 -17
  28. package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
  29. package/dist/src/executor/simple-job-executor.d.ts +14 -2
  30. package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
  31. package/dist/src/executor/simple-job-executor.js +383 -249
  32. package/dist/src/executor/simple-job-executor.js.map +1 -1
  33. package/dist/src/executor/types.d.ts +2 -0
  34. package/dist/src/executor/types.d.ts.map +1 -1
  35. package/dist/src/executor/types.js.map +1 -1
  36. package/dist/src/executor/util.d.ts +18 -0
  37. package/dist/src/executor/util.d.ts.map +1 -1
  38. package/dist/src/executor/util.js +42 -1
  39. package/dist/src/executor/util.js.map +1 -1
  40. package/dist/src/index.d.ts +9 -6
  41. package/dist/src/index.d.ts.map +1 -1
  42. package/dist/src/index.js +6 -2
  43. package/dist/src/index.js.map +1 -1
  44. package/dist/src/job-tracker/in-memory-job-tracker.d.ts +3 -2
  45. package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
  46. package/dist/src/job-tracker/in-memory-job-tracker.js +7 -1
  47. package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
  48. package/dist/src/job-tracker/interfaces.d.ts +5 -4
  49. package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
  50. package/dist/src/queue/interfaces.d.ts +5 -4
  51. package/dist/src/queue/interfaces.d.ts.map +1 -1
  52. package/dist/src/queue/job-execution-handle.d.ts +3 -2
  53. package/dist/src/queue/job-execution-handle.d.ts.map +1 -1
  54. package/dist/src/queue/job-execution-handle.js +2 -2
  55. package/dist/src/queue/job-execution-handle.js.map +1 -1
  56. package/dist/src/queue/queue.d.ts +4 -2
  57. package/dist/src/queue/queue.d.ts.map +1 -1
  58. package/dist/src/queue/queue.js +16 -4
  59. package/dist/src/queue/queue.js.map +1 -1
  60. package/dist/src/queue/types.d.ts +12 -6
  61. package/dist/src/queue/types.d.ts.map +1 -1
  62. package/dist/src/queue/types.js.map +1 -1
  63. package/dist/src/read-models/document-view.d.ts +10 -6
  64. package/dist/src/read-models/document-view.d.ts.map +1 -1
  65. package/dist/src/read-models/document-view.js +116 -113
  66. package/dist/src/read-models/document-view.js.map +1 -1
  67. package/dist/src/read-models/types.d.ts +3 -3
  68. package/dist/src/read-models/types.d.ts.map +1 -1
  69. package/dist/src/shared/consistency-tracker.d.ts +48 -0
  70. package/dist/src/shared/consistency-tracker.d.ts.map +1 -0
  71. package/dist/src/shared/consistency-tracker.js +123 -0
  72. package/dist/src/shared/consistency-tracker.js.map +1 -0
  73. package/dist/src/shared/types.d.ts +35 -1
  74. package/dist/src/shared/types.d.ts.map +1 -1
  75. package/dist/src/shared/types.js.map +1 -1
  76. package/dist/src/storage/interfaces.d.ts +148 -2
  77. package/dist/src/storage/interfaces.d.ts.map +1 -1
  78. package/dist/src/storage/interfaces.js.map +1 -1
  79. package/dist/src/storage/kysely/document-indexer.d.ts +28 -0
  80. package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -0
  81. package/dist/src/storage/kysely/document-indexer.js +350 -0
  82. package/dist/src/storage/kysely/document-indexer.js.map +1 -0
  83. package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -1
  84. package/dist/src/storage/kysely/keyframe-store.js +6 -13
  85. package/dist/src/storage/kysely/keyframe-store.js.map +1 -1
  86. package/dist/src/storage/kysely/store.js +1 -1
  87. package/dist/src/storage/kysely/store.js.map +1 -1
  88. package/dist/src/storage/kysely/types.d.ts +35 -2
  89. package/dist/src/storage/kysely/types.d.ts.map +1 -1
  90. package/dist/src/storage/migrations/001_create_operation_table.d.ts +3 -0
  91. package/dist/src/storage/migrations/001_create_operation_table.d.ts.map +1 -0
  92. package/dist/src/storage/migrations/001_create_operation_table.js +40 -0
  93. package/dist/src/storage/migrations/001_create_operation_table.js.map +1 -0
  94. package/dist/src/storage/migrations/002_create_keyframe_table.d.ts +3 -0
  95. package/dist/src/storage/migrations/002_create_keyframe_table.d.ts.map +1 -0
  96. package/dist/src/storage/migrations/002_create_keyframe_table.js +27 -0
  97. package/dist/src/storage/migrations/002_create_keyframe_table.js.map +1 -0
  98. package/dist/src/storage/migrations/003_create_document_table.d.ts +3 -0
  99. package/dist/src/storage/migrations/003_create_document_table.d.ts.map +1 -0
  100. package/dist/src/storage/migrations/003_create_document_table.js +10 -0
  101. package/dist/src/storage/migrations/003_create_document_table.js.map +1 -0
  102. package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts +3 -0
  103. package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts.map +1 -0
  104. package/dist/src/storage/migrations/004_create_document_relationship_table.js +35 -0
  105. package/dist/src/storage/migrations/004_create_document_relationship_table.js.map +1 -0
  106. package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts +3 -0
  107. package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts.map +1 -0
  108. package/dist/src/storage/migrations/005_create_indexer_state_table.js +10 -0
  109. package/dist/src/storage/migrations/005_create_indexer_state_table.js.map +1 -0
  110. package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts +3 -0
  111. package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts.map +1 -0
  112. package/dist/src/storage/migrations/006_create_document_snapshot_table.js +49 -0
  113. package/dist/src/storage/migrations/006_create_document_snapshot_table.js.map +1 -0
  114. package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts +3 -0
  115. package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts.map +1 -0
  116. package/dist/src/storage/migrations/007_create_slug_mapping_table.js +24 -0
  117. package/dist/src/storage/migrations/007_create_slug_mapping_table.js.map +1 -0
  118. package/dist/src/storage/migrations/008_create_view_state_table.d.ts +3 -0
  119. package/dist/src/storage/migrations/008_create_view_state_table.d.ts.map +1 -0
  120. package/dist/src/storage/migrations/008_create_view_state_table.js +9 -0
  121. package/dist/src/storage/migrations/008_create_view_state_table.js.map +1 -0
  122. package/dist/src/storage/migrations/index.d.ts +3 -0
  123. package/dist/src/storage/migrations/index.d.ts.map +1 -0
  124. package/dist/src/storage/migrations/index.js +3 -0
  125. package/dist/src/storage/migrations/index.js.map +1 -0
  126. package/dist/src/storage/migrations/migrator.d.ts +5 -0
  127. package/dist/src/storage/migrations/migrator.d.ts.map +1 -0
  128. package/dist/src/storage/migrations/migrator.js +51 -0
  129. package/dist/src/storage/migrations/migrator.js.map +1 -0
  130. package/dist/src/storage/migrations/run-migrations.d.ts +2 -0
  131. package/dist/src/storage/migrations/run-migrations.d.ts.map +1 -0
  132. package/dist/src/storage/migrations/run-migrations.js +58 -0
  133. package/dist/src/storage/migrations/run-migrations.js.map +1 -0
  134. package/dist/src/storage/migrations/types.d.ts +9 -0
  135. package/dist/src/storage/migrations/types.d.ts.map +1 -0
  136. package/dist/src/storage/migrations/types.js +2 -0
  137. package/dist/src/storage/migrations/types.js.map +1 -0
  138. package/dist/src/storage/txn.d.ts.map +1 -1
  139. package/dist/src/storage/txn.js +2 -0
  140. package/dist/src/storage/txn.js.map +1 -1
  141. package/package.json +8 -5
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
3
3
  import { createMutableShutdownStatus } from "../shared/factories.js";
4
4
  import { JobStatus } from "../shared/types.js";
5
5
  import { matchesScope } from "../shared/utils.js";
6
- import { filterByParentId, filterByType } from "./utils.js";
6
+ import { filterByParentId, filterByType, getSharedScope, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
7
7
  /**
8
8
  * This class implements the IReactor interface and serves as the main entry point
9
9
  * for the new Reactor architecture.
@@ -16,13 +16,21 @@ export class Reactor {
16
16
  queue;
17
17
  jobTracker;
18
18
  readModelCoordinator;
19
- constructor(driveServer, documentStorage, queue, jobTracker, readModelCoordinator) {
19
+ features;
20
+ documentView;
21
+ documentIndexer;
22
+ operationStore;
23
+ constructor(driveServer, documentStorage, queue, jobTracker, readModelCoordinator, features, documentView, documentIndexer, operationStore) {
20
24
  // Store required dependencies
21
25
  this.driveServer = driveServer;
22
26
  this.documentStorage = documentStorage;
23
27
  this.queue = queue;
24
28
  this.jobTracker = jobTracker;
25
29
  this.readModelCoordinator = readModelCoordinator;
30
+ this.features = features;
31
+ this.documentView = documentView;
32
+ this.documentIndexer = documentIndexer;
33
+ this.operationStore = operationStore;
26
34
  // Start the read model coordinator
27
35
  this.readModelCoordinator.start();
28
36
  // Create mutable shutdown status using factory method
@@ -72,87 +80,153 @@ export class Reactor {
72
80
  /**
73
81
  * Retrieves a specific PHDocument by id
74
82
  */
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
+ async get(id, view, consistencyToken, signal) {
84
+ if (this.features.legacyStorageEnabled) {
85
+ const document = await this.documentStorage.get(id);
86
+ if (signal?.aborted) {
87
+ throw new AbortError();
88
+ }
89
+ const childIds = await this.documentStorage.getChildren(id);
90
+ if (signal?.aborted) {
91
+ throw new AbortError();
92
+ }
93
+ for (const scope in document.state) {
94
+ if (!matchesScope(view, scope)) {
95
+ delete document.state[scope];
96
+ }
97
+ }
98
+ return {
99
+ document,
100
+ childIds,
101
+ };
83
102
  }
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];
103
+ else {
104
+ const document = await this.documentView.get(id, view, consistencyToken, signal);
105
+ if (signal?.aborted) {
106
+ throw new AbortError();
107
+ }
108
+ const relationships = await this.documentIndexer.getOutgoing(id, ["child"], consistencyToken, signal);
109
+ if (signal?.aborted) {
110
+ throw new AbortError();
89
111
  }
112
+ const childIds = relationships.map((rel) => rel.targetId);
113
+ return {
114
+ document,
115
+ childIds,
116
+ };
90
117
  }
91
- return {
92
- document,
93
- childIds,
94
- };
95
118
  }
96
119
  /**
97
120
  * Retrieves a specific PHDocument by slug
98
121
  */
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")) {
122
+ async getBySlug(slug, view, consistencyToken, signal) {
123
+ if (this.features.legacyStorageEnabled) {
124
+ let ids;
125
+ try {
126
+ ids = await this.documentStorage.resolveIds([slug], signal);
127
+ }
128
+ catch (error) {
129
+ if (error instanceof Error && error.message.includes("not found")) {
130
+ throw new Error(`Document not found with slug: ${slug}`);
131
+ }
132
+ throw error;
133
+ }
134
+ if (ids.length === 0 || !ids[0]) {
108
135
  throw new Error(`Document not found with slug: ${slug}`);
109
136
  }
110
- throw error;
137
+ return await this.get(ids[0], view, consistencyToken, signal);
111
138
  }
112
- if (ids.length === 0 || !ids[0]) {
113
- throw new Error(`Document not found with slug: ${slug}`);
139
+ else {
140
+ const documentId = await this.documentView.resolveSlug(slug, view, consistencyToken, signal);
141
+ if (!documentId) {
142
+ throw new Error(`Document not found with slug: ${slug}`);
143
+ }
144
+ return await this.get(documentId, view, consistencyToken, signal);
114
145
  }
115
- // Now get the document by its resolved ID
116
- return await this.get(ids[0], view, signal);
117
146
  }
118
147
  /**
119
148
  * Retrieves the operations for a document
120
149
  */
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();
150
+ async getOperations(documentId, view, paging, consistencyToken, signal) {
151
+ if (this.features.legacyStorageEnabled) {
152
+ // Use storage directly to get the document
153
+ const document = await this.documentStorage.get(documentId);
154
+ if (signal?.aborted) {
155
+ throw new AbortError();
156
+ }
157
+ const operations = document.operations;
158
+ const result = {};
159
+ // apply view filter, per scope -- this will be removed when we pass the viewfilter along
160
+ // to the underlying store, but is here now for the interface.
161
+ for (const scope in operations) {
162
+ if (matchesScope(view, scope)) {
163
+ const scopeOperations = operations[scope];
164
+ // apply paging too
165
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
166
+ const limit = paging?.limit || scopeOperations.length;
167
+ const pagedOperations = scopeOperations.slice(startIndex, startIndex + limit);
168
+ result[scope] = {
169
+ results: pagedOperations,
170
+ options: { cursor: String(startIndex + limit), limit },
171
+ };
172
+ }
173
+ }
174
+ return Promise.resolve(result);
126
175
  }
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);
176
+ else {
177
+ // Use operation store to get operations
178
+ const branch = view?.branch || "main";
179
+ // Get all scopes for this document
180
+ const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
181
+ if (signal?.aborted) {
182
+ throw new AbortError();
183
+ }
184
+ const allScopes = Object.keys(revisions.revision);
185
+ const result = {};
186
+ // Filter scopes based on view filter and query operations for each
187
+ for (const scope of allScopes) {
188
+ if (!matchesScope(view, scope)) {
189
+ continue;
190
+ }
191
+ if (signal?.aborted) {
192
+ throw new AbortError();
193
+ }
194
+ // Get operations for this scope
195
+ const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, paging, signal);
196
+ // Transform to Reactor's PagedResults format
197
+ const currentCursor = paging?.cursor ? parseInt(paging.cursor) : 0;
198
+ const currentLimit = paging?.limit || 100;
138
199
  result[scope] = {
139
- results: pagedOperations,
140
- options: { cursor: String(startIndex + limit), limit },
200
+ results: scopeResult.items,
201
+ options: {
202
+ cursor: scopeResult.nextCursor || String(currentCursor),
203
+ limit: currentLimit,
204
+ },
205
+ nextCursor: scopeResult.nextCursor,
206
+ next: scopeResult.hasMore
207
+ ? async () => {
208
+ const nextPage = await this.getOperations(documentId, view, {
209
+ cursor: scopeResult.nextCursor,
210
+ limit: currentLimit,
211
+ }, consistencyToken, signal);
212
+ return nextPage[scope];
213
+ }
214
+ : undefined,
141
215
  };
142
216
  }
217
+ return result;
143
218
  }
144
- return Promise.resolve(result);
145
219
  }
146
220
  /**
147
221
  * Filters documents by criteria and returns a list of them
148
222
  */
149
- async find(search, view, paging, signal) {
223
+ async find(search, view, paging, consistencyToken, signal) {
150
224
  let results;
151
225
  if (search.ids) {
152
226
  if (search.slugs && search.slugs.length > 0) {
153
227
  throw new Error("Cannot use both ids and slugs in the same search");
154
228
  }
155
- results = await this.findByIds(search.ids, view, paging, signal);
229
+ results = await this.findByIds(search.ids, view, paging, consistencyToken, signal);
156
230
  if (search.parentId) {
157
231
  results = filterByParentId(results, search.parentId);
158
232
  }
@@ -161,7 +235,7 @@ export class Reactor {
161
235
  }
162
236
  }
163
237
  else if (search.slugs) {
164
- results = await this.findBySlugs(search.slugs, view, paging, signal);
238
+ results = await this.findBySlugs(search.slugs, view, paging, consistencyToken, signal);
165
239
  if (search.parentId) {
166
240
  results = filterByParentId(results, search.parentId);
167
241
  }
@@ -176,7 +250,7 @@ export class Reactor {
176
250
  }
177
251
  }
178
252
  else if (search.type) {
179
- results = await this.findByType(search.type, view, paging, signal);
253
+ results = await this.findByType(search.type, view, paging, consistencyToken, signal);
180
254
  }
181
255
  else {
182
256
  throw new Error("No search criteria provided");
@@ -238,10 +312,12 @@ export class Reactor {
238
312
  // Create a single job with both CREATE_DOCUMENT and UPGRADE_DOCUMENT actions
239
313
  const job = {
240
314
  id: uuidv4(),
315
+ kind: "mutation",
241
316
  documentId: document.header.id,
242
317
  scope: "document",
243
318
  branch: "main",
244
319
  actions: [createAction, upgradeAction],
320
+ operations: [],
245
321
  createdAt: new Date().toISOString(),
246
322
  queueHint: [],
247
323
  maxRetries: 3,
@@ -252,6 +328,11 @@ export class Reactor {
252
328
  id: job.id,
253
329
  status: JobStatus.PENDING,
254
330
  createdAtUtcIso,
331
+ consistencyToken: {
332
+ version: 1,
333
+ createdAtUtcIso,
334
+ coordinates: [],
335
+ },
255
336
  };
256
337
  this.jobTracker.registerJob(jobInfo);
257
338
  // Enqueue the job
@@ -279,10 +360,12 @@ export class Reactor {
279
360
  };
280
361
  const job = {
281
362
  id: uuidv4(),
363
+ kind: "mutation",
282
364
  documentId: id,
283
365
  scope: "document",
284
366
  branch: "main",
285
367
  actions: [action],
368
+ operations: [],
286
369
  createdAt: new Date().toISOString(),
287
370
  queueHint: [],
288
371
  maxRetries: 3,
@@ -292,6 +375,11 @@ export class Reactor {
292
375
  id: job.id,
293
376
  status: JobStatus.PENDING,
294
377
  createdAtUtcIso,
378
+ consistencyToken: {
379
+ version: 1,
380
+ createdAtUtcIso,
381
+ coordinates: [],
382
+ },
295
383
  };
296
384
  this.jobTracker.registerJob(jobInfo);
297
385
  await this.queue.enqueue(job);
@@ -300,17 +388,22 @@ export class Reactor {
300
388
  /**
301
389
  * Applies a list of actions to a document
302
390
  */
303
- async mutate(id, actions) {
391
+ async mutate(docId, branch, actions, signal) {
392
+ if (signal?.aborted) {
393
+ throw new AbortError();
394
+ }
304
395
  const createdAtUtcIso = new Date().toISOString();
305
396
  // Determine scope from first action (all actions should have the same scope)
306
397
  const scope = actions.length > 0 ? actions[0].scope || "global" : "global";
307
398
  // Create a single job with all actions
308
399
  const job = {
309
400
  id: uuidv4(),
310
- documentId: id,
401
+ kind: "mutation",
402
+ documentId: docId,
311
403
  scope: scope,
312
- branch: "main", // Default to main branch
404
+ branch: branch,
313
405
  actions: actions,
406
+ operations: [],
314
407
  createdAt: new Date().toISOString(),
315
408
  queueHint: [],
316
409
  maxRetries: 3,
@@ -321,103 +414,193 @@ export class Reactor {
321
414
  id: job.id,
322
415
  status: JobStatus.PENDING,
323
416
  createdAtUtcIso,
417
+ consistencyToken: {
418
+ version: 1,
419
+ createdAtUtcIso,
420
+ coordinates: [],
421
+ },
324
422
  };
325
423
  this.jobTracker.registerJob(jobInfo);
326
424
  // Enqueue the job
327
425
  await this.queue.enqueue(job);
426
+ if (signal?.aborted) {
427
+ throw new AbortError();
428
+ }
328
429
  return jobInfo;
329
430
  }
330
431
  /**
331
- * Adds multiple documents as children to another
432
+ * Imports pre-existing operations that were produced by another reactor.
433
+ * This function may cause a reshuffle, which will generate additional
434
+ * operations.
332
435
  */
333
- async addChildren(parentId, documentIds, view, signal) {
334
- const createdAtUtcIso = new Date().toISOString();
335
- const jobId = uuidv4();
336
- // Check abort signal before starting
436
+ async load(docId, branch, operations, signal) {
337
437
  if (signal?.aborted) {
338
438
  throw new AbortError();
339
439
  }
340
- // TODO: Implement when drive server supports hierarchical documents
341
- // For now, this is a placeholder implementation
342
- // Verify parent exists
343
- try {
344
- await this.driveServer.getDocument(parentId);
440
+ if (operations.length === 0) {
441
+ throw new Error("load requires at least one operation");
345
442
  }
346
- catch (error) {
347
- return {
348
- id: jobId,
349
- status: JobStatus.FAILED,
443
+ // validate the operations
444
+ const scope = getSharedScope(operations);
445
+ operations.forEach((operation, index) => {
446
+ if (!operation.timestampUtcMs) {
447
+ throw new Error(`Operation at position ${index} is missing timestampUtcMs`);
448
+ }
449
+ if (operation.hash === undefined || operation.hash === null) {
450
+ throw new Error(`Operation at position ${index} is missing hash`);
451
+ }
452
+ });
453
+ const createdAtUtcIso = new Date().toISOString();
454
+ const job = {
455
+ id: uuidv4(),
456
+ kind: "load",
457
+ documentId: docId,
458
+ scope,
459
+ branch,
460
+ actions: [],
461
+ operations,
462
+ createdAt: createdAtUtcIso,
463
+ queueHint: [],
464
+ maxRetries: 3,
465
+ errorHistory: [],
466
+ };
467
+ const jobInfo = {
468
+ id: job.id,
469
+ status: JobStatus.PENDING,
470
+ createdAtUtcIso,
471
+ consistencyToken: {
472
+ version: 1,
350
473
  createdAtUtcIso,
351
- error: error instanceof Error ? error.message : "Unknown error",
352
- };
474
+ coordinates: [],
475
+ },
476
+ };
477
+ this.jobTracker.registerJob(jobInfo);
478
+ await this.queue.enqueue(job);
479
+ if (signal?.aborted) {
480
+ throw new AbortError();
353
481
  }
354
- // Check abort signal after parent verification
482
+ return jobInfo;
483
+ }
484
+ /**
485
+ * Applies multiple mutations across documents with dependency management
486
+ */
487
+ async mutateBatch(request, signal) {
355
488
  if (signal?.aborted) {
356
489
  throw new AbortError();
357
490
  }
358
- // Verify all children exist
359
- for (const childId of documentIds) {
360
- try {
361
- await this.driveServer.getDocument(childId);
362
- }
363
- catch (error) {
364
- return {
365
- id: jobId,
366
- status: JobStatus.FAILED,
491
+ validateBatchRequest(request.jobs);
492
+ for (const jobPlan of request.jobs) {
493
+ validateActionScopes(jobPlan);
494
+ }
495
+ const createdAtUtcIso = new Date().toISOString();
496
+ const planKeyToJobId = new Map();
497
+ for (const jobPlan of request.jobs) {
498
+ planKeyToJobId.set(jobPlan.key, uuidv4());
499
+ }
500
+ const jobInfos = new Map();
501
+ for (const jobPlan of request.jobs) {
502
+ const jobId = planKeyToJobId.get(jobPlan.key);
503
+ const jobInfo = {
504
+ id: jobId,
505
+ status: JobStatus.PENDING,
506
+ createdAtUtcIso,
507
+ consistencyToken: {
508
+ version: 1,
367
509
  createdAtUtcIso,
368
- error: error instanceof Error ? error.message : "Unknown error",
510
+ coordinates: [],
511
+ },
512
+ };
513
+ this.jobTracker.registerJob(jobInfo);
514
+ jobInfos.set(jobPlan.key, jobInfo);
515
+ }
516
+ const sortedKeys = topologicalSort(request.jobs);
517
+ const enqueuedKeys = [];
518
+ try {
519
+ for (const key of sortedKeys) {
520
+ if (signal?.aborted) {
521
+ throw new AbortError();
522
+ }
523
+ const jobPlan = request.jobs.find((j) => j.key === key);
524
+ const jobId = planKeyToJobId.get(key);
525
+ const queueHint = jobPlan.dependsOn.map((depKey) => planKeyToJobId.get(depKey));
526
+ const job = {
527
+ id: jobId,
528
+ kind: "mutation",
529
+ documentId: jobPlan.documentId,
530
+ scope: jobPlan.scope,
531
+ branch: jobPlan.branch,
532
+ actions: jobPlan.actions,
533
+ operations: [],
534
+ createdAt: createdAtUtcIso,
535
+ queueHint,
536
+ maxRetries: 3,
537
+ errorHistory: [],
369
538
  };
539
+ await this.queue.enqueue(job);
540
+ enqueuedKeys.push(key);
370
541
  }
371
- // Check abort signal after each child verification
372
- if (signal?.aborted) {
373
- throw new AbortError();
542
+ }
543
+ catch (error) {
544
+ for (const key of enqueuedKeys) {
545
+ const jobId = planKeyToJobId.get(key);
546
+ try {
547
+ await this.queue.remove(jobId);
548
+ }
549
+ catch {
550
+ // Ignore removal errors during cleanup
551
+ }
374
552
  }
553
+ for (const jobInfo of jobInfos.values()) {
554
+ this.jobTracker.markFailed(jobInfo.id, toErrorInfo("Batch enqueue failed"));
555
+ }
556
+ throw error;
375
557
  }
376
- // TODO: Actually establish parent-child relationships
377
- // Return success job info
378
- return {
379
- id: jobId,
380
- status: JobStatus.COMPLETED,
381
- createdAtUtcIso,
382
- completedAtUtcIso: new Date().toISOString(),
558
+ const result = {
559
+ jobs: Object.fromEntries(jobInfos),
383
560
  };
561
+ return result;
384
562
  }
385
563
  /**
386
- * Removes multiple documents as children from another
564
+ * Adds multiple documents as children to another
387
565
  */
388
- async removeChildren(parentId, documentIds, view, signal) {
389
- const createdAtUtcIso = new Date().toISOString();
390
- const jobId = uuidv4();
391
- // Check abort signal before starting
566
+ async addChildren(parentId, documentIds, _view, signal) {
392
567
  if (signal?.aborted) {
393
568
  throw new AbortError();
394
569
  }
395
- // TODO: Implement when drive server supports hierarchical documents
396
- // For now, this is a placeholder implementation
397
- // Verify parent exists
398
- try {
399
- await this.driveServer.getDocument(parentId);
400
- }
401
- catch (error) {
402
- return {
403
- id: jobId,
404
- status: JobStatus.FAILED,
405
- createdAtUtcIso,
406
- error: error instanceof Error ? error.message : "Unknown error",
407
- };
408
- }
409
- // Check abort signal after parent verification
570
+ const actions = documentIds.map((childId) => ({
571
+ id: uuidv4(),
572
+ type: "ADD_RELATIONSHIP",
573
+ scope: "document",
574
+ timestampUtcMs: new Date().toISOString(),
575
+ input: {
576
+ sourceId: parentId,
577
+ targetId: childId,
578
+ relationshipType: "child",
579
+ },
580
+ }));
581
+ const branch = _view?.branch || "main";
582
+ return await this.mutate(parentId, branch, actions, signal);
583
+ }
584
+ /**
585
+ * Removes multiple documents as children from another
586
+ */
587
+ async removeChildren(parentId, documentIds, _view, signal) {
410
588
  if (signal?.aborted) {
411
589
  throw new AbortError();
412
590
  }
413
- // TODO: Actually remove parent-child relationships
414
- // Return success job info
415
- return {
416
- id: jobId,
417
- status: JobStatus.COMPLETED,
418
- createdAtUtcIso,
419
- completedAtUtcIso: new Date().toISOString(),
420
- };
591
+ const actions = documentIds.map((childId) => ({
592
+ id: uuidv4(),
593
+ type: "REMOVE_RELATIONSHIP",
594
+ scope: "document",
595
+ timestampUtcMs: new Date().toISOString(),
596
+ input: {
597
+ sourceId: parentId,
598
+ targetId: childId,
599
+ relationshipType: "child",
600
+ },
601
+ }));
602
+ const branch = _view?.branch || "main";
603
+ return await this.mutate(parentId, branch, actions, signal);
421
604
  }
422
605
  /**
423
606
  * Retrieves the status of a job
@@ -429,12 +612,18 @@ export class Reactor {
429
612
  const jobInfo = this.jobTracker.getJobStatus(jobId);
430
613
  if (!jobInfo) {
431
614
  // Job not found - return FAILED status with appropriate error
615
+ const now = new Date().toISOString();
432
616
  return Promise.resolve({
433
617
  id: jobId,
434
618
  status: JobStatus.FAILED,
435
- createdAtUtcIso: new Date().toISOString(),
436
- completedAtUtcIso: new Date().toISOString(),
437
- error: "Job not found",
619
+ createdAtUtcIso: now,
620
+ completedAtUtcIso: now,
621
+ error: toErrorInfo("Job not found"),
622
+ consistencyToken: {
623
+ version: 1,
624
+ createdAtUtcIso: now,
625
+ coordinates: [],
626
+ },
438
627
  });
439
628
  }
440
629
  return Promise.resolve(jobInfo);
@@ -442,205 +631,359 @@ export class Reactor {
442
631
  /**
443
632
  * Finds documents by their IDs
444
633
  */
445
- async findByIds(ids, view, paging, signal) {
446
- const documents = [];
447
- // Fetch each document by ID using storage directly
448
- for (const id of ids) {
634
+ async findByIds(ids, view, paging, consistencyToken, signal) {
635
+ if (consistencyToken) {
636
+ await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
637
+ }
638
+ if (this.features.legacyStorageEnabled) {
639
+ const documents = [];
640
+ // Fetch each document by ID using storage directly
641
+ for (const id of ids) {
642
+ if (signal?.aborted) {
643
+ throw new AbortError();
644
+ }
645
+ let document;
646
+ try {
647
+ document = await this.documentStorage.get(id);
648
+ }
649
+ catch {
650
+ // Skip documents that don't exist or can't be accessed
651
+ // This matches the behavior expected from a search operation
652
+ continue;
653
+ }
654
+ // Apply view filter - This will be removed when we pass the viewfilter along
655
+ // to the underlying store, but is here now for the interface.
656
+ for (const scope in document.state) {
657
+ if (!matchesScope(view, scope)) {
658
+ delete document.state[scope];
659
+ }
660
+ }
661
+ documents.push(document);
662
+ }
449
663
  if (signal?.aborted) {
450
664
  throw new AbortError();
451
665
  }
452
- let document;
453
- try {
454
- document = await this.documentStorage.get(id);
455
- }
456
- catch {
457
- // Skip documents that don't exist or can't be accessed
458
- // This matches the behavior expected from a search operation
459
- continue;
460
- }
461
- // Apply view filter - This will be removed when we pass the viewfilter along
462
- // to the underlying store, but is here now for the interface.
463
- for (const scope in document.state) {
464
- if (!matchesScope(view, scope)) {
465
- delete document.state[scope];
666
+ // Apply paging
667
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
668
+ const limit = paging?.limit || documents.length;
669
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
670
+ // Create paged results
671
+ const hasMore = startIndex + limit < documents.length;
672
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
673
+ return {
674
+ results: pagedDocuments,
675
+ options: paging || { cursor: "0", limit: documents.length },
676
+ nextCursor,
677
+ next: hasMore
678
+ ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, undefined, signal)
679
+ : undefined,
680
+ };
681
+ }
682
+ else {
683
+ const documents = [];
684
+ // Fetch each document by ID using documentView
685
+ for (const id of ids) {
686
+ if (signal?.aborted) {
687
+ throw new AbortError();
688
+ }
689
+ try {
690
+ const document = await this.documentView.get(id, view, undefined, signal);
691
+ documents.push(document);
692
+ }
693
+ catch {
694
+ // Skip documents that don't exist or can't be accessed
695
+ continue;
466
696
  }
467
697
  }
468
- documents.push(document);
469
- }
470
- if (signal?.aborted) {
471
- throw new AbortError();
698
+ if (signal?.aborted) {
699
+ throw new AbortError();
700
+ }
701
+ // Apply paging
702
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
703
+ const limit = paging?.limit || documents.length;
704
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
705
+ // Create paged results
706
+ const hasMore = startIndex + limit < documents.length;
707
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
708
+ return {
709
+ results: pagedDocuments,
710
+ options: paging || { cursor: "0", limit: documents.length },
711
+ nextCursor,
712
+ next: hasMore
713
+ ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, undefined, signal)
714
+ : undefined,
715
+ };
472
716
  }
473
- // Apply paging
474
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
475
- const limit = paging?.limit || documents.length;
476
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
477
- // Create paged results
478
- const hasMore = startIndex + limit < documents.length;
479
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
480
- return {
481
- results: pagedDocuments,
482
- options: paging || { cursor: "0", limit: documents.length },
483
- nextCursor,
484
- next: hasMore
485
- ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, signal)
486
- : undefined,
487
- };
488
717
  }
489
718
  /**
490
719
  * Finds documents by their slugs
491
720
  */
492
- async findBySlugs(slugs, view, paging, signal) {
493
- const documents = [];
494
- // Use storage to resolve slugs to IDs
495
- let ids;
496
- try {
497
- ids = await this.documentStorage.resolveIds(slugs, signal);
498
- }
499
- catch {
500
- // If slug resolution fails, return empty results
501
- // This matches the behavior expected from a search operation
502
- ids = [];
503
- }
504
- // Fetch each document by resolved ID
505
- for (const id of ids) {
506
- if (signal?.aborted) {
507
- throw new AbortError();
508
- }
509
- let document;
721
+ async findBySlugs(slugs, view, paging, consistencyToken, signal) {
722
+ if (consistencyToken) {
723
+ await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
724
+ }
725
+ if (this.features.legacyStorageEnabled) {
726
+ const documents = [];
727
+ // Use storage to resolve slugs to IDs
728
+ let ids;
510
729
  try {
511
- document = await this.documentStorage.get(id);
730
+ ids = await this.documentStorage.resolveIds(slugs, signal);
512
731
  }
513
732
  catch {
514
- // Skip documents that don't exist or can't be accessed
515
- continue;
733
+ // If slug resolution fails, return empty results
734
+ // This matches the behavior expected from a search operation
735
+ ids = [];
516
736
  }
517
- // Apply view filter - This will be removed when we pass the viewfilter along
518
- // to the underlying store, but is here now for the interface.
519
- for (const scope in document.state) {
520
- if (!matchesScope(view, scope)) {
521
- delete document.state[scope];
737
+ // Fetch each document by resolved ID
738
+ for (const id of ids) {
739
+ if (signal?.aborted) {
740
+ throw new AbortError();
741
+ }
742
+ let document;
743
+ try {
744
+ document = await this.documentStorage.get(id);
745
+ }
746
+ catch {
747
+ // Skip documents that don't exist or can't be accessed
748
+ continue;
522
749
  }
750
+ // Apply view filter - This will be removed when we pass the viewfilter along
751
+ // to the underlying store, but is here now for the interface.
752
+ for (const scope in document.state) {
753
+ if (!matchesScope(view, scope)) {
754
+ delete document.state[scope];
755
+ }
756
+ }
757
+ documents.push(document);
523
758
  }
524
- documents.push(document);
759
+ if (signal?.aborted) {
760
+ throw new AbortError();
761
+ }
762
+ // Apply paging - this will be removed when we pass the paging along
763
+ // to the underlying store, but is here now for the interface.
764
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
765
+ const limit = paging?.limit || documents.length;
766
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
767
+ // Create paged results
768
+ const hasMore = startIndex + limit < documents.length;
769
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
770
+ return {
771
+ results: pagedDocuments,
772
+ options: paging || { cursor: "0", limit: documents.length },
773
+ nextCursor,
774
+ next: hasMore
775
+ ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, undefined, signal)
776
+ : undefined,
777
+ };
525
778
  }
526
- if (signal?.aborted) {
527
- throw new AbortError();
779
+ else {
780
+ const documents = [];
781
+ // Resolve each slug to a document ID
782
+ const documentIds = [];
783
+ for (const slug of slugs) {
784
+ if (signal?.aborted) {
785
+ throw new AbortError();
786
+ }
787
+ const documentId = await this.documentView.resolveSlug(slug, view, undefined, signal);
788
+ if (documentId) {
789
+ documentIds.push(documentId);
790
+ }
791
+ }
792
+ // Fetch each document
793
+ for (const documentId of documentIds) {
794
+ if (signal?.aborted) {
795
+ throw new AbortError();
796
+ }
797
+ try {
798
+ const document = await this.documentView.get(documentId, view, undefined, signal);
799
+ documents.push(document);
800
+ }
801
+ catch {
802
+ // Skip documents that don't exist or can't be accessed
803
+ continue;
804
+ }
805
+ }
806
+ if (signal?.aborted) {
807
+ throw new AbortError();
808
+ }
809
+ // Apply paging
810
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
811
+ const limit = paging?.limit || documents.length;
812
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
813
+ // Create paged results
814
+ const hasMore = startIndex + limit < documents.length;
815
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
816
+ return {
817
+ results: pagedDocuments,
818
+ options: paging || { cursor: "0", limit: documents.length },
819
+ nextCursor,
820
+ next: hasMore
821
+ ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, undefined, signal)
822
+ : undefined,
823
+ };
528
824
  }
529
- // Apply paging - this will be removed when we pass the paging along
530
- // to the underlying store, but is here now for the interface.
531
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
532
- const limit = paging?.limit || documents.length;
533
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
534
- // Create paged results
535
- const hasMore = startIndex + limit < documents.length;
536
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
537
- return {
538
- results: pagedDocuments,
539
- options: paging || { cursor: "0", limit: documents.length },
540
- nextCursor,
541
- next: hasMore
542
- ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, signal)
543
- : undefined,
544
- };
545
825
  }
546
826
  /**
547
827
  * Finds documents by parent ID
548
828
  */
549
829
  async findByParentId(parentId, view, paging, signal) {
550
- // Get child document IDs from storage
551
- const childIds = await this.documentStorage.getChildren(parentId);
552
- if (signal?.aborted) {
553
- throw new AbortError();
554
- }
555
- const documents = [];
556
- // Fetch each child document
557
- for (const childId of childIds) {
830
+ if (this.features.legacyStorageEnabled) {
831
+ // Get child document IDs from storage
832
+ const childIds = await this.documentStorage.getChildren(parentId);
558
833
  if (signal?.aborted) {
559
834
  throw new AbortError();
560
835
  }
561
- let document;
562
- try {
563
- document = await this.documentStorage.get(childId);
836
+ const documents = [];
837
+ // Fetch each child document
838
+ for (const childId of childIds) {
839
+ if (signal?.aborted) {
840
+ throw new AbortError();
841
+ }
842
+ let document;
843
+ try {
844
+ document = await this.documentStorage.get(childId);
845
+ }
846
+ catch {
847
+ // Skip documents that don't exist or can't be accessed
848
+ // This matches the behavior expected from a search operation
849
+ continue;
850
+ }
851
+ // Apply view filter - This will be removed when we pass the viewfilter along
852
+ // to the underlying store, but is here now for the interface.
853
+ for (const scope in document.state) {
854
+ if (!matchesScope(view, scope)) {
855
+ delete document.state[scope];
856
+ }
857
+ }
858
+ documents.push(document);
564
859
  }
565
- catch {
566
- // Skip documents that don't exist or can't be accessed
567
- // This matches the behavior expected from a search operation
568
- continue;
860
+ if (signal?.aborted) {
861
+ throw new AbortError();
569
862
  }
570
- // Apply view filter - This will be removed when we pass the viewfilter along
571
- // to the underlying store, but is here now for the interface.
572
- for (const scope in document.state) {
573
- if (!matchesScope(view, scope)) {
574
- delete document.state[scope];
863
+ // Apply paging
864
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
865
+ const limit = paging?.limit || documents.length;
866
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
867
+ // Create paged results
868
+ const hasMore = startIndex + limit < documents.length;
869
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
870
+ return {
871
+ results: pagedDocuments,
872
+ options: paging || { cursor: "0", limit: documents.length },
873
+ nextCursor,
874
+ next: hasMore
875
+ ? () => this.findByParentId(parentId, view, { cursor: nextCursor, limit }, signal)
876
+ : undefined,
877
+ };
878
+ }
879
+ else {
880
+ // Get child relationships from indexer
881
+ const relationships = await this.documentIndexer.getOutgoing(parentId, ["child"], undefined, signal);
882
+ if (signal?.aborted) {
883
+ throw new AbortError();
884
+ }
885
+ const documents = [];
886
+ // Fetch each child document
887
+ for (const relationship of relationships) {
888
+ if (signal?.aborted) {
889
+ throw new AbortError();
890
+ }
891
+ try {
892
+ const document = await this.documentView.get(relationship.targetId, view, undefined, signal);
893
+ documents.push(document);
894
+ }
895
+ catch {
896
+ // Skip documents that don't exist or can't be accessed
897
+ continue;
575
898
  }
576
899
  }
577
- documents.push(document);
578
- }
579
- if (signal?.aborted) {
580
- throw new AbortError();
900
+ if (signal?.aborted) {
901
+ throw new AbortError();
902
+ }
903
+ // Apply paging
904
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
905
+ const limit = paging?.limit || documents.length;
906
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
907
+ // Create paged results
908
+ const hasMore = startIndex + limit < documents.length;
909
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
910
+ return {
911
+ results: pagedDocuments,
912
+ options: paging || { cursor: "0", limit: documents.length },
913
+ nextCursor,
914
+ next: hasMore
915
+ ? () => this.findByParentId(parentId, view, { cursor: nextCursor, limit }, signal)
916
+ : undefined,
917
+ };
581
918
  }
582
- // Apply paging
583
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
584
- const limit = paging?.limit || documents.length;
585
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
586
- // Create paged results
587
- const hasMore = startIndex + limit < documents.length;
588
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
589
- return {
590
- results: pagedDocuments,
591
- options: paging || { cursor: "0", limit: documents.length },
592
- nextCursor,
593
- next: hasMore
594
- ? () => this.findByParentId(parentId, view, { cursor: nextCursor, limit }, signal)
595
- : undefined,
596
- };
597
919
  }
598
920
  /**
599
921
  * Finds documents by type
600
922
  */
601
- async findByType(type, view, paging, signal) {
602
- const documents = [];
603
- // Use storage's findByType method directly
604
- const cursor = paging?.cursor;
605
- const limit = paging?.limit || 100;
606
- // Get document IDs of the specified type
607
- const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor);
608
- if (signal?.aborted) {
609
- throw new AbortError();
610
- }
611
- // Fetch each document by its ID
612
- for (const documentId of documentIds) {
923
+ async findByType(type, view, paging, consistencyToken, signal) {
924
+ if (consistencyToken) {
925
+ await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
926
+ }
927
+ if (this.features.legacyStorageEnabled) {
928
+ const documents = [];
929
+ // Use storage's findByType method directly
930
+ const cursor = paging?.cursor;
931
+ const limit = paging?.limit || 100;
932
+ // Get document IDs of the specified type
933
+ const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor);
613
934
  if (signal?.aborted) {
614
935
  throw new AbortError();
615
936
  }
616
- let document;
617
- try {
618
- document = await this.documentStorage.get(documentId);
619
- }
620
- catch {
621
- // Skip documents that can't be retrieved
622
- continue;
623
- }
624
- // Apply view filter
625
- for (const scope in document.state) {
626
- if (!matchesScope(view, scope)) {
627
- delete document.state[scope];
937
+ // Fetch each document by its ID
938
+ for (const documentId of documentIds) {
939
+ if (signal?.aborted) {
940
+ throw new AbortError();
941
+ }
942
+ let document;
943
+ try {
944
+ document = await this.documentStorage.get(documentId);
628
945
  }
946
+ catch {
947
+ // Skip documents that can't be retrieved
948
+ continue;
949
+ }
950
+ // Apply view filter
951
+ for (const scope in document.state) {
952
+ if (!matchesScope(view, scope)) {
953
+ delete document.state[scope];
954
+ }
955
+ }
956
+ documents.push(document);
957
+ }
958
+ if (signal?.aborted) {
959
+ throw new AbortError();
629
960
  }
630
- documents.push(document);
961
+ // Results are already paged from the storage layer
962
+ return {
963
+ results: documents,
964
+ options: paging || { cursor: cursor || "0", limit },
965
+ nextCursor,
966
+ next: nextCursor
967
+ ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, undefined, signal)
968
+ : undefined,
969
+ };
631
970
  }
632
- if (signal?.aborted) {
633
- throw new AbortError();
971
+ else {
972
+ const result = await this.documentView.findByType(type, view, paging, undefined, signal);
973
+ if (signal?.aborted) {
974
+ throw new AbortError();
975
+ }
976
+ const cursor = paging?.cursor;
977
+ const limit = paging?.limit || 100;
978
+ return {
979
+ results: result.items,
980
+ options: paging || { cursor: cursor || "0", limit },
981
+ nextCursor: result.nextCursor,
982
+ next: result.nextCursor
983
+ ? async () => this.findByType(type, view, { cursor: result.nextCursor, limit }, undefined, signal)
984
+ : undefined,
985
+ };
634
986
  }
635
- // Results are already paged from the storage layer
636
- return {
637
- results: documents,
638
- options: paging || { cursor: cursor || "0", limit },
639
- nextCursor,
640
- next: nextCursor
641
- ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, signal)
642
- : undefined,
643
- };
644
987
  }
645
988
  }
646
989
  //# sourceMappingURL=reactor.js.map