@powerhousedao/reactor 5.2.0-staging.9 → 5.3.0-staging.1
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/actions/index.d.ts +24 -0
- package/dist/src/actions/index.d.ts.map +1 -0
- package/dist/src/actions/index.js +76 -0
- package/dist/src/actions/index.js.map +1 -0
- package/dist/src/cache/document-meta-cache.d.ts.map +1 -1
- package/dist/src/cache/document-meta-cache.js +2 -1
- package/dist/src/cache/document-meta-cache.js.map +1 -1
- package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
- package/dist/src/cache/kysely-operation-index.js +14 -25
- 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 +3 -2
- package/dist/src/cache/kysely-write-cache.js.map +1 -1
- package/dist/src/cache/operation-index-types.d.ts +1 -1
- package/dist/src/client/reactor-client.d.ts +16 -4
- package/dist/src/client/reactor-client.d.ts.map +1 -1
- package/dist/src/client/reactor-client.js +97 -3
- package/dist/src/client/reactor-client.js.map +1 -1
- package/dist/src/client/types.d.ts +22 -3
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.d.ts +10 -9
- package/dist/src/core/reactor-builder.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.js +52 -14
- package/dist/src/core/reactor-builder.js.map +1 -1
- package/dist/src/core/reactor-client-builder.d.ts +10 -1
- package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
- package/dist/src/core/reactor-client-builder.js +16 -1
- package/dist/src/core/reactor-client-builder.js.map +1 -1
- package/dist/src/core/reactor.d.ts +9 -8
- package/dist/src/core/reactor.d.ts.map +1 -1
- package/dist/src/core/reactor.js +61 -74
- package/dist/src/core/reactor.js.map +1 -1
- package/dist/src/core/types.d.ts +22 -7
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/utils.d.ts +1 -2
- package/dist/src/core/utils.d.ts.map +1 -1
- package/dist/src/core/utils.js +1 -1
- 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 +3 -1
- package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.js +10 -8
- package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
- package/dist/src/executor/simple-job-executor.d.ts +15 -3
- package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor.js +288 -228
- 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 +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 +6 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging/console.d.ts +11 -1
- package/dist/src/logging/console.d.ts.map +1 -1
- package/dist/src/logging/console.js +45 -14
- package/dist/src/logging/console.js.map +1 -1
- package/dist/src/logging/types.d.ts +1 -0
- package/dist/src/logging/types.d.ts.map +1 -1
- package/dist/src/processors/index.d.ts +3 -0
- package/dist/src/processors/index.d.ts.map +1 -0
- package/dist/src/processors/index.js +2 -0
- package/dist/src/processors/index.js.map +1 -0
- package/dist/src/processors/processor-manager.d.ts +38 -0
- package/dist/src/processors/processor-manager.d.ts.map +1 -0
- package/dist/src/processors/processor-manager.js +165 -0
- package/dist/src/processors/processor-manager.js.map +1 -0
- package/dist/src/processors/types.d.ts +63 -0
- package/dist/src/processors/types.d.ts.map +1 -0
- package/dist/src/processors/types.js +2 -0
- package/dist/src/processors/types.js.map +1 -0
- package/dist/src/processors/utils.d.ts +10 -0
- package/dist/src/processors/utils.d.ts.map +1 -0
- package/dist/src/processors/utils.js +58 -0
- package/dist/src/processors/utils.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/coordinator.d.ts +3 -2
- package/dist/src/read-models/coordinator.d.ts.map +1 -1
- package/dist/src/read-models/coordinator.js +12 -13
- package/dist/src/read-models/coordinator.js.map +1 -1
- package/dist/src/read-models/document-view.d.ts.map +1 -1
- package/dist/src/read-models/document-view.js +2 -0
- package/dist/src/read-models/document-view.js.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 +16 -0
- package/dist/src/shared/errors.d.ts.map +1 -1
- package/dist/src/shared/errors.js +28 -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 +2 -22
- package/dist/src/signer/types.d.ts.map +1 -1
- package/dist/src/storage/interfaces.d.ts +13 -1
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/interfaces.js +2 -2
- package/dist/src/storage/interfaces.js.map +1 -1
- package/dist/src/storage/kysely/store.d.ts +1 -0
- package/dist/src/storage/kysely/store.d.ts.map +1 -1
- package/dist/src/storage/kysely/store.js +40 -4
- package/dist/src/storage/kysely/store.js.map +1 -1
- package/dist/src/storage/kysely/sync-cursor-storage.js +2 -2
- package/dist/src/storage/kysely/sync-cursor-storage.js.map +1 -1
- package/dist/src/storage/kysely/sync-remote-storage.js +8 -8
- package/dist/src/storage/kysely/sync-remote-storage.js.map +1 -1
- package/dist/src/storage/kysely/types.d.ts +6 -6
- package/dist/src/storage/migrations/001_create_operation_table.d.ts.map +1 -1
- package/dist/src/storage/migrations/001_create_operation_table.js +2 -1
- package/dist/src/storage/migrations/001_create_operation_table.js.map +1 -1
- package/dist/src/storage/migrations/009_create_operation_index_tables.js +1 -1
- package/dist/src/storage/migrations/009_create_operation_index_tables.js.map +1 -1
- package/dist/src/storage/migrations/010_create_sync_tables.js +5 -5
- package/dist/src/storage/migrations/010_create_sync_tables.js.map +1 -1
- package/dist/src/storage/migrations/migrator.d.ts +3 -2
- package/dist/src/storage/migrations/migrator.d.ts.map +1 -1
- package/dist/src/storage/migrations/migrator.js +29 -6
- package/dist/src/storage/migrations/migrator.js.map +1 -1
- package/dist/src/storage/txn.d.ts.map +1 -1
- package/dist/src/storage/txn.js +2 -3
- package/dist/src/storage/txn.js.map +1 -1
- package/dist/src/subs/subscription-notification-read-model.d.ts +17 -0
- package/dist/src/subs/subscription-notification-read-model.d.ts.map +1 -0
- package/dist/src/subs/subscription-notification-read-model.js +62 -0
- package/dist/src/subs/subscription-notification-read-model.js.map +1 -0
- package/dist/src/sync/channels/composite-channel-factory.d.ts +3 -0
- package/dist/src/sync/channels/composite-channel-factory.d.ts.map +1 -1
- package/dist/src/sync/channels/composite-channel-factory.js +5 -1
- package/dist/src/sync/channels/composite-channel-factory.js.map +1 -1
- package/dist/src/sync/channels/gql-channel-factory.d.ts +3 -0
- package/dist/src/sync/channels/gql-channel-factory.d.ts.map +1 -1
- package/dist/src/sync/channels/gql-channel-factory.js +5 -1
- package/dist/src/sync/channels/gql-channel-factory.js.map +1 -1
- package/dist/src/sync/channels/gql-channel.d.ts +15 -1
- package/dist/src/sync/channels/gql-channel.d.ts.map +1 -1
- package/dist/src/sync/channels/gql-channel.js +77 -16
- package/dist/src/sync/channels/gql-channel.js.map +1 -1
- package/dist/src/sync/channels/polling-channel.d.ts.map +1 -1
- package/dist/src/sync/channels/polling-channel.js +7 -5
- package/dist/src/sync/channels/polling-channel.js.map +1 -1
- package/dist/src/sync/channels/utils.d.ts +17 -2
- package/dist/src/sync/channels/utils.d.ts.map +1 -1
- package/dist/src/sync/channels/utils.js +76 -6
- package/dist/src/sync/channels/utils.js.map +1 -1
- package/dist/src/sync/sync-builder.d.ts +3 -2
- package/dist/src/sync/sync-builder.d.ts.map +1 -1
- package/dist/src/sync/sync-builder.js +4 -4
- package/dist/src/sync/sync-builder.js.map +1 -1
- package/dist/src/sync/sync-manager.d.ts +3 -2
- package/dist/src/sync/sync-manager.d.ts.map +1 -1
- package/dist/src/sync/sync-manager.js +17 -13
- package/dist/src/sync/sync-manager.js.map +1 -1
- package/dist/src/sync/utils.d.ts +19 -0
- package/dist/src/sync/utils.d.ts.map +1 -1
- package/dist/src/sync/utils.js +44 -0
- package/dist/src/sync/utils.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { deriveOperationId, isUndoRedo } from "document-model/core";
|
|
1
2
|
import { driveCollectionId } from "../cache/operation-index-types.js";
|
|
2
3
|
import { OperationEventTypes, } from "../events/types.js";
|
|
3
4
|
import { DocumentDeletedError, InvalidSignatureError, } from "../shared/errors.js";
|
|
4
5
|
import { reshuffleByTimestampAndIndex } from "../utils/reshuffle.js";
|
|
5
6
|
import { applyDeleteDocumentAction, applyUpgradeDocumentAction, createDocumentFromAction, getNextIndexForScope, } from "./util.js";
|
|
6
7
|
const MAX_SKIP_THRESHOLD = 100;
|
|
8
|
+
const documentScopeActions = [
|
|
9
|
+
"CREATE_DOCUMENT",
|
|
10
|
+
"DELETE_DOCUMENT",
|
|
11
|
+
"UPGRADE_DOCUMENT",
|
|
12
|
+
"ADD_RELATIONSHIP",
|
|
13
|
+
"REMOVE_RELATIONSHIP",
|
|
14
|
+
];
|
|
7
15
|
/**
|
|
8
16
|
* Simple job executor that processes a job by applying actions through document model reducers.
|
|
9
17
|
*
|
|
@@ -12,6 +20,7 @@ const MAX_SKIP_THRESHOLD = 100;
|
|
|
12
20
|
* @see docs/planning/Jobs/reshuffle.md for skip mechanism details
|
|
13
21
|
*/
|
|
14
22
|
export class SimpleJobExecutor {
|
|
23
|
+
logger;
|
|
15
24
|
registry;
|
|
16
25
|
documentStorage;
|
|
17
26
|
operationStorage;
|
|
@@ -22,7 +31,8 @@ export class SimpleJobExecutor {
|
|
|
22
31
|
documentMetaCache;
|
|
23
32
|
signatureVerifier;
|
|
24
33
|
config;
|
|
25
|
-
constructor(registry, documentStorage, operationStorage, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, config, signatureVerifier) {
|
|
34
|
+
constructor(logger, registry, documentStorage, operationStorage, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, config, signatureVerifier) {
|
|
35
|
+
this.logger = logger;
|
|
26
36
|
this.registry = registry;
|
|
27
37
|
this.documentStorage = documentStorage;
|
|
28
38
|
this.operationStorage = operationStorage;
|
|
@@ -33,6 +43,7 @@ export class SimpleJobExecutor {
|
|
|
33
43
|
this.documentMetaCache = documentMetaCache;
|
|
34
44
|
this.signatureVerifier = signatureVerifier;
|
|
35
45
|
this.config = {
|
|
46
|
+
maxSkipThreshold: config.maxSkipThreshold ?? MAX_SKIP_THRESHOLD,
|
|
36
47
|
maxConcurrency: config.maxConcurrency ?? 1,
|
|
37
48
|
jobTimeoutMs: config.jobTimeoutMs ?? 30000,
|
|
38
49
|
retryBaseDelayMs: config.retryBaseDelayMs ?? 100,
|
|
@@ -58,6 +69,7 @@ export class SimpleJobExecutor {
|
|
|
58
69
|
const event = {
|
|
59
70
|
jobId: job.id,
|
|
60
71
|
operations: result.operationsWithContext,
|
|
72
|
+
jobMeta: job.meta,
|
|
61
73
|
};
|
|
62
74
|
this.eventBus
|
|
63
75
|
.emit(OperationEventTypes.OPERATION_WRITTEN, event)
|
|
@@ -85,6 +97,7 @@ export class SimpleJobExecutor {
|
|
|
85
97
|
const event = {
|
|
86
98
|
jobId: job.id,
|
|
87
99
|
operations: result.operationsWithContext,
|
|
100
|
+
jobMeta: job.meta,
|
|
88
101
|
};
|
|
89
102
|
this.eventBus
|
|
90
103
|
.emit(OperationEventTypes.OPERATION_WRITTEN, event)
|
|
@@ -100,7 +113,7 @@ export class SimpleJobExecutor {
|
|
|
100
113
|
duration: Date.now() - startTime,
|
|
101
114
|
};
|
|
102
115
|
}
|
|
103
|
-
async processActions(job, actions, startTime, indexTxn, skipValues) {
|
|
116
|
+
async processActions(job, actions, startTime, indexTxn, skipValues, sourceOperations) {
|
|
104
117
|
const generatedOperations = [];
|
|
105
118
|
const operationsWithContext = [];
|
|
106
119
|
try {
|
|
@@ -114,207 +127,23 @@ export class SimpleJobExecutor {
|
|
|
114
127
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
115
128
|
};
|
|
116
129
|
}
|
|
117
|
-
let actionIndex = 0;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
error: error.error,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
actionIndex++;
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (action.type === "DELETE_DOCUMENT") {
|
|
134
|
-
const result = await this.executeDeleteDocumentAction(job, action, startTime, indexTxn);
|
|
135
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
136
|
-
if (error !== null) {
|
|
137
|
-
return {
|
|
138
|
-
success: false,
|
|
139
|
-
generatedOperations,
|
|
140
|
-
operationsWithContext,
|
|
141
|
-
error: error.error,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
actionIndex++;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (action.type === "UPGRADE_DOCUMENT") {
|
|
148
|
-
const result = await this.executeUpgradeDocumentAction(job, action, startTime, indexTxn, skipValues?.[actionIndex]);
|
|
149
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
150
|
-
if (error !== null) {
|
|
151
|
-
return {
|
|
152
|
-
success: false,
|
|
153
|
-
generatedOperations,
|
|
154
|
-
operationsWithContext,
|
|
155
|
-
error: error.error,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
actionIndex++;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (action.type === "ADD_RELATIONSHIP") {
|
|
162
|
-
const result = await this.executeAddRelationshipAction(job, action, startTime, indexTxn);
|
|
163
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
164
|
-
if (error !== null) {
|
|
165
|
-
return {
|
|
166
|
-
success: false,
|
|
167
|
-
generatedOperations,
|
|
168
|
-
operationsWithContext,
|
|
169
|
-
error: error.error,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
actionIndex++;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (action.type === "REMOVE_RELATIONSHIP") {
|
|
176
|
-
const result = await this.executeRemoveRelationshipAction(job, action, startTime, indexTxn);
|
|
177
|
-
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
178
|
-
if (error !== null) {
|
|
179
|
-
return {
|
|
180
|
-
success: false,
|
|
181
|
-
generatedOperations,
|
|
182
|
-
operationsWithContext,
|
|
183
|
-
error: error.error,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
actionIndex++;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
let docMeta;
|
|
190
|
-
try {
|
|
191
|
-
docMeta = await this.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
130
|
+
for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
|
|
131
|
+
const action = actions[actionIndex];
|
|
132
|
+
const skip = skipValues?.[actionIndex] ?? 0;
|
|
133
|
+
const sourceOperation = sourceOperations?.[actionIndex];
|
|
134
|
+
const isDocumentAction = documentScopeActions.includes(action.type);
|
|
135
|
+
const result = isDocumentAction
|
|
136
|
+
? await this.executeDocumentAction(job, action, startTime, indexTxn, skip)
|
|
137
|
+
: await this.executeRegularAction(job, action, startTime, indexTxn, skip, sourceOperation);
|
|
138
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
139
|
+
if (error !== null) {
|
|
194
140
|
return {
|
|
195
141
|
success: false,
|
|
196
142
|
generatedOperations,
|
|
197
143
|
operationsWithContext,
|
|
198
|
-
error: error
|
|
144
|
+
error: error.error,
|
|
199
145
|
};
|
|
200
146
|
}
|
|
201
|
-
if (docMeta.state.isDeleted) {
|
|
202
|
-
return {
|
|
203
|
-
success: false,
|
|
204
|
-
generatedOperations,
|
|
205
|
-
operationsWithContext,
|
|
206
|
-
error: new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso),
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
let document;
|
|
210
|
-
try {
|
|
211
|
-
document = await this.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
return {
|
|
215
|
-
success: false,
|
|
216
|
-
generatedOperations,
|
|
217
|
-
operationsWithContext,
|
|
218
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
let module;
|
|
222
|
-
try {
|
|
223
|
-
module = this.registry.getModule(document.header.documentType);
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
return {
|
|
227
|
-
success: false,
|
|
228
|
-
generatedOperations,
|
|
229
|
-
operationsWithContext,
|
|
230
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
let updatedDocument;
|
|
234
|
-
try {
|
|
235
|
-
updatedDocument = module.reducer(document, action);
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
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)}`;
|
|
239
|
-
const enhancedError = new Error(contextMessage);
|
|
240
|
-
if (error instanceof Error && error.stack) {
|
|
241
|
-
enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
success: false,
|
|
245
|
-
generatedOperations,
|
|
246
|
-
operationsWithContext,
|
|
247
|
-
error: enhancedError,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
const scope = job.scope;
|
|
251
|
-
const operations = updatedDocument.operations[scope];
|
|
252
|
-
if (operations.length === 0) {
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
generatedOperations,
|
|
256
|
-
operationsWithContext,
|
|
257
|
-
error: new Error("No operation generated from action"),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
const newOperation = operations[operations.length - 1];
|
|
261
|
-
if (skipValues && actionIndex < skipValues.length) {
|
|
262
|
-
newOperation.skip = skipValues[actionIndex];
|
|
263
|
-
}
|
|
264
|
-
generatedOperations.push(newOperation);
|
|
265
|
-
if (this.config.legacyStorageEnabled) {
|
|
266
|
-
try {
|
|
267
|
-
await this.operationStorage.addDocumentOperations(job.documentId, [newOperation], updatedDocument);
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
return {
|
|
271
|
-
success: false,
|
|
272
|
-
generatedOperations,
|
|
273
|
-
operationsWithContext,
|
|
274
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
const resultingState = JSON.stringify(updatedDocument.state);
|
|
279
|
-
try {
|
|
280
|
-
await this.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
281
|
-
txn.addOperations(newOperation);
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
return {
|
|
286
|
-
success: false,
|
|
287
|
-
generatedOperations,
|
|
288
|
-
operationsWithContext,
|
|
289
|
-
error: new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
updatedDocument.header.revision = {
|
|
293
|
-
...updatedDocument.header.revision,
|
|
294
|
-
[scope]: newOperation.index + 1,
|
|
295
|
-
};
|
|
296
|
-
this.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
|
|
297
|
-
indexTxn.write([
|
|
298
|
-
{
|
|
299
|
-
...newOperation,
|
|
300
|
-
documentId: job.documentId,
|
|
301
|
-
documentType: document.header.documentType,
|
|
302
|
-
branch: job.branch,
|
|
303
|
-
scope,
|
|
304
|
-
},
|
|
305
|
-
]);
|
|
306
|
-
operationsWithContext.push({
|
|
307
|
-
operation: newOperation,
|
|
308
|
-
context: {
|
|
309
|
-
documentId: job.documentId,
|
|
310
|
-
scope,
|
|
311
|
-
branch: job.branch,
|
|
312
|
-
documentType: document.header.documentType,
|
|
313
|
-
resultingState,
|
|
314
|
-
ordinal: 0,
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
actionIndex++;
|
|
318
147
|
}
|
|
319
148
|
return {
|
|
320
149
|
success: true,
|
|
@@ -322,6 +151,26 @@ export class SimpleJobExecutor {
|
|
|
322
151
|
operationsWithContext,
|
|
323
152
|
};
|
|
324
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Execute a document scope action (CREATE_DOCUMENT, DELETE_DOCUMENT, UPGRADE_DOCUMENT,
|
|
156
|
+
* ADD_RELATIONSHIP, REMOVE_RELATIONSHIP) by dispatching to the appropriate handler.
|
|
157
|
+
*/
|
|
158
|
+
async executeDocumentAction(job, action, startTime, indexTxn, skip = 0) {
|
|
159
|
+
switch (action.type) {
|
|
160
|
+
case "CREATE_DOCUMENT":
|
|
161
|
+
return this.executeCreateDocumentAction(job, action, startTime, indexTxn, skip);
|
|
162
|
+
case "DELETE_DOCUMENT":
|
|
163
|
+
return this.executeDeleteDocumentAction(job, action, startTime, indexTxn);
|
|
164
|
+
case "UPGRADE_DOCUMENT":
|
|
165
|
+
return this.executeUpgradeDocumentAction(job, action, startTime, indexTxn, skip);
|
|
166
|
+
case "ADD_RELATIONSHIP":
|
|
167
|
+
return this.executeAddRelationshipAction(job, action, startTime, indexTxn);
|
|
168
|
+
case "REMOVE_RELATIONSHIP":
|
|
169
|
+
return this.executeRemoveRelationshipAction(job, action, startTime, indexTxn);
|
|
170
|
+
default:
|
|
171
|
+
return this.buildErrorResult(job, new Error(`Unknown document action type: ${action.type}`), startTime);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
325
174
|
/**
|
|
326
175
|
* Execute a CREATE_DOCUMENT system action.
|
|
327
176
|
* This creates a new document in storage along with its initial operation.
|
|
@@ -346,7 +195,11 @@ export class SimpleJobExecutor {
|
|
|
346
195
|
return this.buildErrorResult(job, new Error(`Failed to create document in storage: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
347
196
|
}
|
|
348
197
|
}
|
|
349
|
-
const operation = this.createOperation(action, 0, skip
|
|
198
|
+
const operation = this.createOperation(action, 0, skip, {
|
|
199
|
+
documentId: document.header.id,
|
|
200
|
+
scope: job.scope,
|
|
201
|
+
branch: job.branch,
|
|
202
|
+
});
|
|
350
203
|
// Legacy: Write the CREATE_DOCUMENT operation to legacy storage
|
|
351
204
|
if (this.config.legacyStorageEnabled) {
|
|
352
205
|
try {
|
|
@@ -417,7 +270,11 @@ export class SimpleJobExecutor {
|
|
|
417
270
|
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
418
271
|
}
|
|
419
272
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
420
|
-
const operation = this.createOperation(action, nextIndex
|
|
273
|
+
const operation = this.createOperation(action, nextIndex, 0, {
|
|
274
|
+
documentId,
|
|
275
|
+
scope: job.scope,
|
|
276
|
+
branch: job.branch,
|
|
277
|
+
});
|
|
421
278
|
if (this.config.legacyStorageEnabled) {
|
|
422
279
|
try {
|
|
423
280
|
await this.documentStorage.delete(documentId);
|
|
@@ -457,7 +314,7 @@ export class SimpleJobExecutor {
|
|
|
457
314
|
}
|
|
458
315
|
/**
|
|
459
316
|
* Execute an UPGRADE_DOCUMENT system action.
|
|
460
|
-
*
|
|
317
|
+
* Handles initial upgrades (version 0 to N), same-version no-ops, and multi-step upgrade chains.
|
|
461
318
|
* The operation index is determined from the document's current operation count.
|
|
462
319
|
*/
|
|
463
320
|
async executeUpgradeDocumentAction(job, action, startTime, indexTxn, skip = 0) {
|
|
@@ -466,6 +323,8 @@ export class SimpleJobExecutor {
|
|
|
466
323
|
return this.buildErrorResult(job, new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
467
324
|
}
|
|
468
325
|
const documentId = input.documentId;
|
|
326
|
+
const fromVersion = input.fromVersion;
|
|
327
|
+
const toVersion = input.toVersion;
|
|
469
328
|
let document;
|
|
470
329
|
try {
|
|
471
330
|
document = await this.writeCache.getState(documentId, job.scope, job.branch);
|
|
@@ -478,8 +337,35 @@ export class SimpleJobExecutor {
|
|
|
478
337
|
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
479
338
|
}
|
|
480
339
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
481
|
-
|
|
482
|
-
|
|
340
|
+
let upgradePath;
|
|
341
|
+
if (fromVersion > 0 && fromVersion < toVersion) {
|
|
342
|
+
try {
|
|
343
|
+
upgradePath = this.registry.computeUpgradePath(document.header.documentType, fromVersion, toVersion);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (fromVersion === toVersion && fromVersion > 0) {
|
|
350
|
+
return {
|
|
351
|
+
job,
|
|
352
|
+
success: true,
|
|
353
|
+
operations: [],
|
|
354
|
+
operationsWithContext: [],
|
|
355
|
+
duration: Date.now() - startTime,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
document = applyUpgradeDocumentAction(document, action, upgradePath);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
363
|
+
}
|
|
364
|
+
const operation = this.createOperation(action, nextIndex, skip, {
|
|
365
|
+
documentId,
|
|
366
|
+
scope: job.scope,
|
|
367
|
+
branch: job.branch,
|
|
368
|
+
});
|
|
483
369
|
// Write the updated document to legacy storage
|
|
484
370
|
if (this.config.legacyStorageEnabled) {
|
|
485
371
|
try {
|
|
@@ -535,19 +421,12 @@ export class SimpleJobExecutor {
|
|
|
535
421
|
catch (error) {
|
|
536
422
|
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
537
423
|
}
|
|
538
|
-
let targetDoc;
|
|
539
|
-
try {
|
|
540
|
-
targetDoc = await this.writeCache.getState(input.targetId, "document", job.branch);
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP: target document ${input.targetId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
544
|
-
}
|
|
545
|
-
const targetDocState = targetDoc.state.document;
|
|
546
|
-
if (targetDocState.isDeleted) {
|
|
547
|
-
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP: target document ${input.targetId} is deleted`), startTime);
|
|
548
|
-
}
|
|
549
424
|
const nextIndex = getNextIndexForScope(sourceDoc, job.scope);
|
|
550
|
-
const operation = this.createOperation(action, nextIndex
|
|
425
|
+
const operation = this.createOperation(action, nextIndex, 0, {
|
|
426
|
+
documentId: input.sourceId,
|
|
427
|
+
scope: job.scope,
|
|
428
|
+
branch: job.branch,
|
|
429
|
+
});
|
|
551
430
|
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
552
431
|
if (writeError !== null) {
|
|
553
432
|
return writeError;
|
|
@@ -581,6 +460,11 @@ export class SimpleJobExecutor {
|
|
|
581
460
|
const collectionId = driveCollectionId(job.branch, input.sourceId);
|
|
582
461
|
indexTxn.addToCollection(collectionId, input.targetId);
|
|
583
462
|
}
|
|
463
|
+
this.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
|
|
464
|
+
state: sourceDoc.state.document,
|
|
465
|
+
documentType: sourceDoc.header.documentType,
|
|
466
|
+
documentScopeRevision: operation.index + 1,
|
|
467
|
+
});
|
|
584
468
|
return this.buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
585
469
|
}
|
|
586
470
|
async executeRemoveRelationshipAction(job, action, startTime, indexTxn) {
|
|
@@ -599,7 +483,11 @@ export class SimpleJobExecutor {
|
|
|
599
483
|
return this.buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
600
484
|
}
|
|
601
485
|
const nextIndex = getNextIndexForScope(sourceDoc, job.scope);
|
|
602
|
-
const operation = this.createOperation(action, nextIndex
|
|
486
|
+
const operation = this.createOperation(action, nextIndex, 0, {
|
|
487
|
+
documentId: input.sourceId,
|
|
488
|
+
scope: job.scope,
|
|
489
|
+
branch: job.branch,
|
|
490
|
+
});
|
|
603
491
|
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
604
492
|
if (writeError !== null) {
|
|
605
493
|
return writeError;
|
|
@@ -633,10 +521,127 @@ export class SimpleJobExecutor {
|
|
|
633
521
|
const collectionId = driveCollectionId(job.branch, input.sourceId);
|
|
634
522
|
indexTxn.removeFromCollection(collectionId, input.targetId);
|
|
635
523
|
}
|
|
524
|
+
this.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
|
|
525
|
+
state: sourceDoc.state.document,
|
|
526
|
+
documentType: sourceDoc.header.documentType,
|
|
527
|
+
documentScopeRevision: operation.index + 1,
|
|
528
|
+
});
|
|
636
529
|
return this.buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
637
530
|
}
|
|
638
|
-
|
|
531
|
+
/**
|
|
532
|
+
* Execute a regular document action by applying it through the document model reducer.
|
|
533
|
+
* If sourceOperation is provided (for load jobs), its id and timestamp are preserved.
|
|
534
|
+
*/
|
|
535
|
+
async executeRegularAction(job, action, startTime, indexTxn, skip = 0, sourceOperation) {
|
|
536
|
+
let docMeta;
|
|
537
|
+
try {
|
|
538
|
+
docMeta = await this.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
542
|
+
}
|
|
543
|
+
if (docMeta.state.isDeleted) {
|
|
544
|
+
return this.buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
545
|
+
}
|
|
546
|
+
let document;
|
|
547
|
+
try {
|
|
548
|
+
document = await this.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
552
|
+
}
|
|
553
|
+
let module;
|
|
554
|
+
try {
|
|
555
|
+
// Use document version to get the correct module
|
|
556
|
+
// Version 0 means not yet upgraded - use latest version
|
|
557
|
+
const moduleVersion = docMeta.state.version === 0 ? undefined : docMeta.state.version;
|
|
558
|
+
module = this.registry.getModule(document.header.documentType, moduleVersion);
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
562
|
+
}
|
|
563
|
+
let updatedDocument;
|
|
564
|
+
try {
|
|
565
|
+
const reducerOptions = sourceOperation
|
|
566
|
+
? {
|
|
567
|
+
skip,
|
|
568
|
+
branch: job.branch,
|
|
569
|
+
replayOptions: { operation: sourceOperation },
|
|
570
|
+
}
|
|
571
|
+
: { skip, branch: job.branch };
|
|
572
|
+
updatedDocument = module.reducer(document, action, undefined, reducerOptions);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
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)}`;
|
|
576
|
+
const enhancedError = new Error(contextMessage);
|
|
577
|
+
if (error instanceof Error && error.stack) {
|
|
578
|
+
enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
|
|
579
|
+
}
|
|
580
|
+
return this.buildErrorResult(job, enhancedError, startTime);
|
|
581
|
+
}
|
|
582
|
+
const scope = job.scope;
|
|
583
|
+
const operations = updatedDocument.operations[scope];
|
|
584
|
+
if (operations.length === 0) {
|
|
585
|
+
return this.buildErrorResult(job, new Error("No operation generated from action"), startTime);
|
|
586
|
+
}
|
|
587
|
+
const newOperation = operations[operations.length - 1];
|
|
588
|
+
if (!isUndoRedo(action)) {
|
|
589
|
+
newOperation.skip = skip;
|
|
590
|
+
}
|
|
591
|
+
if (this.config.legacyStorageEnabled) {
|
|
592
|
+
try {
|
|
593
|
+
await this.operationStorage.addDocumentOperations(job.documentId, [newOperation], updatedDocument);
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
return this.buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const resultingState = JSON.stringify({
|
|
600
|
+
...updatedDocument.state,
|
|
601
|
+
header: updatedDocument.header,
|
|
602
|
+
});
|
|
603
|
+
const writeFailResult = await this.writeOperationToStore(job.documentId, document.header.documentType, scope, job.branch, newOperation, job, startTime);
|
|
604
|
+
if (writeFailResult !== null) {
|
|
605
|
+
return writeFailResult;
|
|
606
|
+
}
|
|
607
|
+
updatedDocument.header.revision = {
|
|
608
|
+
...updatedDocument.header.revision,
|
|
609
|
+
[scope]: newOperation.index + 1,
|
|
610
|
+
};
|
|
611
|
+
this.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
|
|
612
|
+
indexTxn.write([
|
|
613
|
+
{
|
|
614
|
+
...newOperation,
|
|
615
|
+
documentId: job.documentId,
|
|
616
|
+
documentType: document.header.documentType,
|
|
617
|
+
branch: job.branch,
|
|
618
|
+
scope,
|
|
619
|
+
},
|
|
620
|
+
]);
|
|
621
|
+
return {
|
|
622
|
+
job,
|
|
623
|
+
success: true,
|
|
624
|
+
operations: [newOperation],
|
|
625
|
+
operationsWithContext: [
|
|
626
|
+
{
|
|
627
|
+
operation: newOperation,
|
|
628
|
+
context: {
|
|
629
|
+
documentId: job.documentId,
|
|
630
|
+
scope,
|
|
631
|
+
branch: job.branch,
|
|
632
|
+
documentType: document.header.documentType,
|
|
633
|
+
resultingState,
|
|
634
|
+
ordinal: 0,
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
duration: Date.now() - startTime,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
createOperation(action, index, skip = 0, context) {
|
|
642
|
+
const id = deriveOperationId(context.documentId, context.scope, context.branch, action.id);
|
|
639
643
|
return {
|
|
644
|
+
id,
|
|
640
645
|
index: index,
|
|
641
646
|
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
642
647
|
hash: "",
|
|
@@ -657,29 +662,81 @@ export class SimpleJobExecutor {
|
|
|
657
662
|
catch {
|
|
658
663
|
latestRevision = 0;
|
|
659
664
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
+
let minIncomingIndex = Number.POSITIVE_INFINITY;
|
|
666
|
+
let minIncomingTimestamp = job.operations[0]?.timestampUtcMs || "";
|
|
667
|
+
for (const operation of job.operations) {
|
|
668
|
+
minIncomingIndex = Math.min(minIncomingIndex, operation.index);
|
|
669
|
+
const ts = operation.timestampUtcMs || "";
|
|
670
|
+
if (ts < minIncomingTimestamp) {
|
|
671
|
+
minIncomingTimestamp = ts;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
let conflictingOps = [];
|
|
675
|
+
try {
|
|
676
|
+
const conflictingResult = await this.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp, { limit: this.config.maxSkipThreshold + 1 });
|
|
677
|
+
if (conflictingResult.hasMore) {
|
|
678
|
+
return {
|
|
679
|
+
job,
|
|
680
|
+
success: false,
|
|
681
|
+
error: new Error(`Excessive reshuffle detected: more than ${this.config.maxSkipThreshold} conflicting operations found. ` +
|
|
682
|
+
`This indicates a significant divergence between local and incoming operations.`),
|
|
683
|
+
duration: Date.now() - startTime,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
conflictingOps = conflictingResult.items;
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
conflictingOps = [];
|
|
690
|
+
}
|
|
691
|
+
// Filter out operations that have been superseded by later operations with skip values.
|
|
692
|
+
// An operation at index N is superseded if there exists an operation at index M > N
|
|
693
|
+
// where (M - skip_M) <= N, meaning the later operation's logical index covers N.
|
|
694
|
+
const nonSupersededOps = conflictingOps.filter((op) => {
|
|
695
|
+
for (const laterOp of conflictingOps) {
|
|
696
|
+
if (laterOp.index > op.index && laterOp.skip > 0) {
|
|
697
|
+
const logicalIndex = laterOp.index - laterOp.skip;
|
|
698
|
+
if (logicalIndex <= op.index) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return true;
|
|
704
|
+
});
|
|
705
|
+
// All non-superseded conflicting operations need to be reshuffled
|
|
706
|
+
const existingOpsToReshuffle = nonSupersededOps;
|
|
707
|
+
// Skip count is the number of existing operations that need to be rewound
|
|
708
|
+
const skipCount = existingOpsToReshuffle.length;
|
|
709
|
+
if (skipCount > this.config.maxSkipThreshold) {
|
|
665
710
|
return {
|
|
666
711
|
job,
|
|
667
712
|
success: false,
|
|
668
|
-
error: new Error(`Excessive reshuffle detected: skip count of ${skipCount} exceeds threshold of ${
|
|
669
|
-
`This indicates
|
|
713
|
+
error: new Error(`Excessive reshuffle detected: skip count of ${skipCount} exceeds threshold of ${this.config.maxSkipThreshold}. ` +
|
|
714
|
+
`This indicates a significant divergence between local and incoming operations.`),
|
|
670
715
|
duration: Date.now() - startTime,
|
|
671
716
|
};
|
|
672
717
|
}
|
|
718
|
+
// Filter out incoming operations that are duplicates (action already exists locally
|
|
719
|
+
// or appears multiple times in incoming)
|
|
720
|
+
const existingActionIds = new Set(nonSupersededOps.map((op) => op.action.id));
|
|
721
|
+
const seenIncomingActionIds = new Set();
|
|
722
|
+
const incomingOpsToApply = job.operations.filter((op) => {
|
|
723
|
+
if (existingActionIds.has(op.action.id))
|
|
724
|
+
return false;
|
|
725
|
+
if (seenIncomingActionIds.has(op.action.id))
|
|
726
|
+
return false;
|
|
727
|
+
seenIncomingActionIds.add(op.action.id);
|
|
728
|
+
return true;
|
|
729
|
+
});
|
|
673
730
|
const reshuffledOperations = reshuffleByTimestampAndIndex({
|
|
674
731
|
index: latestRevision,
|
|
675
732
|
skip: skipCount,
|
|
676
|
-
},
|
|
733
|
+
}, existingOpsToReshuffle, incomingOpsToApply.map((operation) => ({
|
|
677
734
|
...operation,
|
|
678
735
|
id: operation.id,
|
|
679
736
|
})));
|
|
680
737
|
const actions = reshuffledOperations.map((operation) => operation.action);
|
|
681
738
|
const skipValues = reshuffledOperations.map((operation) => operation.skip);
|
|
682
|
-
const result = await this.processActions(job, actions, startTime, indexTxn, skipValues);
|
|
739
|
+
const result = await this.processActions(job, actions, startTime, indexTxn, skipValues, reshuffledOperations);
|
|
683
740
|
if (!result.success) {
|
|
684
741
|
return {
|
|
685
742
|
job,
|
|
@@ -708,6 +765,8 @@ export class SimpleJobExecutor {
|
|
|
708
765
|
return null;
|
|
709
766
|
}
|
|
710
767
|
catch (error) {
|
|
768
|
+
this.logger.error("Error writing @Operation to IOperationStore: @Error", operation, error);
|
|
769
|
+
this.writeCache.invalidate(documentId, scope, branch);
|
|
711
770
|
return {
|
|
712
771
|
job,
|
|
713
772
|
success: false,
|
|
@@ -765,7 +824,7 @@ export class SimpleJobExecutor {
|
|
|
765
824
|
continue;
|
|
766
825
|
}
|
|
767
826
|
if (signer.signatures.length === 0) {
|
|
768
|
-
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id
|
|
827
|
+
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id} at index ${operation.index} has signer but no signatures`);
|
|
769
828
|
}
|
|
770
829
|
const publicKey = signer.app.key;
|
|
771
830
|
let isValid = false;
|
|
@@ -774,10 +833,10 @@ export class SimpleJobExecutor {
|
|
|
774
833
|
}
|
|
775
834
|
catch (error) {
|
|
776
835
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
777
|
-
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id
|
|
836
|
+
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id} at index ${operation.index} verification failed: ${errorMessage}`);
|
|
778
837
|
}
|
|
779
838
|
if (!isValid) {
|
|
780
|
-
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id
|
|
839
|
+
throw new InvalidSignatureError(job.documentId, `Operation ${operation.id} at index ${operation.index} signature verification returned false`);
|
|
781
840
|
}
|
|
782
841
|
}
|
|
783
842
|
}
|
|
@@ -797,6 +856,7 @@ export class SimpleJobExecutor {
|
|
|
797
856
|
let isValid = false;
|
|
798
857
|
try {
|
|
799
858
|
const tempOperation = {
|
|
859
|
+
id: deriveOperationId(job.documentId, action.scope, job.branch, action.id),
|
|
800
860
|
index: 0,
|
|
801
861
|
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
802
862
|
hash: "",
|