@powerhousedao/reactor 5.1.0-dev.3 → 5.1.0-dev.30
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/document-meta-cache-types.d.ts +114 -0
- package/dist/src/cache/document-meta-cache-types.d.ts.map +1 -0
- package/dist/src/cache/document-meta-cache-types.js +2 -0
- package/dist/src/cache/document-meta-cache-types.js.map +1 -0
- package/dist/src/cache/document-meta-cache.d.ts +30 -0
- package/dist/src/cache/document-meta-cache.d.ts.map +1 -0
- package/dist/src/cache/document-meta-cache.js +128 -0
- package/dist/src/cache/document-meta-cache.js.map +1 -0
- package/dist/src/cache/kysely-operation-index.d.ts +4 -2
- package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
- package/dist/src/cache/kysely-operation-index.js +54 -0
- 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 +2 -1
- package/dist/src/cache/kysely-write-cache.js.map +1 -1
- package/dist/src/cache/operation-index-types.d.ts +3 -2
- 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 +7 -8
- package/dist/src/client/reactor-client.d.ts.map +1 -1
- package/dist/src/client/reactor-client.js +48 -45
- package/dist/src/client/reactor-client.js.map +1 -1
- package/dist/src/client/types.d.ts +10 -10
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.d.ts +15 -11
- package/dist/src/core/reactor-builder.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.js +57 -19
- package/dist/src/core/reactor-builder.js.map +1 -1
- package/dist/src/core/{builder.d.ts → reactor-client-builder.d.ts} +12 -4
- package/dist/src/core/reactor-client-builder.d.ts.map +1 -0
- package/dist/src/core/{builder.js → reactor-client-builder.js} +44 -23
- package/dist/src/core/reactor-client-builder.js.map +1 -0
- package/dist/src/core/reactor.d.ts +12 -12
- package/dist/src/core/reactor.d.ts.map +1 -1
- package/dist/src/core/reactor.js +61 -36
- package/dist/src/core/reactor.js.map +1 -1
- package/dist/src/core/types.d.ts +49 -14
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/utils.d.ts +9 -1
- package/dist/src/core/utils.d.ts.map +1 -1
- package/dist/src/core/utils.js +30 -0
- package/dist/src/core/utils.js.map +1 -1
- package/dist/src/events/types.d.ts +1 -0
- package/dist/src/events/types.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.js +1 -0
- package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
- package/dist/src/executor/simple-job-executor.d.ts +17 -2
- package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor.js +283 -192
- package/dist/src/executor/simple-job-executor.js.map +1 -1
- package/dist/src/executor/util.d.ts +14 -5
- package/dist/src/executor/util.d.ts.map +1 -1
- package/dist/src/executor/util.js +36 -9
- package/dist/src/executor/util.js.map +1 -1
- package/dist/src/index.d.ts +12 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +9 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging/console.d.ts +13 -0
- package/dist/src/logging/console.d.ts.map +1 -0
- package/dist/src/logging/console.js +77 -0
- package/dist/src/logging/console.js.map +1 -0
- package/dist/src/logging/types.d.ts +11 -0
- package/dist/src/logging/types.d.ts.map +1 -0
- package/dist/src/logging/types.js +2 -0
- package/dist/src/logging/types.js.map +1 -0
- package/dist/src/queue/types.d.ts +2 -0
- package/dist/src/queue/types.d.ts.map +1 -1
- package/dist/src/queue/types.js.map +1 -1
- package/dist/src/read-models/base-read-model.d.ts +60 -0
- package/dist/src/read-models/base-read-model.d.ts.map +1 -0
- package/dist/src/read-models/base-read-model.js +143 -0
- package/dist/src/read-models/base-read-model.js.map +1 -0
- package/dist/src/read-models/coordinator.d.ts +3 -1
- package/dist/src/read-models/coordinator.d.ts.map +1 -1
- package/dist/src/read-models/coordinator.js +8 -7
- package/dist/src/read-models/coordinator.js.map +1 -1
- package/dist/src/read-models/document-view.d.ts +6 -7
- package/dist/src/read-models/document-view.d.ts.map +1 -1
- package/dist/src/read-models/document-view.js +16 -81
- package/dist/src/read-models/document-view.js.map +1 -1
- package/dist/src/read-models/types.d.ts +2 -1
- package/dist/src/read-models/types.d.ts.map +1 -1
- package/dist/src/registry/implementation.d.ts +42 -34
- package/dist/src/registry/implementation.d.ts.map +1 -1
- package/dist/src/registry/implementation.js +168 -48
- package/dist/src/registry/implementation.js.map +1 -1
- package/dist/src/registry/interfaces.d.ts +69 -8
- package/dist/src/registry/interfaces.d.ts.map +1 -1
- package/dist/src/shared/errors.d.ts +24 -0
- package/dist/src/shared/errors.d.ts.map +1 -1
- package/dist/src/shared/errors.js +42 -0
- package/dist/src/shared/errors.js.map +1 -1
- package/dist/src/shared/types.d.ts +4 -0
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/types.js.map +1 -1
- package/dist/src/signer/passthrough-signer.d.ts +9 -3
- package/dist/src/signer/passthrough-signer.d.ts.map +1 -1
- package/dist/src/signer/passthrough-signer.js +13 -0
- package/dist/src/signer/passthrough-signer.js.map +1 -1
- package/dist/src/signer/types.d.ts +12 -10
- package/dist/src/signer/types.d.ts.map +1 -1
- package/dist/src/storage/consistency-aware-legacy-storage.d.ts +33 -0
- package/dist/src/storage/consistency-aware-legacy-storage.d.ts.map +1 -0
- package/dist/src/storage/consistency-aware-legacy-storage.js +65 -0
- package/dist/src/storage/consistency-aware-legacy-storage.js.map +1 -0
- package/dist/src/storage/interfaces.d.ts +81 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/interfaces.js.map +1 -1
- package/dist/src/storage/kysely/store.d.ts.map +1 -1
- package/dist/src/storage/kysely/store.js +1 -0
- package/dist/src/storage/kysely/store.js.map +1 -1
- package/dist/src/storage/migrations/008_create_view_state_table.d.ts +1 -1
- package/dist/src/storage/migrations/008_create_view_state_table.d.ts.map +1 -1
- package/dist/src/storage/migrations/008_create_view_state_table.js +2 -1
- package/dist/src/storage/migrations/008_create_view_state_table.js.map +1 -1
- package/dist/src/storage/migrations/run-migrations.js +3 -3
- package/dist/src/storage/migrations/run-migrations.js.map +1 -1
- package/dist/src/subs/subscription-notification-read-model.d.ts +16 -0
- package/dist/src/subs/subscription-notification-read-model.d.ts.map +1 -0
- package/dist/src/subs/subscription-notification-read-model.js +51 -0
- package/dist/src/subs/subscription-notification-read-model.js.map +1 -0
- package/dist/src/sync/channels/composite-channel-factory.d.ts +27 -0
- package/dist/src/sync/channels/composite-channel-factory.d.ts.map +1 -0
- package/dist/src/sync/channels/composite-channel-factory.js +83 -0
- package/dist/src/sync/channels/composite-channel-factory.js.map +1 -0
- package/dist/src/sync/channels/gql-channel-factory.d.ts +2 -2
- package/dist/src/sync/channels/gql-channel-factory.d.ts.map +1 -1
- package/dist/src/sync/channels/gql-channel-factory.js +3 -1
- package/dist/src/sync/channels/gql-channel-factory.js.map +1 -1
- package/dist/src/sync/channels/gql-channel.d.ts +21 -0
- package/dist/src/sync/channels/gql-channel.d.ts.map +1 -1
- package/dist/src/sync/channels/gql-channel.js +108 -13
- package/dist/src/sync/channels/gql-channel.js.map +1 -1
- package/dist/src/sync/channels/index.d.ts +2 -1
- package/dist/src/sync/channels/index.d.ts.map +1 -1
- package/dist/src/sync/channels/index.js +2 -1
- package/dist/src/sync/channels/index.js.map +1 -1
- package/dist/src/sync/channels/polling-channel.d.ts +39 -0
- package/dist/src/sync/channels/polling-channel.d.ts.map +1 -0
- package/dist/src/sync/channels/polling-channel.js +70 -0
- package/dist/src/sync/channels/polling-channel.js.map +1 -0
- package/dist/src/sync/channels/utils.d.ts +4 -2
- package/dist/src/sync/channels/utils.d.ts.map +1 -1
- package/dist/src/sync/channels/utils.js +52 -6
- package/dist/src/sync/channels/utils.js.map +1 -1
- package/dist/src/sync/errors.d.ts +1 -1
- package/dist/src/sync/errors.d.ts.map +1 -1
- package/dist/src/sync/errors.js +2 -2
- package/dist/src/sync/errors.js.map +1 -1
- package/dist/src/sync/index.d.ts +2 -2
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/index.js +2 -2
- package/dist/src/sync/index.js.map +1 -1
- package/dist/src/sync/interfaces.d.ts +16 -1
- package/dist/src/sync/interfaces.d.ts.map +1 -1
- package/dist/src/sync/sync-manager.d.ts +1 -0
- package/dist/src/sync/sync-manager.d.ts.map +1 -1
- package/dist/src/sync/sync-manager.js +52 -4
- package/dist/src/sync/sync-manager.js.map +1 -1
- package/package.json +4 -4
- package/dist/src/core/builder.d.ts.map +0 -1
- package/dist/src/core/builder.js.map +0 -1
- package/dist/src/sync/channels/internal-channel.d.ts +0 -57
- package/dist/src/sync/channels/internal-channel.d.ts.map +0 -1
- package/dist/src/sync/channels/internal-channel.js +0 -106
- package/dist/src/sync/channels/internal-channel.js.map +0 -1
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { driveCollectionId } from "../cache/operation-index-types.js";
|
|
2
2
|
import { OperationEventTypes, } from "../events/types.js";
|
|
3
|
-
import { DocumentDeletedError } from "../shared/errors.js";
|
|
3
|
+
import { DocumentDeletedError, InvalidSignatureError, } from "../shared/errors.js";
|
|
4
4
|
import { reshuffleByTimestampAndIndex } from "../utils/reshuffle.js";
|
|
5
5
|
import { applyDeleteDocumentAction, applyUpgradeDocumentAction, createDocumentFromAction, getNextIndexForScope, } from "./util.js";
|
|
6
6
|
const MAX_SKIP_THRESHOLD = 100;
|
|
7
|
+
const documentScopeActions = [
|
|
8
|
+
"CREATE_DOCUMENT",
|
|
9
|
+
"DELETE_DOCUMENT",
|
|
10
|
+
"UPGRADE_DOCUMENT",
|
|
11
|
+
"ADD_RELATIONSHIP",
|
|
12
|
+
"REMOVE_RELATIONSHIP",
|
|
13
|
+
];
|
|
7
14
|
/**
|
|
8
15
|
* Simple job executor that processes a job by applying actions through document model reducers.
|
|
9
16
|
*
|
|
@@ -19,8 +26,10 @@ export class SimpleJobExecutor {
|
|
|
19
26
|
eventBus;
|
|
20
27
|
writeCache;
|
|
21
28
|
operationIndex;
|
|
29
|
+
documentMetaCache;
|
|
30
|
+
signatureVerifier;
|
|
22
31
|
config;
|
|
23
|
-
constructor(registry, documentStorage, operationStorage, operationStore, eventBus, writeCache, operationIndex, config) {
|
|
32
|
+
constructor(registry, documentStorage, operationStorage, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, config, signatureVerifier) {
|
|
24
33
|
this.registry = registry;
|
|
25
34
|
this.documentStorage = documentStorage;
|
|
26
35
|
this.operationStorage = operationStorage;
|
|
@@ -28,6 +37,8 @@ export class SimpleJobExecutor {
|
|
|
28
37
|
this.eventBus = eventBus;
|
|
29
38
|
this.writeCache = writeCache;
|
|
30
39
|
this.operationIndex = operationIndex;
|
|
40
|
+
this.documentMetaCache = documentMetaCache;
|
|
41
|
+
this.signatureVerifier = signatureVerifier;
|
|
31
42
|
this.config = {
|
|
32
43
|
maxConcurrency: config.maxConcurrency ?? 1,
|
|
33
44
|
jobTimeoutMs: config.jobTimeoutMs ?? 30000,
|
|
@@ -45,8 +56,23 @@ export class SimpleJobExecutor {
|
|
|
45
56
|
const indexTxn = this.operationIndex.start();
|
|
46
57
|
if (job.kind === "load") {
|
|
47
58
|
const result = await this.executeLoadJob(job, startTime, indexTxn);
|
|
48
|
-
if (result.success) {
|
|
49
|
-
await this.operationIndex.commit(indexTxn);
|
|
59
|
+
if (result.success && result.operationsWithContext) {
|
|
60
|
+
const ordinals = await this.operationIndex.commit(indexTxn);
|
|
61
|
+
for (let i = 0; i < result.operationsWithContext.length; i++) {
|
|
62
|
+
result.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
63
|
+
}
|
|
64
|
+
if (result.operationsWithContext.length > 0) {
|
|
65
|
+
const event = {
|
|
66
|
+
jobId: job.id,
|
|
67
|
+
operations: result.operationsWithContext,
|
|
68
|
+
jobMeta: job.meta,
|
|
69
|
+
};
|
|
70
|
+
this.eventBus
|
|
71
|
+
.emit(OperationEventTypes.OPERATION_WRITTEN, event)
|
|
72
|
+
.catch(() => {
|
|
73
|
+
// TODO: Log error
|
|
74
|
+
});
|
|
75
|
+
}
|
|
50
76
|
}
|
|
51
77
|
return result;
|
|
52
78
|
}
|
|
@@ -59,11 +85,15 @@ export class SimpleJobExecutor {
|
|
|
59
85
|
duration: Date.now() - startTime,
|
|
60
86
|
};
|
|
61
87
|
}
|
|
62
|
-
await this.operationIndex.commit(indexTxn);
|
|
88
|
+
const ordinals = await this.operationIndex.commit(indexTxn);
|
|
63
89
|
if (result.operationsWithContext.length > 0) {
|
|
90
|
+
for (let i = 0; i < result.operationsWithContext.length; i++) {
|
|
91
|
+
result.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
92
|
+
}
|
|
64
93
|
const event = {
|
|
65
94
|
jobId: job.id,
|
|
66
95
|
operations: result.operationsWithContext,
|
|
96
|
+
jobMeta: job.meta,
|
|
67
97
|
};
|
|
68
98
|
this.eventBus
|
|
69
99
|
.emit(OperationEventTypes.OPERATION_WRITTEN, event)
|
|
@@ -82,186 +112,33 @@ export class SimpleJobExecutor {
|
|
|
82
112
|
async processActions(job, actions, startTime, indexTxn, skipValues) {
|
|
83
113
|
const generatedOperations = [];
|
|
84
114
|
const operationsWithContext = [];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
success: false,
|
|
107
|
-
generatedOperations,
|
|
108
|
-
operationsWithContext,
|
|
109
|
-
error: error.error,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
actionIndex++;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (action.type === "UPGRADE_DOCUMENT") {
|
|
116
|
-
const result = await this.executeUpgradeDocumentAction(job, action, startTime, indexTxn, skipValues?.[actionIndex]);
|
|
117
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
118
|
-
if (error !== null) {
|
|
119
|
-
return {
|
|
120
|
-
success: false,
|
|
121
|
-
generatedOperations,
|
|
122
|
-
operationsWithContext,
|
|
123
|
-
error: error.error,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
actionIndex++;
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (action.type === "ADD_RELATIONSHIP") {
|
|
130
|
-
const result = await this.executeAddRelationshipAction(job, action, startTime, indexTxn);
|
|
131
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
132
|
-
if (error !== null) {
|
|
133
|
-
return {
|
|
134
|
-
success: false,
|
|
135
|
-
generatedOperations,
|
|
136
|
-
operationsWithContext,
|
|
137
|
-
error: error.error,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
actionIndex++;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (action.type === "REMOVE_RELATIONSHIP") {
|
|
144
|
-
const result = await this.executeRemoveRelationshipAction(job, action, startTime, indexTxn);
|
|
145
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
146
|
-
if (error !== null) {
|
|
147
|
-
return {
|
|
148
|
-
success: false,
|
|
149
|
-
generatedOperations,
|
|
150
|
-
operationsWithContext,
|
|
151
|
-
error: error.error,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
actionIndex++;
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
let document;
|
|
158
|
-
try {
|
|
159
|
-
document = await this.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
return {
|
|
163
|
-
success: false,
|
|
164
|
-
generatedOperations,
|
|
165
|
-
operationsWithContext,
|
|
166
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
const documentState = document.state.document;
|
|
170
|
-
if (documentState.isDeleted) {
|
|
171
|
-
return {
|
|
172
|
-
success: false,
|
|
173
|
-
generatedOperations,
|
|
174
|
-
operationsWithContext,
|
|
175
|
-
error: new DocumentDeletedError(job.documentId, documentState.deletedAtUtcIso),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
let module;
|
|
179
|
-
try {
|
|
180
|
-
module = this.registry.getModule(document.header.documentType);
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
return {
|
|
184
|
-
success: false,
|
|
185
|
-
generatedOperations,
|
|
186
|
-
operationsWithContext,
|
|
187
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
let updatedDocument;
|
|
191
|
-
try {
|
|
192
|
-
updatedDocument = module.reducer(document, action);
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
const contextMessage = `Failed to apply action to document:\n Action type: ${action.type}\n Document ID: ${job.documentId}\n Document type: ${document.header.documentType}\n Scope: ${job.scope}\n Original error: ${error instanceof Error ? error.message : String(error)}`;
|
|
196
|
-
const enhancedError = new Error(contextMessage);
|
|
197
|
-
if (error instanceof Error && error.stack) {
|
|
198
|
-
enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
success: false,
|
|
202
|
-
generatedOperations,
|
|
203
|
-
operationsWithContext,
|
|
204
|
-
error: enhancedError,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
const scope = job.scope;
|
|
208
|
-
const operations = updatedDocument.operations[scope];
|
|
209
|
-
if (operations.length === 0) {
|
|
210
|
-
return {
|
|
211
|
-
success: false,
|
|
212
|
-
generatedOperations,
|
|
213
|
-
operationsWithContext,
|
|
214
|
-
error: new Error("No operation generated from action"),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
const newOperation = operations[operations.length - 1];
|
|
218
|
-
if (skipValues && actionIndex < skipValues.length) {
|
|
219
|
-
newOperation.skip = skipValues[actionIndex];
|
|
220
|
-
}
|
|
221
|
-
generatedOperations.push(newOperation);
|
|
222
|
-
if (this.config.legacyStorageEnabled) {
|
|
223
|
-
try {
|
|
224
|
-
await this.operationStorage.addDocumentOperations(job.documentId, [newOperation], updatedDocument);
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
return {
|
|
228
|
-
success: false,
|
|
229
|
-
generatedOperations,
|
|
230
|
-
operationsWithContext,
|
|
231
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const resultingState = JSON.stringify(updatedDocument.state);
|
|
236
|
-
try {
|
|
237
|
-
await this.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
238
|
-
txn.addOperations(newOperation);
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
115
|
+
try {
|
|
116
|
+
await this.verifyActionSignatures(job, actions);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
generatedOperations,
|
|
122
|
+
operationsWithContext,
|
|
123
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
|
|
127
|
+
const action = actions[actionIndex];
|
|
128
|
+
const skip = skipValues?.[actionIndex] ?? 0;
|
|
129
|
+
const isDocumentAction = documentScopeActions.includes(action.type);
|
|
130
|
+
const result = isDocumentAction
|
|
131
|
+
? await this.executeDocumentAction(job, action, startTime, indexTxn, skip)
|
|
132
|
+
: await this.executeRegularAction(job, action, startTime, indexTxn, skip);
|
|
133
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
134
|
+
if (error !== null) {
|
|
242
135
|
return {
|
|
243
136
|
success: false,
|
|
244
137
|
generatedOperations,
|
|
245
138
|
operationsWithContext,
|
|
246
|
-
error:
|
|
139
|
+
error: error.error,
|
|
247
140
|
};
|
|
248
141
|
}
|
|
249
|
-
updatedDocument.header.revision = {
|
|
250
|
-
...updatedDocument.header.revision,
|
|
251
|
-
[scope]: newOperation.index + 1,
|
|
252
|
-
};
|
|
253
|
-
this.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
|
|
254
|
-
operationsWithContext.push({
|
|
255
|
-
operation: newOperation,
|
|
256
|
-
context: {
|
|
257
|
-
documentId: job.documentId,
|
|
258
|
-
scope,
|
|
259
|
-
branch: job.branch,
|
|
260
|
-
documentType: document.header.documentType,
|
|
261
|
-
resultingState,
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
actionIndex++;
|
|
265
142
|
}
|
|
266
143
|
return {
|
|
267
144
|
success: true,
|
|
@@ -269,6 +146,26 @@ export class SimpleJobExecutor {
|
|
|
269
146
|
operationsWithContext,
|
|
270
147
|
};
|
|
271
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Execute a document scope action (CREATE_DOCUMENT, DELETE_DOCUMENT, UPGRADE_DOCUMENT,
|
|
151
|
+
* ADD_RELATIONSHIP, REMOVE_RELATIONSHIP) by dispatching to the appropriate handler.
|
|
152
|
+
*/
|
|
153
|
+
async executeDocumentAction(job, action, startTime, indexTxn, skip = 0) {
|
|
154
|
+
switch (action.type) {
|
|
155
|
+
case "CREATE_DOCUMENT":
|
|
156
|
+
return this.executeCreateDocumentAction(job, action, startTime, indexTxn, skip);
|
|
157
|
+
case "DELETE_DOCUMENT":
|
|
158
|
+
return this.executeDeleteDocumentAction(job, action, startTime, indexTxn);
|
|
159
|
+
case "UPGRADE_DOCUMENT":
|
|
160
|
+
return this.executeUpgradeDocumentAction(job, action, startTime, indexTxn, skip);
|
|
161
|
+
case "ADD_RELATIONSHIP":
|
|
162
|
+
return this.executeAddRelationshipAction(job, action, startTime, indexTxn);
|
|
163
|
+
case "REMOVE_RELATIONSHIP":
|
|
164
|
+
return this.executeRemoveRelationshipAction(job, action, startTime, indexTxn);
|
|
165
|
+
default:
|
|
166
|
+
return this.buildErrorResult(job, new Error(`Unknown document action type: ${action.type}`), startTime);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
272
169
|
/**
|
|
273
170
|
* Execute a CREATE_DOCUMENT system action.
|
|
274
171
|
* This creates a new document in storage along with its initial operation.
|
|
@@ -333,6 +230,11 @@ export class SimpleJobExecutor {
|
|
|
333
230
|
indexTxn.createCollection(collectionId);
|
|
334
231
|
indexTxn.addToCollection(collectionId, document.header.id);
|
|
335
232
|
}
|
|
233
|
+
this.documentMetaCache.putDocumentMeta(document.header.id, job.branch, {
|
|
234
|
+
state: document.state.document,
|
|
235
|
+
documentType: document.header.documentType,
|
|
236
|
+
documentScopeRevision: 1,
|
|
237
|
+
});
|
|
336
238
|
return this.buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
|
|
337
239
|
}
|
|
338
240
|
/**
|
|
@@ -390,11 +292,16 @@ export class SimpleJobExecutor {
|
|
|
390
292
|
scope: job.scope,
|
|
391
293
|
},
|
|
392
294
|
]);
|
|
295
|
+
this.documentMetaCache.putDocumentMeta(documentId, job.branch, {
|
|
296
|
+
state: document.state.document,
|
|
297
|
+
documentType: document.header.documentType,
|
|
298
|
+
documentScopeRevision: operation.index + 1,
|
|
299
|
+
});
|
|
393
300
|
return this.buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
394
301
|
}
|
|
395
302
|
/**
|
|
396
303
|
* Execute an UPGRADE_DOCUMENT system action.
|
|
397
|
-
*
|
|
304
|
+
* Handles initial upgrades (version 0 to N), same-version no-ops, and multi-step upgrade chains.
|
|
398
305
|
* The operation index is determined from the document's current operation count.
|
|
399
306
|
*/
|
|
400
307
|
async executeUpgradeDocumentAction(job, action, startTime, indexTxn, skip = 0) {
|
|
@@ -403,6 +310,8 @@ export class SimpleJobExecutor {
|
|
|
403
310
|
return this.buildErrorResult(job, new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
404
311
|
}
|
|
405
312
|
const documentId = input.documentId;
|
|
313
|
+
const fromVersion = input.fromVersion;
|
|
314
|
+
const toVersion = input.toVersion;
|
|
406
315
|
let document;
|
|
407
316
|
try {
|
|
408
317
|
document = await this.writeCache.getState(documentId, job.scope, job.branch);
|
|
@@ -415,7 +324,30 @@ export class SimpleJobExecutor {
|
|
|
415
324
|
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
416
325
|
}
|
|
417
326
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
418
|
-
|
|
327
|
+
let upgradePath;
|
|
328
|
+
if (fromVersion > 0 && fromVersion < toVersion) {
|
|
329
|
+
try {
|
|
330
|
+
upgradePath = this.registry.computeUpgradePath(document.header.documentType, fromVersion, toVersion);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (fromVersion === toVersion && fromVersion > 0) {
|
|
337
|
+
return {
|
|
338
|
+
job,
|
|
339
|
+
success: true,
|
|
340
|
+
operations: [],
|
|
341
|
+
operationsWithContext: [],
|
|
342
|
+
duration: Date.now() - startTime,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
document = applyUpgradeDocumentAction(document, action, upgradePath);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
350
|
+
}
|
|
419
351
|
const operation = this.createOperation(action, nextIndex, skip);
|
|
420
352
|
// Write the updated document to legacy storage
|
|
421
353
|
if (this.config.legacyStorageEnabled) {
|
|
@@ -447,6 +379,11 @@ export class SimpleJobExecutor {
|
|
|
447
379
|
scope: job.scope,
|
|
448
380
|
},
|
|
449
381
|
]);
|
|
382
|
+
this.documentMetaCache.putDocumentMeta(documentId, job.branch, {
|
|
383
|
+
state: document.state.document,
|
|
384
|
+
documentType: document.header.documentType,
|
|
385
|
+
documentScopeRevision: operation.index + 1,
|
|
386
|
+
});
|
|
450
387
|
return this.buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
451
388
|
}
|
|
452
389
|
async executeAddRelationshipAction(job, action, startTime, indexTxn) {
|
|
@@ -567,6 +504,107 @@ export class SimpleJobExecutor {
|
|
|
567
504
|
}
|
|
568
505
|
return this.buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
569
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Execute a regular document action by applying it through the document model reducer.
|
|
509
|
+
*/
|
|
510
|
+
async executeRegularAction(job, action, startTime, indexTxn, skip = 0) {
|
|
511
|
+
let docMeta;
|
|
512
|
+
try {
|
|
513
|
+
docMeta = await this.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
517
|
+
}
|
|
518
|
+
if (docMeta.state.isDeleted) {
|
|
519
|
+
return this.buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
520
|
+
}
|
|
521
|
+
let document;
|
|
522
|
+
try {
|
|
523
|
+
document = await this.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
527
|
+
}
|
|
528
|
+
let module;
|
|
529
|
+
try {
|
|
530
|
+
// Use document version to get the correct module
|
|
531
|
+
// Version 0 means not yet upgraded - use latest version
|
|
532
|
+
const moduleVersion = docMeta.state.version === 0 ? undefined : docMeta.state.version;
|
|
533
|
+
module = this.registry.getModule(document.header.documentType, moduleVersion);
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
537
|
+
}
|
|
538
|
+
let updatedDocument;
|
|
539
|
+
try {
|
|
540
|
+
updatedDocument = module.reducer(document, action);
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
const contextMessage = `Failed to apply action to document:\n Action type: ${action.type}\n Document ID: ${job.documentId}\n Document type: ${document.header.documentType}\n Scope: ${job.scope}\n Original error: ${error instanceof Error ? error.message : String(error)}`;
|
|
544
|
+
const enhancedError = new Error(contextMessage);
|
|
545
|
+
if (error instanceof Error && error.stack) {
|
|
546
|
+
enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
|
|
547
|
+
}
|
|
548
|
+
return this.buildErrorResult(job, enhancedError, startTime);
|
|
549
|
+
}
|
|
550
|
+
const scope = job.scope;
|
|
551
|
+
const operations = updatedDocument.operations[scope];
|
|
552
|
+
if (operations.length === 0) {
|
|
553
|
+
return this.buildErrorResult(job, new Error("No operation generated from action"), startTime);
|
|
554
|
+
}
|
|
555
|
+
const newOperation = operations[operations.length - 1];
|
|
556
|
+
newOperation.skip = skip;
|
|
557
|
+
if (this.config.legacyStorageEnabled) {
|
|
558
|
+
try {
|
|
559
|
+
await this.operationStorage.addDocumentOperations(job.documentId, [newOperation], updatedDocument);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const resultingState = JSON.stringify(updatedDocument.state);
|
|
566
|
+
try {
|
|
567
|
+
await this.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
568
|
+
txn.addOperations(newOperation);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
return this.buildErrorResult(job, new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
573
|
+
}
|
|
574
|
+
updatedDocument.header.revision = {
|
|
575
|
+
...updatedDocument.header.revision,
|
|
576
|
+
[scope]: newOperation.index + 1,
|
|
577
|
+
};
|
|
578
|
+
this.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
|
|
579
|
+
indexTxn.write([
|
|
580
|
+
{
|
|
581
|
+
...newOperation,
|
|
582
|
+
documentId: job.documentId,
|
|
583
|
+
documentType: document.header.documentType,
|
|
584
|
+
branch: job.branch,
|
|
585
|
+
scope,
|
|
586
|
+
},
|
|
587
|
+
]);
|
|
588
|
+
return {
|
|
589
|
+
job,
|
|
590
|
+
success: true,
|
|
591
|
+
operations: [newOperation],
|
|
592
|
+
operationsWithContext: [
|
|
593
|
+
{
|
|
594
|
+
operation: newOperation,
|
|
595
|
+
context: {
|
|
596
|
+
documentId: job.documentId,
|
|
597
|
+
scope,
|
|
598
|
+
branch: job.branch,
|
|
599
|
+
documentType: document.header.documentType,
|
|
600
|
+
resultingState,
|
|
601
|
+
ordinal: 0,
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
],
|
|
605
|
+
duration: Date.now() - startTime,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
570
608
|
createOperation(action, index, skip = 0) {
|
|
571
609
|
return {
|
|
572
610
|
index: index,
|
|
@@ -620,18 +658,10 @@ export class SimpleJobExecutor {
|
|
|
620
658
|
duration: Date.now() - startTime,
|
|
621
659
|
};
|
|
622
660
|
}
|
|
623
|
-
if (result.operationsWithContext.length > 0) {
|
|
624
|
-
const event = {
|
|
625
|
-
jobId: job.id,
|
|
626
|
-
operations: result.operationsWithContext,
|
|
627
|
-
};
|
|
628
|
-
this.eventBus
|
|
629
|
-
.emit(OperationEventTypes.OPERATION_WRITTEN, event)
|
|
630
|
-
.catch(() => {
|
|
631
|
-
// TODO: log error channel once logging is wired
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
661
|
this.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
662
|
+
if (scope === "document") {
|
|
663
|
+
this.documentMetaCache.invalidate(job.documentId, job.branch);
|
|
664
|
+
}
|
|
635
665
|
return {
|
|
636
666
|
job,
|
|
637
667
|
success: true,
|
|
@@ -679,6 +709,7 @@ export class SimpleJobExecutor {
|
|
|
679
709
|
branch: job.branch,
|
|
680
710
|
documentType: documentType,
|
|
681
711
|
resultingState,
|
|
712
|
+
ordinal: 0,
|
|
682
713
|
},
|
|
683
714
|
},
|
|
684
715
|
],
|
|
@@ -693,6 +724,66 @@ export class SimpleJobExecutor {
|
|
|
693
724
|
duration: Date.now() - startTime,
|
|
694
725
|
};
|
|
695
726
|
}
|
|
727
|
+
async verifyOperationSignatures(job, operations) {
|
|
728
|
+
if (!this.signatureVerifier) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
for (let i = 0; i < operations.length; i++) {
|
|
732
|
+
const operation = operations[i];
|
|
733
|
+
const signer = operation.action.context?.signer;
|
|
734
|
+
if (!signer) {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (signer.signatures.length === 0) {
|
|
738
|
+
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id ?? "unknown"} at index ${operation.index} has signer but no signatures`);
|
|
739
|
+
}
|
|
740
|
+
const publicKey = signer.app.key;
|
|
741
|
+
let isValid = false;
|
|
742
|
+
try {
|
|
743
|
+
isValid = await this.signatureVerifier(operation, publicKey);
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
747
|
+
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id ?? "unknown"} at index ${operation.index} verification failed: ${errorMessage}`);
|
|
748
|
+
}
|
|
749
|
+
if (!isValid) {
|
|
750
|
+
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id ?? "unknown"} at index ${operation.index} signature verification returned false`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async verifyActionSignatures(job, actions) {
|
|
755
|
+
if (!this.signatureVerifier) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
for (const action of actions) {
|
|
759
|
+
const signer = action.context?.signer;
|
|
760
|
+
if (!signer) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (signer.signatures.length === 0) {
|
|
764
|
+
throw new InvalidSignatureError(job.documentId, `Action ${action.id} has signer but no signatures`);
|
|
765
|
+
}
|
|
766
|
+
const publicKey = signer.app.key;
|
|
767
|
+
let isValid = false;
|
|
768
|
+
try {
|
|
769
|
+
const tempOperation = {
|
|
770
|
+
index: 0,
|
|
771
|
+
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
772
|
+
hash: "",
|
|
773
|
+
skip: 0,
|
|
774
|
+
action: action,
|
|
775
|
+
};
|
|
776
|
+
isValid = await this.signatureVerifier(tempOperation, publicKey);
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
780
|
+
throw new InvalidSignatureError(job.documentId, `Action ${action.id} verification failed: ${errorMessage}`);
|
|
781
|
+
}
|
|
782
|
+
if (!isValid) {
|
|
783
|
+
throw new InvalidSignatureError(job.documentId, `Action ${action.id} signature verification returned false`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
696
787
|
accumulateResultOrReturnError(result, generatedOperations, operationsWithContext) {
|
|
697
788
|
if (!result.success) {
|
|
698
789
|
return result;
|