@powerhousedao/reactor 5.0.1-staging.1 → 5.0.1-staging.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cache/kysely-write-cache.d.ts +4 -3
- package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
- package/dist/src/cache/kysely-write-cache.js +12 -12
- package/dist/src/cache/kysely-write-cache.js.map +1 -1
- package/dist/src/client/reactor-client.d.ts +2 -2
- package/dist/src/client/reactor-client.d.ts.map +1 -1
- package/dist/src/client/reactor-client.js +8 -8
- package/dist/src/client/reactor-client.js.map +1 -1
- package/dist/src/client/types.d.ts +4 -4
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.d.ts +32 -0
- package/dist/src/core/reactor-builder.d.ts.map +1 -0
- package/dist/src/core/reactor-builder.js +120 -0
- package/dist/src/core/reactor-builder.js.map +1 -0
- package/dist/src/core/reactor.d.ts +25 -10
- package/dist/src/core/reactor.d.ts.map +1 -1
- package/dist/src/core/reactor.js +627 -284
- package/dist/src/core/reactor.js.map +1 -1
- package/dist/src/core/types.d.ts +64 -7
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/utils.d.ts +46 -2
- package/dist/src/core/utils.d.ts.map +1 -1
- package/dist/src/core/utils.js +119 -0
- package/dist/src/core/utils.js.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.d.ts +1 -0
- package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.js +41 -17
- package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
- package/dist/src/executor/simple-job-executor.d.ts +14 -2
- package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor.js +383 -249
- package/dist/src/executor/simple-job-executor.js.map +1 -1
- package/dist/src/executor/types.d.ts +2 -0
- package/dist/src/executor/types.d.ts.map +1 -1
- package/dist/src/executor/types.js.map +1 -1
- package/dist/src/executor/util.d.ts +18 -0
- package/dist/src/executor/util.d.ts.map +1 -1
- package/dist/src/executor/util.js +42 -1
- package/dist/src/executor/util.js.map +1 -1
- package/dist/src/index.d.ts +9 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/job-tracker/in-memory-job-tracker.d.ts +3 -2
- package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
- package/dist/src/job-tracker/in-memory-job-tracker.js +7 -1
- package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
- package/dist/src/job-tracker/interfaces.d.ts +5 -4
- package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
- package/dist/src/queue/interfaces.d.ts +5 -4
- package/dist/src/queue/interfaces.d.ts.map +1 -1
- package/dist/src/queue/job-execution-handle.d.ts +3 -2
- package/dist/src/queue/job-execution-handle.d.ts.map +1 -1
- package/dist/src/queue/job-execution-handle.js +2 -2
- package/dist/src/queue/job-execution-handle.js.map +1 -1
- package/dist/src/queue/queue.d.ts +4 -2
- package/dist/src/queue/queue.d.ts.map +1 -1
- package/dist/src/queue/queue.js +16 -4
- package/dist/src/queue/queue.js.map +1 -1
- package/dist/src/queue/types.d.ts +12 -6
- package/dist/src/queue/types.d.ts.map +1 -1
- package/dist/src/queue/types.js.map +1 -1
- package/dist/src/read-models/document-view.d.ts +10 -6
- package/dist/src/read-models/document-view.d.ts.map +1 -1
- package/dist/src/read-models/document-view.js +116 -113
- package/dist/src/read-models/document-view.js.map +1 -1
- package/dist/src/read-models/types.d.ts +3 -3
- package/dist/src/read-models/types.d.ts.map +1 -1
- package/dist/src/shared/consistency-tracker.d.ts +48 -0
- package/dist/src/shared/consistency-tracker.d.ts.map +1 -0
- package/dist/src/shared/consistency-tracker.js +123 -0
- package/dist/src/shared/consistency-tracker.js.map +1 -0
- package/dist/src/shared/types.d.ts +35 -1
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/types.js.map +1 -1
- package/dist/src/storage/interfaces.d.ts +148 -2
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/interfaces.js.map +1 -1
- package/dist/src/storage/kysely/document-indexer.d.ts +28 -0
- package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -0
- package/dist/src/storage/kysely/document-indexer.js +350 -0
- package/dist/src/storage/kysely/document-indexer.js.map +1 -0
- package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -1
- package/dist/src/storage/kysely/keyframe-store.js +6 -13
- package/dist/src/storage/kysely/keyframe-store.js.map +1 -1
- package/dist/src/storage/kysely/store.js +1 -1
- package/dist/src/storage/kysely/store.js.map +1 -1
- package/dist/src/storage/kysely/types.d.ts +35 -2
- package/dist/src/storage/kysely/types.d.ts.map +1 -1
- package/dist/src/storage/migrations/001_create_operation_table.d.ts +3 -0
- package/dist/src/storage/migrations/001_create_operation_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/001_create_operation_table.js +40 -0
- package/dist/src/storage/migrations/001_create_operation_table.js.map +1 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.d.ts +3 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.js +27 -0
- package/dist/src/storage/migrations/002_create_keyframe_table.js.map +1 -0
- package/dist/src/storage/migrations/003_create_document_table.d.ts +3 -0
- package/dist/src/storage/migrations/003_create_document_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/003_create_document_table.js +10 -0
- package/dist/src/storage/migrations/003_create_document_table.js.map +1 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts +3 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.js +35 -0
- package/dist/src/storage/migrations/004_create_document_relationship_table.js.map +1 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts +3 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.js +10 -0
- package/dist/src/storage/migrations/005_create_indexer_state_table.js.map +1 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts +3 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.js +49 -0
- package/dist/src/storage/migrations/006_create_document_snapshot_table.js.map +1 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts +3 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.js +24 -0
- package/dist/src/storage/migrations/007_create_slug_mapping_table.js.map +1 -0
- package/dist/src/storage/migrations/008_create_view_state_table.d.ts +3 -0
- package/dist/src/storage/migrations/008_create_view_state_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/008_create_view_state_table.js +9 -0
- package/dist/src/storage/migrations/008_create_view_state_table.js.map +1 -0
- package/dist/src/storage/migrations/index.d.ts +3 -0
- package/dist/src/storage/migrations/index.d.ts.map +1 -0
- package/dist/src/storage/migrations/index.js +3 -0
- package/dist/src/storage/migrations/index.js.map +1 -0
- package/dist/src/storage/migrations/migrator.d.ts +5 -0
- package/dist/src/storage/migrations/migrator.d.ts.map +1 -0
- package/dist/src/storage/migrations/migrator.js +51 -0
- package/dist/src/storage/migrations/migrator.js.map +1 -0
- package/dist/src/storage/migrations/run-migrations.d.ts +2 -0
- package/dist/src/storage/migrations/run-migrations.d.ts.map +1 -0
- package/dist/src/storage/migrations/run-migrations.js +58 -0
- package/dist/src/storage/migrations/run-migrations.js.map +1 -0
- package/dist/src/storage/migrations/types.d.ts +9 -0
- package/dist/src/storage/migrations/types.d.ts.map +1 -0
- package/dist/src/storage/migrations/types.js +2 -0
- package/dist/src/storage/migrations/types.js.map +1 -0
- package/dist/src/storage/txn.d.ts.map +1 -1
- package/dist/src/storage/txn.js +2 -0
- package/dist/src/storage/txn.js.map +1 -1
- package/package.json +8 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OperationEventTypes } from "../events/types.js";
|
|
2
2
|
import { DocumentDeletedError } from "../shared/errors.js";
|
|
3
|
+
import { reshuffleByTimestampAndIndex } from "../utils/reshuffle.js";
|
|
3
4
|
import { applyDeleteDocumentAction, applyUpgradeDocumentAction, createDocumentFromAction, getNextIndexForScope, } from "./util.js";
|
|
4
5
|
/**
|
|
5
6
|
* Simple job executor that processes a job by applying actions through document model reducers.
|
|
@@ -15,13 +16,21 @@ export class SimpleJobExecutor {
|
|
|
15
16
|
operationStore;
|
|
16
17
|
eventBus;
|
|
17
18
|
writeCache;
|
|
18
|
-
|
|
19
|
+
config;
|
|
20
|
+
constructor(registry, documentStorage, operationStorage, operationStore, eventBus, writeCache, config) {
|
|
19
21
|
this.registry = registry;
|
|
20
22
|
this.documentStorage = documentStorage;
|
|
21
23
|
this.operationStorage = operationStorage;
|
|
22
24
|
this.operationStore = operationStore;
|
|
23
25
|
this.eventBus = eventBus;
|
|
24
26
|
this.writeCache = writeCache;
|
|
27
|
+
this.config = {
|
|
28
|
+
maxConcurrency: config.maxConcurrency ?? 1,
|
|
29
|
+
jobTimeoutMs: config.jobTimeoutMs ?? 30000,
|
|
30
|
+
retryBaseDelayMs: config.retryBaseDelayMs ?? 100,
|
|
31
|
+
retryMaxDelayMs: config.retryMaxDelayMs ?? 5000,
|
|
32
|
+
legacyStorageEnabled: config.legacyStorageEnabled ?? true,
|
|
33
|
+
};
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
36
|
* Execute a single job by applying all its actions through the appropriate reducers.
|
|
@@ -29,71 +38,129 @@ export class SimpleJobExecutor {
|
|
|
29
38
|
*/
|
|
30
39
|
async executeJob(job) {
|
|
31
40
|
const startTime = Date.now();
|
|
41
|
+
if (job.kind === "load") {
|
|
42
|
+
return await this.executeLoadJob(job, startTime);
|
|
43
|
+
}
|
|
44
|
+
const result = await this.processActions(job, job.actions, startTime);
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
return {
|
|
47
|
+
job,
|
|
48
|
+
success: false,
|
|
49
|
+
error: result.error,
|
|
50
|
+
duration: Date.now() - startTime,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (result.operationsWithContext.length > 0) {
|
|
54
|
+
this.eventBus
|
|
55
|
+
.emit(OperationEventTypes.OPERATION_WRITTEN, {
|
|
56
|
+
operations: result.operationsWithContext,
|
|
57
|
+
})
|
|
58
|
+
.catch(() => {
|
|
59
|
+
// TODO: Log error
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
job,
|
|
64
|
+
success: true,
|
|
65
|
+
operations: result.generatedOperations,
|
|
66
|
+
operationsWithContext: result.operationsWithContext,
|
|
67
|
+
duration: Date.now() - startTime,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async processActions(job, actions, startTime, skipValues) {
|
|
32
71
|
const generatedOperations = [];
|
|
33
72
|
const operationsWithContext = [];
|
|
34
|
-
|
|
35
|
-
for (const action of
|
|
36
|
-
// Handle system actions specially (CREATE_DOCUMENT, DELETE_DOCUMENT, etc.)
|
|
73
|
+
let actionIndex = 0;
|
|
74
|
+
for (const action of actions) {
|
|
37
75
|
if (action.type === "CREATE_DOCUMENT") {
|
|
38
|
-
const result = await this.executeCreateDocumentAction(job, action, startTime);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
const result = await this.executeCreateDocumentAction(job, action, startTime, skipValues?.[actionIndex]);
|
|
77
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
78
|
+
if (error !== null) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
generatedOperations,
|
|
82
|
+
operationsWithContext,
|
|
83
|
+
error: error.error,
|
|
84
|
+
};
|
|
47
85
|
}
|
|
86
|
+
actionIndex++;
|
|
48
87
|
continue;
|
|
49
88
|
}
|
|
50
89
|
if (action.type === "DELETE_DOCUMENT") {
|
|
51
90
|
const result = await this.executeDeleteDocumentAction(job, action, startTime);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
92
|
+
if (error !== null) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
generatedOperations,
|
|
96
|
+
operationsWithContext,
|
|
97
|
+
error: error.error,
|
|
98
|
+
};
|
|
60
99
|
}
|
|
100
|
+
actionIndex++;
|
|
61
101
|
continue;
|
|
62
102
|
}
|
|
63
103
|
if (action.type === "UPGRADE_DOCUMENT") {
|
|
64
|
-
const result = await this.executeUpgradeDocumentAction(job, action, startTime);
|
|
65
|
-
|
|
66
|
-
|
|
104
|
+
const result = await this.executeUpgradeDocumentAction(job, action, startTime, skipValues?.[actionIndex]);
|
|
105
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
106
|
+
if (error !== null) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
generatedOperations,
|
|
110
|
+
operationsWithContext,
|
|
111
|
+
error: error.error,
|
|
112
|
+
};
|
|
67
113
|
}
|
|
68
|
-
|
|
69
|
-
|
|
114
|
+
actionIndex++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (action.type === "ADD_RELATIONSHIP") {
|
|
118
|
+
const result = await this.executeAddRelationshipAction(job, action, startTime);
|
|
119
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
120
|
+
if (error !== null) {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
generatedOperations,
|
|
124
|
+
operationsWithContext,
|
|
125
|
+
error: error.error,
|
|
126
|
+
};
|
|
70
127
|
}
|
|
71
|
-
|
|
72
|
-
|
|
128
|
+
actionIndex++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (action.type === "REMOVE_RELATIONSHIP") {
|
|
132
|
+
const result = await this.executeRemoveRelationshipAction(job, action, startTime);
|
|
133
|
+
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
134
|
+
if (error !== null) {
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
generatedOperations,
|
|
138
|
+
operationsWithContext,
|
|
139
|
+
error: error.error,
|
|
140
|
+
};
|
|
73
141
|
}
|
|
142
|
+
actionIndex++;
|
|
74
143
|
continue;
|
|
75
144
|
}
|
|
76
|
-
// For regular actions, load the document and apply through reducer
|
|
77
145
|
let document;
|
|
78
146
|
try {
|
|
79
147
|
document = await this.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
80
148
|
}
|
|
81
149
|
catch (error) {
|
|
82
150
|
return {
|
|
83
|
-
job,
|
|
84
151
|
success: false,
|
|
152
|
+
generatedOperations,
|
|
153
|
+
operationsWithContext,
|
|
85
154
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
86
|
-
duration: Date.now() - startTime,
|
|
87
155
|
};
|
|
88
156
|
}
|
|
89
|
-
// Check if document is deleted
|
|
90
157
|
const documentState = document.state.document;
|
|
91
158
|
if (documentState.isDeleted) {
|
|
92
159
|
return {
|
|
93
|
-
job,
|
|
94
160
|
success: false,
|
|
161
|
+
generatedOperations,
|
|
162
|
+
operationsWithContext,
|
|
95
163
|
error: new DocumentDeletedError(job.documentId, documentState.deletedAtUtcIso),
|
|
96
|
-
duration: Date.now() - startTime,
|
|
97
164
|
};
|
|
98
165
|
}
|
|
99
166
|
let module;
|
|
@@ -102,36 +169,58 @@ export class SimpleJobExecutor {
|
|
|
102
169
|
}
|
|
103
170
|
catch (error) {
|
|
104
171
|
return {
|
|
105
|
-
job,
|
|
106
172
|
success: false,
|
|
173
|
+
generatedOperations,
|
|
174
|
+
operationsWithContext,
|
|
107
175
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
108
|
-
duration: Date.now() - startTime,
|
|
109
176
|
};
|
|
110
177
|
}
|
|
111
|
-
|
|
112
|
-
const updatedDocument = module.reducer(document, action);
|
|
113
|
-
const scope = job.scope;
|
|
114
|
-
const operations = updatedDocument.operations[scope];
|
|
115
|
-
if (operations.length === 0) {
|
|
116
|
-
throw new Error("No operation generated from action");
|
|
117
|
-
}
|
|
118
|
-
const newOperation = operations[operations.length - 1];
|
|
119
|
-
generatedOperations.push(newOperation);
|
|
120
|
-
// Write the operation to legacy storage
|
|
178
|
+
let updatedDocument;
|
|
121
179
|
try {
|
|
122
|
-
|
|
180
|
+
updatedDocument = module.reducer(document, action);
|
|
123
181
|
}
|
|
124
182
|
catch (error) {
|
|
183
|
+
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)}`;
|
|
184
|
+
const enhancedError = new Error(contextMessage);
|
|
185
|
+
if (error instanceof Error && error.stack) {
|
|
186
|
+
enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
|
|
187
|
+
}
|
|
125
188
|
return {
|
|
126
|
-
job,
|
|
127
189
|
success: false,
|
|
128
|
-
|
|
129
|
-
|
|
190
|
+
generatedOperations,
|
|
191
|
+
operationsWithContext,
|
|
192
|
+
error: enhancedError,
|
|
130
193
|
};
|
|
131
194
|
}
|
|
132
|
-
|
|
195
|
+
const scope = job.scope;
|
|
196
|
+
const operations = updatedDocument.operations[scope];
|
|
197
|
+
if (operations.length === 0) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
generatedOperations,
|
|
201
|
+
operationsWithContext,
|
|
202
|
+
error: new Error("No operation generated from action"),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const newOperation = operations[operations.length - 1];
|
|
206
|
+
if (skipValues && actionIndex < skipValues.length) {
|
|
207
|
+
newOperation.skip = skipValues[actionIndex];
|
|
208
|
+
}
|
|
209
|
+
generatedOperations.push(newOperation);
|
|
210
|
+
if (this.config.legacyStorageEnabled) {
|
|
211
|
+
try {
|
|
212
|
+
await this.operationStorage.addDocumentOperations(job.documentId, [newOperation], updatedDocument);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
generatedOperations,
|
|
218
|
+
operationsWithContext,
|
|
219
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
133
223
|
const resultingState = JSON.stringify(updatedDocument.state);
|
|
134
|
-
// Write the operation to new IOperationStore (dual-writing)
|
|
135
224
|
try {
|
|
136
225
|
await this.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
137
226
|
txn.addOperations(newOperation);
|
|
@@ -139,10 +228,10 @@ export class SimpleJobExecutor {
|
|
|
139
228
|
}
|
|
140
229
|
catch (error) {
|
|
141
230
|
return {
|
|
142
|
-
job,
|
|
143
231
|
success: false,
|
|
232
|
+
generatedOperations,
|
|
233
|
+
operationsWithContext,
|
|
144
234
|
error: new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
145
|
-
duration: Date.now() - startTime,
|
|
146
235
|
};
|
|
147
236
|
}
|
|
148
237
|
updatedDocument.header.revision = {
|
|
@@ -157,25 +246,15 @@ export class SimpleJobExecutor {
|
|
|
157
246
|
scope,
|
|
158
247
|
branch: job.branch,
|
|
159
248
|
documentType: document.header.documentType,
|
|
160
|
-
resultingState,
|
|
249
|
+
resultingState,
|
|
161
250
|
},
|
|
162
251
|
});
|
|
163
|
-
|
|
164
|
-
// Emit event for read models with all operations - non-blocking
|
|
165
|
-
if (operationsWithContext.length > 0) {
|
|
166
|
-
this.eventBus
|
|
167
|
-
.emit(OperationEventTypes.OPERATION_WRITTEN, {
|
|
168
|
-
operations: operationsWithContext,
|
|
169
|
-
})
|
|
170
|
-
.catch(() => {
|
|
171
|
-
// TODO: Log error
|
|
172
|
-
});
|
|
252
|
+
actionIndex++;
|
|
173
253
|
}
|
|
174
254
|
return {
|
|
175
|
-
job,
|
|
176
255
|
success: true,
|
|
177
|
-
|
|
178
|
-
|
|
256
|
+
generatedOperations,
|
|
257
|
+
operationsWithContext,
|
|
179
258
|
};
|
|
180
259
|
}
|
|
181
260
|
/**
|
|
@@ -183,7 +262,7 @@ export class SimpleJobExecutor {
|
|
|
183
262
|
* This creates a new document in storage along with its initial operation.
|
|
184
263
|
* For a new document, the operation index is always 0.
|
|
185
264
|
*/
|
|
186
|
-
async executeCreateDocumentAction(job, action, startTime) {
|
|
265
|
+
async executeCreateDocumentAction(job, action, startTime, skip = 0) {
|
|
187
266
|
if (job.scope !== "document") {
|
|
188
267
|
return {
|
|
189
268
|
job,
|
|
@@ -194,36 +273,23 @@ export class SimpleJobExecutor {
|
|
|
194
273
|
}
|
|
195
274
|
const document = createDocumentFromAction(action);
|
|
196
275
|
// Legacy: Store the document in storage
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
job,
|
|
203
|
-
|
|
204
|
-
error: new Error(`Failed to create document in storage: ${error instanceof Error ? error.message : String(error)}`),
|
|
205
|
-
duration: Date.now() - startTime,
|
|
206
|
-
};
|
|
276
|
+
if (this.config.legacyStorageEnabled) {
|
|
277
|
+
try {
|
|
278
|
+
await this.documentStorage.create(document);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return this.buildErrorResult(job, new Error(`Failed to create document in storage: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
282
|
+
}
|
|
207
283
|
}
|
|
208
|
-
|
|
209
|
-
const operation = {
|
|
210
|
-
index: 0,
|
|
211
|
-
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
212
|
-
hash: "", // Will be computed later
|
|
213
|
-
skip: 0, // Always 0 for new operations; skip > 0 only during reshuffle
|
|
214
|
-
action: action,
|
|
215
|
-
};
|
|
284
|
+
const operation = this.createOperation(action, 0, skip);
|
|
216
285
|
// Legacy: Write the CREATE_DOCUMENT operation to legacy storage
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
job,
|
|
223
|
-
|
|
224
|
-
error: new Error(`Failed to write CREATE_DOCUMENT operation to legacy storage: ${error instanceof Error ? error.message : String(error)}`),
|
|
225
|
-
duration: Date.now() - startTime,
|
|
226
|
-
};
|
|
286
|
+
if (this.config.legacyStorageEnabled) {
|
|
287
|
+
try {
|
|
288
|
+
await this.operationStorage.addDocumentOperations(document.header.id, [operation], document);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
return this.buildErrorResult(job, new Error(`Failed to write CREATE_DOCUMENT operation to legacy storage: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
292
|
+
}
|
|
227
293
|
}
|
|
228
294
|
// Compute resultingState for passing via context (not persisted)
|
|
229
295
|
// Include header and all scopes present in the document state (auth, document, etc.)
|
|
@@ -233,44 +299,13 @@ export class SimpleJobExecutor {
|
|
|
233
299
|
...document.state,
|
|
234
300
|
};
|
|
235
301
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await this.operationStore.apply(document.header.id, document.header.documentType, job.scope, job.branch, operation.index, (txn) => {
|
|
240
|
-
txn.addOperations(operation);
|
|
241
|
-
});
|
|
302
|
+
const writeError = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
303
|
+
if (writeError !== null) {
|
|
304
|
+
return writeError;
|
|
242
305
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
success: false,
|
|
247
|
-
error: new Error(`Failed to write CREATE_DOCUMENT operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
248
|
-
duration: Date.now() - startTime,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
document.header.revision = {
|
|
252
|
-
...document.header.revision,
|
|
253
|
-
[job.scope]: operation.index + 1,
|
|
254
|
-
};
|
|
255
|
-
this.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
|
|
256
|
-
return {
|
|
257
|
-
job,
|
|
258
|
-
success: true,
|
|
259
|
-
operations: [operation],
|
|
260
|
-
operationsWithContext: [
|
|
261
|
-
{
|
|
262
|
-
operation,
|
|
263
|
-
context: {
|
|
264
|
-
documentId: document.header.id,
|
|
265
|
-
scope: job.scope,
|
|
266
|
-
branch: job.branch,
|
|
267
|
-
documentType: document.header.documentType,
|
|
268
|
-
resultingState,
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
duration: Date.now() - startTime,
|
|
273
|
-
};
|
|
306
|
+
this.updateDocumentRevision(document, job.scope, operation.index);
|
|
307
|
+
this.writeCacheState(document.header.id, job.scope, job.branch, operation.index, document);
|
|
308
|
+
return this.buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
|
|
274
309
|
}
|
|
275
310
|
/**
|
|
276
311
|
* Execute a DELETE_DOCUMENT system action.
|
|
@@ -280,12 +315,7 @@ export class SimpleJobExecutor {
|
|
|
280
315
|
async executeDeleteDocumentAction(job, action, startTime) {
|
|
281
316
|
const input = action.input;
|
|
282
317
|
if (!input.documentId) {
|
|
283
|
-
return
|
|
284
|
-
job,
|
|
285
|
-
success: false,
|
|
286
|
-
error: new Error("DELETE_DOCUMENT action requires a documentId in input"),
|
|
287
|
-
duration: Date.now() - startTime,
|
|
288
|
-
};
|
|
318
|
+
return this.buildErrorResult(job, new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
|
|
289
319
|
}
|
|
290
320
|
const documentId = input.documentId;
|
|
291
321
|
let document;
|
|
@@ -293,43 +323,22 @@ export class SimpleJobExecutor {
|
|
|
293
323
|
document = await this.writeCache.getState(documentId, job.scope, job.branch);
|
|
294
324
|
}
|
|
295
325
|
catch (error) {
|
|
296
|
-
return {
|
|
297
|
-
job,
|
|
298
|
-
success: false,
|
|
299
|
-
error: new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`),
|
|
300
|
-
duration: Date.now() - startTime,
|
|
301
|
-
};
|
|
326
|
+
return this.buildErrorResult(job, new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
302
327
|
}
|
|
303
328
|
// Check if document is already deleted
|
|
304
329
|
const documentState = document.state.document;
|
|
305
330
|
if (documentState.isDeleted) {
|
|
306
|
-
return
|
|
307
|
-
job,
|
|
308
|
-
success: false,
|
|
309
|
-
error: new DocumentDeletedError(documentId, documentState.deletedAtUtcIso),
|
|
310
|
-
duration: Date.now() - startTime,
|
|
311
|
-
};
|
|
331
|
+
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
312
332
|
}
|
|
313
|
-
// Determine the next operation index for this scope only (per-scope indexing)
|
|
314
333
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
await this.documentStorage.delete(documentId);
|
|
325
|
-
}
|
|
326
|
-
catch (error) {
|
|
327
|
-
return {
|
|
328
|
-
job,
|
|
329
|
-
success: false,
|
|
330
|
-
error: new Error(`Failed to delete document from legacy storage: ${error instanceof Error ? error.message : String(error)}`),
|
|
331
|
-
duration: Date.now() - startTime,
|
|
332
|
-
};
|
|
334
|
+
const operation = this.createOperation(action, nextIndex);
|
|
335
|
+
if (this.config.legacyStorageEnabled) {
|
|
336
|
+
try {
|
|
337
|
+
await this.documentStorage.delete(documentId);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
return this.buildErrorResult(job, new Error(`Failed to delete document from legacy storage: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
341
|
+
}
|
|
333
342
|
}
|
|
334
343
|
// Mark the document as deleted in the state for read model indexing
|
|
335
344
|
applyDeleteDocumentAction(document, action);
|
|
@@ -340,131 +349,236 @@ export class SimpleJobExecutor {
|
|
|
340
349
|
document: document.state.document,
|
|
341
350
|
};
|
|
342
351
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
await this.operationStore.apply(documentId, document.header.documentType, job.scope, job.branch, operation.index, (txn) => {
|
|
347
|
-
txn.addOperations(operation);
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
return {
|
|
352
|
-
job,
|
|
353
|
-
success: false,
|
|
354
|
-
error: new Error(`Failed to write DELETE_DOCUMENT operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
355
|
-
duration: Date.now() - startTime,
|
|
356
|
-
};
|
|
352
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
353
|
+
if (writeError !== null) {
|
|
354
|
+
return writeError;
|
|
357
355
|
}
|
|
358
|
-
return
|
|
359
|
-
job,
|
|
360
|
-
success: true,
|
|
361
|
-
operations: [operation],
|
|
362
|
-
operationsWithContext: [
|
|
363
|
-
{
|
|
364
|
-
operation,
|
|
365
|
-
context: {
|
|
366
|
-
documentId,
|
|
367
|
-
scope: job.scope,
|
|
368
|
-
branch: job.branch,
|
|
369
|
-
documentType: document.header.documentType,
|
|
370
|
-
resultingState,
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
],
|
|
374
|
-
duration: Date.now() - startTime,
|
|
375
|
-
};
|
|
356
|
+
return this.buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
376
357
|
}
|
|
377
358
|
/**
|
|
378
359
|
* Execute an UPGRADE_DOCUMENT system action.
|
|
379
360
|
* This sets the document's initial state from the upgrade action.
|
|
380
361
|
* The operation index is determined from the document's current operation count.
|
|
381
362
|
*/
|
|
382
|
-
async executeUpgradeDocumentAction(job, action, startTime) {
|
|
363
|
+
async executeUpgradeDocumentAction(job, action, startTime, skip = 0) {
|
|
383
364
|
const input = action.input;
|
|
384
365
|
if (!input.documentId) {
|
|
385
|
-
return
|
|
386
|
-
job,
|
|
387
|
-
success: false,
|
|
388
|
-
error: new Error("UPGRADE_DOCUMENT action requires a documentId in input"),
|
|
389
|
-
duration: Date.now() - startTime,
|
|
390
|
-
};
|
|
366
|
+
return this.buildErrorResult(job, new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
391
367
|
}
|
|
392
368
|
const documentId = input.documentId;
|
|
393
|
-
// Load the document from write cache
|
|
394
369
|
let document;
|
|
395
370
|
try {
|
|
396
371
|
document = await this.writeCache.getState(documentId, job.scope, job.branch);
|
|
397
372
|
}
|
|
398
373
|
catch (error) {
|
|
399
|
-
return {
|
|
400
|
-
job,
|
|
401
|
-
success: false,
|
|
402
|
-
error: new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`),
|
|
403
|
-
duration: Date.now() - startTime,
|
|
404
|
-
};
|
|
374
|
+
return this.buildErrorResult(job, new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
405
375
|
}
|
|
406
|
-
// Check if document is deleted
|
|
407
376
|
const documentState = document.state.document;
|
|
408
377
|
if (documentState.isDeleted) {
|
|
409
|
-
return
|
|
410
|
-
job,
|
|
411
|
-
success: false,
|
|
412
|
-
error: new DocumentDeletedError(documentId, documentState.deletedAtUtcIso),
|
|
413
|
-
duration: Date.now() - startTime,
|
|
414
|
-
};
|
|
378
|
+
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
415
379
|
}
|
|
416
|
-
// Determine the next operation index for this scope only (per-scope indexing)
|
|
417
380
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
418
|
-
// Apply the initialState from the upgrade action
|
|
419
|
-
// The initialState from UPGRADE_DOCUMENT should be merged with the existing base state
|
|
420
|
-
// to preserve auth and document scopes while adding model-specific scopes (global, local, etc.)
|
|
421
381
|
applyUpgradeDocumentAction(document, action);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
382
|
+
const operation = this.createOperation(action, nextIndex, skip);
|
|
383
|
+
// Write the updated document to legacy storage
|
|
384
|
+
if (this.config.legacyStorageEnabled) {
|
|
385
|
+
try {
|
|
386
|
+
await this.operationStorage.addDocumentOperations(documentId, [operation], document);
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
return this.buildErrorResult(job, new Error(`Failed to write UPGRADE_DOCUMENT operation to legacy storage: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Compute resultingState for passing via context (not persisted)
|
|
393
|
+
const resultingStateObj = {
|
|
394
|
+
header: document.header,
|
|
395
|
+
...document.state,
|
|
396
|
+
};
|
|
397
|
+
const resultingState = JSON.stringify(resultingStateObj);
|
|
398
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
399
|
+
if (writeError !== null) {
|
|
400
|
+
return writeError;
|
|
401
|
+
}
|
|
402
|
+
this.updateDocumentRevision(document, job.scope, operation.index);
|
|
403
|
+
this.writeCacheState(documentId, job.scope, job.branch, operation.index, document);
|
|
404
|
+
return this.buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
405
|
+
}
|
|
406
|
+
async executeAddRelationshipAction(job, action, startTime) {
|
|
407
|
+
if (job.scope !== "document") {
|
|
408
|
+
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
|
|
409
|
+
}
|
|
410
|
+
const input = action.input;
|
|
411
|
+
if (!input.sourceId || !input.targetId || !input.relationshipType) {
|
|
412
|
+
return this.buildErrorResult(job, new Error("ADD_RELATIONSHIP action requires sourceId, targetId, and relationshipType in input"), startTime);
|
|
413
|
+
}
|
|
414
|
+
if (input.sourceId === input.targetId) {
|
|
415
|
+
return this.buildErrorResult(job, new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)"), startTime);
|
|
416
|
+
}
|
|
417
|
+
let sourceDoc;
|
|
418
|
+
try {
|
|
419
|
+
sourceDoc = await this.writeCache.getState(input.sourceId, "document", job.branch);
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
423
|
+
}
|
|
424
|
+
let targetDoc;
|
|
425
|
+
try {
|
|
426
|
+
targetDoc = await this.writeCache.getState(input.targetId, "document", job.branch);
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP: target document ${input.targetId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
430
|
+
}
|
|
431
|
+
const targetDocState = targetDoc.state.document;
|
|
432
|
+
if (targetDocState.isDeleted) {
|
|
433
|
+
return this.buildErrorResult(job, new Error(`ADD_RELATIONSHIP: target document ${input.targetId} is deleted`), startTime);
|
|
434
|
+
}
|
|
435
|
+
const nextIndex = getNextIndexForScope(sourceDoc, job.scope);
|
|
436
|
+
const operation = this.createOperation(action, nextIndex);
|
|
437
|
+
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
438
|
+
if (writeError !== null) {
|
|
439
|
+
return writeError;
|
|
440
|
+
}
|
|
441
|
+
sourceDoc.header.lastModifiedAtUtcIso =
|
|
442
|
+
operation.timestampUtcMs || new Date().toISOString();
|
|
443
|
+
this.updateDocumentRevision(sourceDoc, job.scope, operation.index);
|
|
444
|
+
sourceDoc.operations = {
|
|
445
|
+
...sourceDoc.operations,
|
|
446
|
+
[job.scope]: [...(sourceDoc.operations[job.scope] ?? []), operation],
|
|
447
|
+
};
|
|
448
|
+
const scopeState = sourceDoc.state[job.scope];
|
|
449
|
+
const resultingStateObj = {
|
|
450
|
+
header: structuredClone(sourceDoc.header),
|
|
451
|
+
[job.scope]: scopeState === undefined ? {} : structuredClone(scopeState),
|
|
452
|
+
};
|
|
453
|
+
const resultingState = JSON.stringify(resultingStateObj);
|
|
454
|
+
this.writeCacheState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
|
|
455
|
+
return this.buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
456
|
+
}
|
|
457
|
+
async executeRemoveRelationshipAction(job, action, startTime) {
|
|
458
|
+
if (job.scope !== "document") {
|
|
459
|
+
return this.buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
|
|
460
|
+
}
|
|
461
|
+
const input = action.input;
|
|
462
|
+
if (!input.sourceId || !input.targetId || !input.relationshipType) {
|
|
463
|
+
return this.buildErrorResult(job, new Error("REMOVE_RELATIONSHIP action requires sourceId, targetId, and relationshipType in input"), startTime);
|
|
464
|
+
}
|
|
465
|
+
let sourceDoc;
|
|
466
|
+
try {
|
|
467
|
+
sourceDoc = await this.writeCache.getState(input.sourceId, "document", job.branch);
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
return this.buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
471
|
+
}
|
|
472
|
+
const nextIndex = getNextIndexForScope(sourceDoc, job.scope);
|
|
473
|
+
const operation = this.createOperation(action, nextIndex);
|
|
474
|
+
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
475
|
+
if (writeError !== null) {
|
|
476
|
+
return writeError;
|
|
477
|
+
}
|
|
478
|
+
sourceDoc.header.lastModifiedAtUtcIso =
|
|
479
|
+
operation.timestampUtcMs || new Date().toISOString();
|
|
480
|
+
this.updateDocumentRevision(sourceDoc, job.scope, operation.index);
|
|
481
|
+
sourceDoc.operations = {
|
|
482
|
+
...sourceDoc.operations,
|
|
483
|
+
[job.scope]: [...(sourceDoc.operations[job.scope] ?? []), operation],
|
|
484
|
+
};
|
|
485
|
+
const scopeState = sourceDoc.state[job.scope];
|
|
486
|
+
const resultingStateObj = {
|
|
487
|
+
header: structuredClone(sourceDoc.header),
|
|
488
|
+
[job.scope]: scopeState === undefined ? {} : structuredClone(scopeState),
|
|
489
|
+
};
|
|
490
|
+
const resultingState = JSON.stringify(resultingStateObj);
|
|
491
|
+
this.writeCacheState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
|
|
492
|
+
return this.buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
493
|
+
}
|
|
494
|
+
createOperation(action, index, skip = 0) {
|
|
495
|
+
return {
|
|
496
|
+
index: index,
|
|
425
497
|
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
426
|
-
hash: "",
|
|
427
|
-
skip:
|
|
498
|
+
hash: "",
|
|
499
|
+
skip: skip,
|
|
428
500
|
action: action,
|
|
429
501
|
};
|
|
430
|
-
|
|
502
|
+
}
|
|
503
|
+
async executeLoadJob(job, startTime) {
|
|
504
|
+
if (job.operations.length === 0) {
|
|
505
|
+
return this.buildErrorResult(job, new Error("Load job must include at least one operation"), startTime);
|
|
506
|
+
}
|
|
507
|
+
const scope = job.scope;
|
|
508
|
+
let latestRevision = 0;
|
|
431
509
|
try {
|
|
432
|
-
await this.
|
|
510
|
+
const revisions = await this.operationStore.getRevisions(job.documentId, job.branch);
|
|
511
|
+
latestRevision = revisions.revision[scope] ?? 0;
|
|
433
512
|
}
|
|
434
513
|
catch (error) {
|
|
514
|
+
latestRevision = 0;
|
|
515
|
+
}
|
|
516
|
+
const minIncomingIndex = job.operations.reduce((min, operation) => Math.min(min, operation.index), Number.POSITIVE_INFINITY);
|
|
517
|
+
const skipCount = minIncomingIndex === Number.POSITIVE_INFINITY
|
|
518
|
+
? 0
|
|
519
|
+
: Math.max(0, latestRevision - minIncomingIndex);
|
|
520
|
+
const reshuffledOperations = reshuffleByTimestampAndIndex({
|
|
521
|
+
index: latestRevision,
|
|
522
|
+
skip: skipCount,
|
|
523
|
+
}, [], job.operations.map((operation) => ({
|
|
524
|
+
...operation,
|
|
525
|
+
id: operation.id,
|
|
526
|
+
})));
|
|
527
|
+
const actions = reshuffledOperations.map((operation) => operation.action);
|
|
528
|
+
const skipValues = reshuffledOperations.map((operation) => operation.skip);
|
|
529
|
+
const result = await this.processActions(job, actions, startTime, skipValues);
|
|
530
|
+
if (!result.success) {
|
|
435
531
|
return {
|
|
436
532
|
job,
|
|
437
533
|
success: false,
|
|
438
|
-
error:
|
|
534
|
+
error: result.error,
|
|
439
535
|
duration: Date.now() - startTime,
|
|
440
536
|
};
|
|
441
537
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
538
|
+
if (result.operationsWithContext.length > 0) {
|
|
539
|
+
this.eventBus
|
|
540
|
+
.emit(OperationEventTypes.OPERATION_WRITTEN, {
|
|
541
|
+
operations: result.operationsWithContext,
|
|
542
|
+
})
|
|
543
|
+
.catch(() => {
|
|
544
|
+
// TODO: log error channel once logging is wired
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
this.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
548
|
+
return {
|
|
549
|
+
job,
|
|
550
|
+
success: true,
|
|
551
|
+
operations: result.generatedOperations,
|
|
552
|
+
operationsWithContext: result.operationsWithContext,
|
|
553
|
+
duration: Date.now() - startTime,
|
|
446
554
|
};
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// Note: resultingState is NOT persisted in IOperationStore
|
|
555
|
+
}
|
|
556
|
+
async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime) {
|
|
450
557
|
try {
|
|
451
|
-
await this.operationStore.apply(documentId,
|
|
558
|
+
await this.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
|
|
452
559
|
txn.addOperations(operation);
|
|
453
560
|
});
|
|
561
|
+
return null;
|
|
454
562
|
}
|
|
455
563
|
catch (error) {
|
|
456
564
|
return {
|
|
457
565
|
job,
|
|
458
566
|
success: false,
|
|
459
|
-
error: new Error(`Failed to write
|
|
567
|
+
error: new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
460
568
|
duration: Date.now() - startTime,
|
|
461
569
|
};
|
|
462
570
|
}
|
|
571
|
+
}
|
|
572
|
+
updateDocumentRevision(document, scope, operationIndex) {
|
|
463
573
|
document.header.revision = {
|
|
464
574
|
...document.header.revision,
|
|
465
|
-
[
|
|
575
|
+
[scope]: operationIndex + 1,
|
|
466
576
|
};
|
|
467
|
-
|
|
577
|
+
}
|
|
578
|
+
writeCacheState(documentId, scope, branch, operationIndex, document) {
|
|
579
|
+
this.writeCache.putState(documentId, scope, branch, operationIndex, document);
|
|
580
|
+
}
|
|
581
|
+
buildSuccessResult(job, operation, documentId, documentType, resultingState, startTime) {
|
|
468
582
|
return {
|
|
469
583
|
job,
|
|
470
584
|
success: true,
|
|
@@ -473,10 +587,10 @@ export class SimpleJobExecutor {
|
|
|
473
587
|
{
|
|
474
588
|
operation,
|
|
475
589
|
context: {
|
|
476
|
-
documentId,
|
|
590
|
+
documentId: documentId,
|
|
477
591
|
scope: job.scope,
|
|
478
592
|
branch: job.branch,
|
|
479
|
-
documentType:
|
|
593
|
+
documentType: documentType,
|
|
480
594
|
resultingState,
|
|
481
595
|
},
|
|
482
596
|
},
|
|
@@ -484,5 +598,25 @@ export class SimpleJobExecutor {
|
|
|
484
598
|
duration: Date.now() - startTime,
|
|
485
599
|
};
|
|
486
600
|
}
|
|
601
|
+
buildErrorResult(job, error, startTime) {
|
|
602
|
+
return {
|
|
603
|
+
job,
|
|
604
|
+
success: false,
|
|
605
|
+
error: error,
|
|
606
|
+
duration: Date.now() - startTime,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
accumulateResultOrReturnError(result, generatedOperations, operationsWithContext) {
|
|
610
|
+
if (!result.success) {
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
if (result.operations && result.operations.length > 0) {
|
|
614
|
+
generatedOperations.push(...result.operations);
|
|
615
|
+
}
|
|
616
|
+
if (result.operationsWithContext) {
|
|
617
|
+
operationsWithContext.push(...result.operationsWithContext);
|
|
618
|
+
}
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
487
621
|
}
|
|
488
622
|
//# sourceMappingURL=simple-job-executor.js.map
|