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