@powerhousedao/reactor 5.0.1-staging.1 → 5.0.1-staging.10
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.
- package/dist/src/cache/kysely-write-cache.d.ts +4 -3
- package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
- package/dist/src/cache/kysely-write-cache.js +12 -12
- package/dist/src/cache/kysely-write-cache.js.map +1 -1
- package/dist/src/client/reactor-client.d.ts +2 -2
- package/dist/src/client/reactor-client.d.ts.map +1 -1
- package/dist/src/client/reactor-client.js +8 -8
- package/dist/src/client/reactor-client.js.map +1 -1
- package/dist/src/client/types.d.ts +4 -4
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.d.ts +32 -0
- package/dist/src/core/reactor-builder.d.ts.map +1 -0
- package/dist/src/core/reactor-builder.js +120 -0
- package/dist/src/core/reactor-builder.js.map +1 -0
- package/dist/src/core/reactor.d.ts +25 -10
- package/dist/src/core/reactor.d.ts.map +1 -1
- package/dist/src/core/reactor.js +627 -284
- package/dist/src/core/reactor.js.map +1 -1
- package/dist/src/core/types.d.ts +64 -7
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/utils.d.ts +46 -2
- package/dist/src/core/utils.d.ts.map +1 -1
- package/dist/src/core/utils.js +119 -0
- package/dist/src/core/utils.js.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.d.ts +1 -0
- package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.js +41 -17
- package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
- package/dist/src/executor/simple-job-executor.d.ts +14 -2
- package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor.js +383 -249
- package/dist/src/executor/simple-job-executor.js.map +1 -1
- package/dist/src/executor/types.d.ts +2 -0
- package/dist/src/executor/types.d.ts.map +1 -1
- package/dist/src/executor/types.js.map +1 -1
- package/dist/src/executor/util.d.ts +18 -0
- package/dist/src/executor/util.d.ts.map +1 -1
- package/dist/src/executor/util.js +42 -1
- package/dist/src/executor/util.js.map +1 -1
- package/dist/src/index.d.ts +9 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/job-tracker/in-memory-job-tracker.d.ts +3 -2
- package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
- package/dist/src/job-tracker/in-memory-job-tracker.js +7 -1
- package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
- package/dist/src/job-tracker/interfaces.d.ts +5 -4
- package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
- package/dist/src/queue/interfaces.d.ts +5 -4
- package/dist/src/queue/interfaces.d.ts.map +1 -1
- package/dist/src/queue/job-execution-handle.d.ts +3 -2
- package/dist/src/queue/job-execution-handle.d.ts.map +1 -1
- package/dist/src/queue/job-execution-handle.js +2 -2
- package/dist/src/queue/job-execution-handle.js.map +1 -1
- package/dist/src/queue/queue.d.ts +4 -2
- package/dist/src/queue/queue.d.ts.map +1 -1
- package/dist/src/queue/queue.js +16 -4
- package/dist/src/queue/queue.js.map +1 -1
- package/dist/src/queue/types.d.ts +12 -6
- package/dist/src/queue/types.d.ts.map +1 -1
- package/dist/src/queue/types.js.map +1 -1
- package/dist/src/read-models/document-view.d.ts +10 -6
- package/dist/src/read-models/document-view.d.ts.map +1 -1
- package/dist/src/read-models/document-view.js +116 -113
- package/dist/src/read-models/document-view.js.map +1 -1
- package/dist/src/read-models/types.d.ts +3 -3
- package/dist/src/read-models/types.d.ts.map +1 -1
- package/dist/src/shared/consistency-tracker.d.ts +48 -0
- package/dist/src/shared/consistency-tracker.d.ts.map +1 -0
- package/dist/src/shared/consistency-tracker.js +123 -0
- package/dist/src/shared/consistency-tracker.js.map +1 -0
- package/dist/src/shared/types.d.ts +35 -1
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/types.js.map +1 -1
- package/dist/src/storage/interfaces.d.ts +148 -2
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/interfaces.js.map +1 -1
- package/dist/src/storage/kysely/document-indexer.d.ts +28 -0
- package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -0
- package/dist/src/storage/kysely/document-indexer.js +350 -0
- package/dist/src/storage/kysely/document-indexer.js.map +1 -0
- package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -1
- package/dist/src/storage/kysely/keyframe-store.js +6 -13
- package/dist/src/storage/kysely/keyframe-store.js.map +1 -1
- package/dist/src/storage/kysely/store.js +1 -1
- package/dist/src/storage/kysely/store.js.map +1 -1
- package/dist/src/storage/kysely/types.d.ts +35 -2
- package/dist/src/storage/kysely/types.d.ts.map +1 -1
- package/dist/src/storage/migrations/001_create_operation_table.d.ts +3 -0
- package/dist/src/storage/migrations/001_create_operation_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/001_create_operation_table.js +40 -0
- package/dist/src/storage/migrations/001_create_operation_table.js.map +1 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.d.ts +3 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.js +27 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.js.map +1 -0
- package/dist/src/storage/migrations/003_create_document_table.d.ts +3 -0
- package/dist/src/storage/migrations/003_create_document_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/003_create_document_table.js +10 -0
- package/dist/src/storage/migrations/003_create_document_table.js.map +1 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts +3 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.js +35 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.js.map +1 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts +3 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.js +10 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.js.map +1 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts +3 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.js +49 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.js.map +1 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts +3 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.js +24 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.js.map +1 -0
- package/dist/src/storage/migrations/008_create_view_state_table.d.ts +3 -0
- package/dist/src/storage/migrations/008_create_view_state_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/008_create_view_state_table.js +9 -0
- package/dist/src/storage/migrations/008_create_view_state_table.js.map +1 -0
- package/dist/src/storage/migrations/index.d.ts +3 -0
- package/dist/src/storage/migrations/index.d.ts.map +1 -0
- package/dist/src/storage/migrations/index.js +3 -0
- package/dist/src/storage/migrations/index.js.map +1 -0
- package/dist/src/storage/migrations/migrator.d.ts +5 -0
- package/dist/src/storage/migrations/migrator.d.ts.map +1 -0
- package/dist/src/storage/migrations/migrator.js +51 -0
- package/dist/src/storage/migrations/migrator.js.map +1 -0
- package/dist/src/storage/migrations/run-migrations.d.ts +2 -0
- package/dist/src/storage/migrations/run-migrations.d.ts.map +1 -0
- package/dist/src/storage/migrations/run-migrations.js +58 -0
- package/dist/src/storage/migrations/run-migrations.js.map +1 -0
- package/dist/src/storage/migrations/types.d.ts +9 -0
- package/dist/src/storage/migrations/types.d.ts.map +1 -0
- package/dist/src/storage/migrations/types.js +2 -0
- package/dist/src/storage/migrations/types.js.map +1 -0
- package/dist/src/storage/txn.d.ts.map +1 -1
- package/dist/src/storage/txn.js +2 -0
- package/dist/src/storage/txn.js.map +1 -1
- package/package.json +8 -5
package/dist/src/core/reactor.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
137
|
+
return await this.get(ids[0], view, consistencyToken, signal);
|
|
111
138
|
}
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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:
|
|
140
|
-
options: {
|
|
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(
|
|
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
|
-
|
|
401
|
+
kind: "mutation",
|
|
402
|
+
documentId: docId,
|
|
311
403
|
scope: scope,
|
|
312
|
-
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
|
-
*
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
359
|
-
for (const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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
|
-
*
|
|
564
|
+
* Adds multiple documents as children to another
|
|
387
565
|
*/
|
|
388
|
-
async
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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:
|
|
436
|
-
completedAtUtcIso:
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
730
|
+
ids = await this.documentStorage.resolveIds(slugs, signal);
|
|
512
731
|
}
|
|
513
732
|
catch {
|
|
514
|
-
//
|
|
515
|
-
|
|
733
|
+
// If slug resolution fails, return empty results
|
|
734
|
+
// This matches the behavior expected from a search operation
|
|
735
|
+
ids = [];
|
|
516
736
|
}
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
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
|
-
|
|
527
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
// This matches the behavior expected from a search operation
|
|
568
|
-
continue;
|
|
860
|
+
if (signal?.aborted) {
|
|
861
|
+
throw new AbortError();
|
|
569
862
|
}
|
|
570
|
-
// Apply
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|