@powerhousedao/reactor 5.1.0-dev.3 → 5.1.0-dev.30

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