@powerhousedao/reactor 5.0.0 → 5.0.1-staging.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cache/kysely-write-cache.d.ts +4 -3
- package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
- package/dist/src/cache/kysely-write-cache.js +12 -12
- package/dist/src/cache/kysely-write-cache.js.map +1 -1
- package/dist/src/client/reactor-client.d.ts +2 -2
- package/dist/src/client/reactor-client.d.ts.map +1 -1
- package/dist/src/client/reactor-client.js +8 -8
- package/dist/src/client/reactor-client.js.map +1 -1
- package/dist/src/client/types.d.ts +4 -4
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.d.ts +32 -0
- package/dist/src/core/reactor-builder.d.ts.map +1 -0
- package/dist/src/core/reactor-builder.js +120 -0
- package/dist/src/core/reactor-builder.js.map +1 -0
- package/dist/src/core/reactor.d.ts +25 -11
- package/dist/src/core/reactor.d.ts.map +1 -1
- package/dist/src/core/reactor.js +625 -294
- 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.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.js +3 -1
- 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 +368 -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 +2 -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 +3 -2
- package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
- package/dist/src/queue/types.d.ts +6 -1
- 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 +26 -0
- 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,10 +169,10 @@ 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
178
|
let updatedDocument;
|
|
@@ -119,34 +186,41 @@ export class SimpleJobExecutor {
|
|
|
119
186
|
enhancedError.stack = `${contextMessage}\n\nOriginal stack trace:\n${error.stack}`;
|
|
120
187
|
}
|
|
121
188
|
return {
|
|
122
|
-
job,
|
|
123
189
|
success: false,
|
|
190
|
+
generatedOperations,
|
|
191
|
+
operationsWithContext,
|
|
124
192
|
error: enhancedError,
|
|
125
|
-
duration: Date.now() - startTime,
|
|
126
193
|
};
|
|
127
194
|
}
|
|
128
195
|
const scope = job.scope;
|
|
129
196
|
const operations = updatedDocument.operations[scope];
|
|
130
197
|
if (operations.length === 0) {
|
|
131
|
-
throw new Error("No operation generated from action");
|
|
132
|
-
}
|
|
133
|
-
const newOperation = operations[operations.length - 1];
|
|
134
|
-
generatedOperations.push(newOperation);
|
|
135
|
-
// Write the operation to legacy storage
|
|
136
|
-
try {
|
|
137
|
-
await this.operationStorage.addDocumentOperations(job.documentId, [newOperation], updatedDocument);
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
198
|
return {
|
|
141
|
-
job,
|
|
142
199
|
success: false,
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
generatedOperations,
|
|
201
|
+
operationsWithContext,
|
|
202
|
+
error: new Error("No operation generated from action"),
|
|
145
203
|
};
|
|
146
204
|
}
|
|
147
|
-
|
|
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
|
+
}
|
|
148
223
|
const resultingState = JSON.stringify(updatedDocument.state);
|
|
149
|
-
// Write the operation to new IOperationStore (dual-writing)
|
|
150
224
|
try {
|
|
151
225
|
await this.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
152
226
|
txn.addOperations(newOperation);
|
|
@@ -154,10 +228,10 @@ export class SimpleJobExecutor {
|
|
|
154
228
|
}
|
|
155
229
|
catch (error) {
|
|
156
230
|
return {
|
|
157
|
-
job,
|
|
158
231
|
success: false,
|
|
232
|
+
generatedOperations,
|
|
233
|
+
operationsWithContext,
|
|
159
234
|
error: new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
160
|
-
duration: Date.now() - startTime,
|
|
161
235
|
};
|
|
162
236
|
}
|
|
163
237
|
updatedDocument.header.revision = {
|
|
@@ -172,25 +246,15 @@ export class SimpleJobExecutor {
|
|
|
172
246
|
scope,
|
|
173
247
|
branch: job.branch,
|
|
174
248
|
documentType: document.header.documentType,
|
|
175
|
-
resultingState,
|
|
249
|
+
resultingState,
|
|
176
250
|
},
|
|
177
251
|
});
|
|
178
|
-
|
|
179
|
-
// Emit event for read models with all operations - non-blocking
|
|
180
|
-
if (operationsWithContext.length > 0) {
|
|
181
|
-
this.eventBus
|
|
182
|
-
.emit(OperationEventTypes.OPERATION_WRITTEN, {
|
|
183
|
-
operations: operationsWithContext,
|
|
184
|
-
})
|
|
185
|
-
.catch(() => {
|
|
186
|
-
// TODO: Log error
|
|
187
|
-
});
|
|
252
|
+
actionIndex++;
|
|
188
253
|
}
|
|
189
254
|
return {
|
|
190
|
-
job,
|
|
191
255
|
success: true,
|
|
192
|
-
|
|
193
|
-
|
|
256
|
+
generatedOperations,
|
|
257
|
+
operationsWithContext,
|
|
194
258
|
};
|
|
195
259
|
}
|
|
196
260
|
/**
|
|
@@ -198,7 +262,7 @@ export class SimpleJobExecutor {
|
|
|
198
262
|
* This creates a new document in storage along with its initial operation.
|
|
199
263
|
* For a new document, the operation index is always 0.
|
|
200
264
|
*/
|
|
201
|
-
async executeCreateDocumentAction(job, action, startTime) {
|
|
265
|
+
async executeCreateDocumentAction(job, action, startTime, skip = 0) {
|
|
202
266
|
if (job.scope !== "document") {
|
|
203
267
|
return {
|
|
204
268
|
job,
|
|
@@ -209,36 +273,23 @@ export class SimpleJobExecutor {
|
|
|
209
273
|
}
|
|
210
274
|
const document = createDocumentFromAction(action);
|
|
211
275
|
// Legacy: Store the document in storage
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
job,
|
|
218
|
-
|
|
219
|
-
error: new Error(`Failed to create document in storage: ${error instanceof Error ? error.message : String(error)}`),
|
|
220
|
-
duration: Date.now() - startTime,
|
|
221
|
-
};
|
|
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
|
+
}
|
|
222
283
|
}
|
|
223
|
-
|
|
224
|
-
const operation = {
|
|
225
|
-
index: 0,
|
|
226
|
-
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
227
|
-
hash: "", // Will be computed later
|
|
228
|
-
skip: 0, // Always 0 for new operations; skip > 0 only during reshuffle
|
|
229
|
-
action: action,
|
|
230
|
-
};
|
|
284
|
+
const operation = this.createOperation(action, 0, skip);
|
|
231
285
|
// Legacy: Write the CREATE_DOCUMENT operation to legacy storage
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
job,
|
|
238
|
-
|
|
239
|
-
error: new Error(`Failed to write CREATE_DOCUMENT operation to legacy storage: ${error instanceof Error ? error.message : String(error)}`),
|
|
240
|
-
duration: Date.now() - startTime,
|
|
241
|
-
};
|
|
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
|
+
}
|
|
242
293
|
}
|
|
243
294
|
// Compute resultingState for passing via context (not persisted)
|
|
244
295
|
// Include header and all scopes present in the document state (auth, document, etc.)
|
|
@@ -248,44 +299,13 @@ export class SimpleJobExecutor {
|
|
|
248
299
|
...document.state,
|
|
249
300
|
};
|
|
250
301
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
await this.operationStore.apply(document.header.id, document.header.documentType, job.scope, job.branch, operation.index, (txn) => {
|
|
255
|
-
txn.addOperations(operation);
|
|
256
|
-
});
|
|
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;
|
|
257
305
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
success: false,
|
|
262
|
-
error: new Error(`Failed to write CREATE_DOCUMENT operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
263
|
-
duration: Date.now() - startTime,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
document.header.revision = {
|
|
267
|
-
...document.header.revision,
|
|
268
|
-
[job.scope]: operation.index + 1,
|
|
269
|
-
};
|
|
270
|
-
this.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
|
|
271
|
-
return {
|
|
272
|
-
job,
|
|
273
|
-
success: true,
|
|
274
|
-
operations: [operation],
|
|
275
|
-
operationsWithContext: [
|
|
276
|
-
{
|
|
277
|
-
operation,
|
|
278
|
-
context: {
|
|
279
|
-
documentId: document.header.id,
|
|
280
|
-
scope: job.scope,
|
|
281
|
-
branch: job.branch,
|
|
282
|
-
documentType: document.header.documentType,
|
|
283
|
-
resultingState,
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
],
|
|
287
|
-
duration: Date.now() - startTime,
|
|
288
|
-
};
|
|
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);
|
|
289
309
|
}
|
|
290
310
|
/**
|
|
291
311
|
* Execute a DELETE_DOCUMENT system action.
|
|
@@ -295,12 +315,7 @@ export class SimpleJobExecutor {
|
|
|
295
315
|
async executeDeleteDocumentAction(job, action, startTime) {
|
|
296
316
|
const input = action.input;
|
|
297
317
|
if (!input.documentId) {
|
|
298
|
-
return
|
|
299
|
-
job,
|
|
300
|
-
success: false,
|
|
301
|
-
error: new Error("DELETE_DOCUMENT action requires a documentId in input"),
|
|
302
|
-
duration: Date.now() - startTime,
|
|
303
|
-
};
|
|
318
|
+
return this.buildErrorResult(job, new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
|
|
304
319
|
}
|
|
305
320
|
const documentId = input.documentId;
|
|
306
321
|
let document;
|
|
@@ -308,43 +323,22 @@ export class SimpleJobExecutor {
|
|
|
308
323
|
document = await this.writeCache.getState(documentId, job.scope, job.branch);
|
|
309
324
|
}
|
|
310
325
|
catch (error) {
|
|
311
|
-
return {
|
|
312
|
-
job,
|
|
313
|
-
success: false,
|
|
314
|
-
error: new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`),
|
|
315
|
-
duration: Date.now() - startTime,
|
|
316
|
-
};
|
|
326
|
+
return this.buildErrorResult(job, new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
317
327
|
}
|
|
318
328
|
// Check if document is already deleted
|
|
319
329
|
const documentState = document.state.document;
|
|
320
330
|
if (documentState.isDeleted) {
|
|
321
|
-
return
|
|
322
|
-
job,
|
|
323
|
-
success: false,
|
|
324
|
-
error: new DocumentDeletedError(documentId, documentState.deletedAtUtcIso),
|
|
325
|
-
duration: Date.now() - startTime,
|
|
326
|
-
};
|
|
331
|
+
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
327
332
|
}
|
|
328
|
-
// Determine the next operation index for this scope only (per-scope indexing)
|
|
329
333
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
await this.documentStorage.delete(documentId);
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
return {
|
|
343
|
-
job,
|
|
344
|
-
success: false,
|
|
345
|
-
error: new Error(`Failed to delete document from legacy storage: ${error instanceof Error ? error.message : String(error)}`),
|
|
346
|
-
duration: Date.now() - startTime,
|
|
347
|
-
};
|
|
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
|
+
}
|
|
348
342
|
}
|
|
349
343
|
// Mark the document as deleted in the state for read model indexing
|
|
350
344
|
applyDeleteDocumentAction(document, action);
|
|
@@ -355,131 +349,236 @@ export class SimpleJobExecutor {
|
|
|
355
349
|
document: document.state.document,
|
|
356
350
|
};
|
|
357
351
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
await this.operationStore.apply(documentId, document.header.documentType, job.scope, job.branch, operation.index, (txn) => {
|
|
362
|
-
txn.addOperations(operation);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
catch (error) {
|
|
366
|
-
return {
|
|
367
|
-
job,
|
|
368
|
-
success: false,
|
|
369
|
-
error: new Error(`Failed to write DELETE_DOCUMENT operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
370
|
-
duration: Date.now() - startTime,
|
|
371
|
-
};
|
|
352
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
353
|
+
if (writeError !== null) {
|
|
354
|
+
return writeError;
|
|
372
355
|
}
|
|
373
|
-
return
|
|
374
|
-
job,
|
|
375
|
-
success: true,
|
|
376
|
-
operations: [operation],
|
|
377
|
-
operationsWithContext: [
|
|
378
|
-
{
|
|
379
|
-
operation,
|
|
380
|
-
context: {
|
|
381
|
-
documentId,
|
|
382
|
-
scope: job.scope,
|
|
383
|
-
branch: job.branch,
|
|
384
|
-
documentType: document.header.documentType,
|
|
385
|
-
resultingState,
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
],
|
|
389
|
-
duration: Date.now() - startTime,
|
|
390
|
-
};
|
|
356
|
+
return this.buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
391
357
|
}
|
|
392
358
|
/**
|
|
393
359
|
* Execute an UPGRADE_DOCUMENT system action.
|
|
394
360
|
* This sets the document's initial state from the upgrade action.
|
|
395
361
|
* The operation index is determined from the document's current operation count.
|
|
396
362
|
*/
|
|
397
|
-
async executeUpgradeDocumentAction(job, action, startTime) {
|
|
363
|
+
async executeUpgradeDocumentAction(job, action, startTime, skip = 0) {
|
|
398
364
|
const input = action.input;
|
|
399
365
|
if (!input.documentId) {
|
|
400
|
-
return
|
|
401
|
-
job,
|
|
402
|
-
success: false,
|
|
403
|
-
error: new Error("UPGRADE_DOCUMENT action requires a documentId in input"),
|
|
404
|
-
duration: Date.now() - startTime,
|
|
405
|
-
};
|
|
366
|
+
return this.buildErrorResult(job, new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
406
367
|
}
|
|
407
368
|
const documentId = input.documentId;
|
|
408
|
-
// Load the document from write cache
|
|
409
369
|
let document;
|
|
410
370
|
try {
|
|
411
371
|
document = await this.writeCache.getState(documentId, job.scope, job.branch);
|
|
412
372
|
}
|
|
413
373
|
catch (error) {
|
|
414
|
-
return {
|
|
415
|
-
job,
|
|
416
|
-
success: false,
|
|
417
|
-
error: new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`),
|
|
418
|
-
duration: Date.now() - startTime,
|
|
419
|
-
};
|
|
374
|
+
return this.buildErrorResult(job, new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
420
375
|
}
|
|
421
|
-
// Check if document is deleted
|
|
422
376
|
const documentState = document.state.document;
|
|
423
377
|
if (documentState.isDeleted) {
|
|
424
|
-
return
|
|
425
|
-
job,
|
|
426
|
-
success: false,
|
|
427
|
-
error: new DocumentDeletedError(documentId, documentState.deletedAtUtcIso),
|
|
428
|
-
duration: Date.now() - startTime,
|
|
429
|
-
};
|
|
378
|
+
return this.buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
430
379
|
}
|
|
431
|
-
// Determine the next operation index for this scope only (per-scope indexing)
|
|
432
380
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
433
|
-
// Apply the initialState from the upgrade action
|
|
434
|
-
// The initialState from UPGRADE_DOCUMENT should be merged with the existing base state
|
|
435
|
-
// to preserve auth and document scopes while adding model-specific scopes (global, local, etc.)
|
|
436
381
|
applyUpgradeDocumentAction(document, action);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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,
|
|
440
497
|
timestampUtcMs: action.timestampUtcMs || new Date().toISOString(),
|
|
441
|
-
hash: "",
|
|
442
|
-
skip:
|
|
498
|
+
hash: "",
|
|
499
|
+
skip: skip,
|
|
443
500
|
action: action,
|
|
444
501
|
};
|
|
445
|
-
|
|
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;
|
|
446
509
|
try {
|
|
447
|
-
await this.
|
|
510
|
+
const revisions = await this.operationStore.getRevisions(job.documentId, job.branch);
|
|
511
|
+
latestRevision = revisions.revision[scope] ?? 0;
|
|
448
512
|
}
|
|
449
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) {
|
|
450
531
|
return {
|
|
451
532
|
job,
|
|
452
533
|
success: false,
|
|
453
|
-
error:
|
|
534
|
+
error: result.error,
|
|
454
535
|
duration: Date.now() - startTime,
|
|
455
536
|
};
|
|
456
537
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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,
|
|
461
554
|
};
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// Note: resultingState is NOT persisted in IOperationStore
|
|
555
|
+
}
|
|
556
|
+
async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime) {
|
|
465
557
|
try {
|
|
466
|
-
await this.operationStore.apply(documentId,
|
|
558
|
+
await this.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
|
|
467
559
|
txn.addOperations(operation);
|
|
468
560
|
});
|
|
561
|
+
return null;
|
|
469
562
|
}
|
|
470
563
|
catch (error) {
|
|
471
564
|
return {
|
|
472
565
|
job,
|
|
473
566
|
success: false,
|
|
474
|
-
error: new Error(`Failed to write
|
|
567
|
+
error: new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
|
|
475
568
|
duration: Date.now() - startTime,
|
|
476
569
|
};
|
|
477
570
|
}
|
|
571
|
+
}
|
|
572
|
+
updateDocumentRevision(document, scope, operationIndex) {
|
|
478
573
|
document.header.revision = {
|
|
479
574
|
...document.header.revision,
|
|
480
|
-
[
|
|
575
|
+
[scope]: operationIndex + 1,
|
|
481
576
|
};
|
|
482
|
-
|
|
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) {
|
|
483
582
|
return {
|
|
484
583
|
job,
|
|
485
584
|
success: true,
|
|
@@ -488,10 +587,10 @@ export class SimpleJobExecutor {
|
|
|
488
587
|
{
|
|
489
588
|
operation,
|
|
490
589
|
context: {
|
|
491
|
-
documentId,
|
|
590
|
+
documentId: documentId,
|
|
492
591
|
scope: job.scope,
|
|
493
592
|
branch: job.branch,
|
|
494
|
-
documentType:
|
|
593
|
+
documentType: documentType,
|
|
495
594
|
resultingState,
|
|
496
595
|
},
|
|
497
596
|
},
|
|
@@ -499,5 +598,25 @@ export class SimpleJobExecutor {
|
|
|
499
598
|
duration: Date.now() - startTime,
|
|
500
599
|
};
|
|
501
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
|
+
}
|
|
502
621
|
}
|
|
503
622
|
//# sourceMappingURL=simple-job-executor.js.map
|