@powerhousedao/reactor 6.0.0-dev.25 → 6.0.0-dev.27

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 (143) hide show
  1. package/dist/src/cache/document-meta-cache.js +3 -3
  2. package/dist/src/cache/document-meta-cache.js.map +1 -1
  3. package/dist/src/cache/kysely-operation-index.d.ts +2 -1
  4. package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
  5. package/dist/src/cache/kysely-operation-index.js +16 -4
  6. package/dist/src/cache/kysely-operation-index.js.map +1 -1
  7. package/dist/src/cache/kysely-write-cache.js +8 -8
  8. package/dist/src/cache/kysely-write-cache.js.map +1 -1
  9. package/dist/src/cache/operation-index-types.d.ts +2 -1
  10. package/dist/src/cache/operation-index-types.d.ts.map +1 -1
  11. package/dist/src/cache/operation-index-types.js.map +1 -1
  12. package/dist/src/client/reactor-client.d.ts +4 -6
  13. package/dist/src/client/reactor-client.d.ts.map +1 -1
  14. package/dist/src/client/reactor-client.js +20 -25
  15. package/dist/src/client/reactor-client.js.map +1 -1
  16. package/dist/src/client/types.d.ts +1 -4
  17. package/dist/src/client/types.d.ts.map +1 -1
  18. package/dist/src/core/reactor-builder.d.ts.map +1 -1
  19. package/dist/src/core/reactor-builder.js +2 -5
  20. package/dist/src/core/reactor-builder.js.map +1 -1
  21. package/dist/src/core/reactor-client-builder.d.ts +3 -2
  22. package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
  23. package/dist/src/core/reactor-client-builder.js +13 -4
  24. package/dist/src/core/reactor-client-builder.js.map +1 -1
  25. package/dist/src/core/reactor.d.ts +11 -75
  26. package/dist/src/core/reactor.d.ts.map +1 -1
  27. package/dist/src/core/reactor.js +89 -552
  28. package/dist/src/core/reactor.js.map +1 -1
  29. package/dist/src/core/types.d.ts +28 -19
  30. package/dist/src/core/types.d.ts.map +1 -1
  31. package/dist/src/core/utils.d.ts +6 -1
  32. package/dist/src/core/utils.d.ts.map +1 -1
  33. package/dist/src/core/utils.js +22 -6
  34. package/dist/src/core/utils.js.map +1 -1
  35. package/dist/src/events/types.d.ts +24 -8
  36. package/dist/src/events/types.d.ts.map +1 -1
  37. package/dist/src/events/types.js +7 -5
  38. package/dist/src/events/types.js.map +1 -1
  39. package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
  40. package/dist/src/executor/simple-job-executor-manager.js +14 -4
  41. package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
  42. package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
  43. package/dist/src/executor/simple-job-executor.js +24 -9
  44. package/dist/src/executor/simple-job-executor.js.map +1 -1
  45. package/dist/src/index.d.ts +4 -5
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +3 -4
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/job-tracker/in-memory-job-tracker.d.ts +2 -2
  50. package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
  51. package/dist/src/job-tracker/in-memory-job-tracker.js +11 -11
  52. package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
  53. package/dist/src/read-models/base-read-model.js +4 -4
  54. package/dist/src/read-models/base-read-model.js.map +1 -1
  55. package/dist/src/read-models/coordinator.d.ts +2 -2
  56. package/dist/src/read-models/coordinator.d.ts.map +1 -1
  57. package/dist/src/read-models/coordinator.js +8 -8
  58. package/dist/src/read-models/coordinator.js.map +1 -1
  59. package/dist/src/read-models/document-view.d.ts +5 -2
  60. package/dist/src/read-models/document-view.d.ts.map +1 -1
  61. package/dist/src/read-models/document-view.js +128 -28
  62. package/dist/src/read-models/document-view.js.map +1 -1
  63. package/dist/src/shared/awaiter.d.ts +2 -2
  64. package/dist/src/shared/awaiter.d.ts.map +1 -1
  65. package/dist/src/shared/awaiter.js +11 -11
  66. package/dist/src/shared/awaiter.js.map +1 -1
  67. package/dist/src/shared/collect-all-pages.d.ts +7 -0
  68. package/dist/src/shared/collect-all-pages.d.ts.map +1 -0
  69. package/dist/src/shared/collect-all-pages.js +17 -0
  70. package/dist/src/shared/collect-all-pages.js.map +1 -0
  71. package/dist/src/shared/types.d.ts +4 -4
  72. package/dist/src/shared/types.d.ts.map +1 -1
  73. package/dist/src/shared/types.js +4 -4
  74. package/dist/src/shared/types.js.map +1 -1
  75. package/dist/src/signer/passthrough-signer.d.ts +1 -1
  76. package/dist/src/signer/passthrough-signer.d.ts.map +1 -1
  77. package/dist/src/signer/passthrough-signer.js +1 -3
  78. package/dist/src/signer/passthrough-signer.js.map +1 -1
  79. package/dist/src/storage/interfaces.d.ts +38 -94
  80. package/dist/src/storage/interfaces.d.ts.map +1 -1
  81. package/dist/src/storage/interfaces.js.map +1 -1
  82. package/dist/src/storage/kysely/document-indexer.d.ts +6 -6
  83. package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -1
  84. package/dist/src/storage/kysely/document-indexer.js +123 -52
  85. package/dist/src/storage/kysely/document-indexer.js.map +1 -1
  86. package/dist/src/storage/kysely/store.d.ts +2 -1
  87. package/dist/src/storage/kysely/store.d.ts.map +1 -1
  88. package/dist/src/storage/kysely/store.js +33 -15
  89. package/dist/src/storage/kysely/store.js.map +1 -1
  90. package/dist/src/subs/subscription-notification-read-model.d.ts +1 -1
  91. package/dist/src/subs/subscription-notification-read-model.js +1 -1
  92. package/dist/src/sync/channels/composite-channel-factory.d.ts.map +1 -1
  93. package/dist/src/sync/channels/composite-channel-factory.js +5 -2
  94. package/dist/src/sync/channels/composite-channel-factory.js.map +1 -1
  95. package/dist/src/sync/channels/gql-channel-factory.d.ts.map +1 -1
  96. package/dist/src/sync/channels/gql-channel-factory.js +5 -2
  97. package/dist/src/sync/channels/gql-channel-factory.js.map +1 -1
  98. package/dist/src/sync/channels/gql-channel.d.ts +4 -9
  99. package/dist/src/sync/channels/gql-channel.d.ts.map +1 -1
  100. package/dist/src/sync/channels/gql-channel.js +11 -31
  101. package/dist/src/sync/channels/gql-channel.js.map +1 -1
  102. package/dist/src/sync/channels/index.d.ts +2 -0
  103. package/dist/src/sync/channels/index.d.ts.map +1 -1
  104. package/dist/src/sync/channels/index.js +2 -0
  105. package/dist/src/sync/channels/index.js.map +1 -1
  106. package/dist/src/sync/channels/interval-poll-timer.d.ts +18 -0
  107. package/dist/src/sync/channels/interval-poll-timer.d.ts.map +1 -0
  108. package/dist/src/sync/channels/interval-poll-timer.js +43 -0
  109. package/dist/src/sync/channels/interval-poll-timer.js.map +1 -0
  110. package/dist/src/sync/channels/poll-timer.d.ts +14 -0
  111. package/dist/src/sync/channels/poll-timer.d.ts.map +1 -0
  112. package/dist/src/sync/channels/poll-timer.js +2 -0
  113. package/dist/src/sync/channels/poll-timer.js.map +1 -0
  114. package/dist/src/sync/channels/utils.d.ts.map +1 -1
  115. package/dist/src/sync/channels/utils.js +2 -2
  116. package/dist/src/sync/channels/utils.js.map +1 -1
  117. package/dist/src/sync/index.d.ts +3 -3
  118. package/dist/src/sync/index.d.ts.map +1 -1
  119. package/dist/src/sync/index.js +2 -2
  120. package/dist/src/sync/index.js.map +1 -1
  121. package/dist/src/sync/interfaces.d.ts +11 -1
  122. package/dist/src/sync/interfaces.d.ts.map +1 -1
  123. package/dist/src/sync/sync-awaiter.d.ts +34 -0
  124. package/dist/src/sync/sync-awaiter.d.ts.map +1 -0
  125. package/dist/src/sync/sync-awaiter.js +124 -0
  126. package/dist/src/sync/sync-awaiter.js.map +1 -0
  127. package/dist/src/sync/sync-manager.d.ts +6 -2
  128. package/dist/src/sync/sync-manager.d.ts.map +1 -1
  129. package/dist/src/sync/sync-manager.js +87 -9
  130. package/dist/src/sync/sync-manager.js.map +1 -1
  131. package/dist/src/sync/sync-operation.d.ts +2 -1
  132. package/dist/src/sync/sync-operation.d.ts.map +1 -1
  133. package/dist/src/sync/sync-operation.js +3 -1
  134. package/dist/src/sync/sync-operation.js.map +1 -1
  135. package/dist/src/sync/types.d.ts +62 -0
  136. package/dist/src/sync/types.d.ts.map +1 -1
  137. package/dist/src/sync/types.js +10 -0
  138. package/dist/src/sync/types.js.map +1 -1
  139. package/package.json +3 -3
  140. package/dist/src/storage/consistency-aware-legacy-storage.d.ts +0 -33
  141. package/dist/src/storage/consistency-aware-legacy-storage.d.ts.map +0 -1
  142. package/dist/src/storage/consistency-aware-legacy-storage.js +0 -65
  143. package/dist/src/storage/consistency-aware-legacy-storage.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { addRelationshipAction, createDocumentAction, deleteDocumentAction, removeRelationshipAction, upgradeDocumentAction, } from "#actions/index.js";
2
2
  import { AbortError } from "document-drive";
3
3
  import { v4 as uuidv4 } from "uuid";
4
+ import { ReactorEventTypes } from "../events/types.js";
4
5
  import { createMutableShutdownStatus } from "../shared/factories.js";
5
6
  import { JobStatus } from "../shared/types.js";
6
7
  import { matchesScope } from "../shared/utils.js";
7
- import { filterByType, getSharedScope, signAction, signActions, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
8
+ import { filterByType, getSharedActionScope, getSharedOperationScope, signAction, signActions, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
8
9
  /**
9
10
  * This class implements the IReactor interface and serves as the main entry point
10
11
  * for the new Reactor architecture.
@@ -12,7 +13,6 @@ import { filterByType, getSharedScope, signAction, signActions, toErrorInfo, top
12
13
  export class Reactor {
13
14
  logger;
14
15
  documentModelRegistry;
15
- documentStorage;
16
16
  shutdownStatus;
17
17
  setShutdown;
18
18
  queue;
@@ -20,57 +20,44 @@ export class Reactor {
20
20
  readModelCoordinator;
21
21
  features;
22
22
  documentView;
23
- _documentIndexer;
23
+ documentIndexer;
24
24
  operationStore;
25
- constructor(logger, documentModelRegistry, documentStorage, queue, jobTracker, readModelCoordinator, features, documentView, documentIndexer, operationStore) {
25
+ eventBus;
26
+ constructor(logger, documentModelRegistry, queue, jobTracker, readModelCoordinator, features, documentView, documentIndexer, operationStore, eventBus) {
26
27
  this.logger = logger;
27
28
  this.documentModelRegistry = documentModelRegistry;
28
- this.documentStorage = documentStorage;
29
29
  this.queue = queue;
30
30
  this.jobTracker = jobTracker;
31
31
  this.readModelCoordinator = readModelCoordinator;
32
32
  this.features = features;
33
33
  this.documentView = documentView;
34
- this._documentIndexer = documentIndexer;
34
+ this.documentIndexer = documentIndexer;
35
35
  this.operationStore = operationStore;
36
+ this.eventBus = eventBus;
36
37
  const [status, setter] = createMutableShutdownStatus(false);
37
38
  this.shutdownStatus = status;
38
39
  this.setShutdown = setter;
39
- this.logger.verbose("Reactor({ legacyStorage: @legacy })", features.legacyStorageEnabled);
40
40
  this.readModelCoordinator.start();
41
41
  }
42
- /**
43
- * Signals that the reactor should shutdown.
44
- */
45
42
  kill() {
46
43
  this.logger.verbose("kill()");
47
- // Mark the reactor as shutdown
48
44
  this.setShutdown(true);
49
- // Stop the read model coordinator
50
45
  this.readModelCoordinator.stop();
51
- // Stop the job tracker
52
46
  this.jobTracker.shutdown();
53
47
  return this.shutdownStatus;
54
48
  }
55
- /**
56
- * Retrieves a list of document model specifications
57
- */
58
49
  getDocumentModels(namespace, paging, signal) {
59
50
  this.logger.verbose("getDocumentModels(@namespace, @paging)", namespace, paging);
60
- // Get document model modules from the registry + filter
51
+ if (signal?.aborted) {
52
+ throw new AbortError();
53
+ }
61
54
  const modules = this.documentModelRegistry.getAllModules();
62
55
  const filteredModels = modules.filter((module) => !namespace || module.documentModel.global.id.startsWith(namespace));
63
- // Apply paging
64
56
  const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
65
57
  const limit = paging?.limit || filteredModels.length;
66
58
  const pagedModels = filteredModels.slice(startIndex, startIndex + limit);
67
- // Create paged results
68
59
  const hasMore = startIndex + limit < filteredModels.length;
69
60
  const nextCursor = hasMore ? String(startIndex + limit) : undefined;
70
- // even thought this is currently synchronous, they could have passed in an already-aborted signal
71
- if (signal?.aborted) {
72
- throw new AbortError();
73
- }
74
61
  return Promise.resolve({
75
62
  results: pagedModels,
76
63
  options: paging || { cursor: "0", limit: filteredModels.length },
@@ -80,197 +67,70 @@ export class Reactor {
80
67
  : undefined,
81
68
  });
82
69
  }
83
- /**
84
- * Retrieves a specific PHDocument by id
85
- */
86
70
  async get(id, view, consistencyToken, signal) {
87
71
  this.logger.verbose("get(@id, @view)", id, view);
88
- if (this.features.legacyStorageEnabled) {
89
- const document = await this.documentStorage.get(id, consistencyToken, signal);
90
- if (signal?.aborted) {
91
- throw new AbortError();
92
- }
93
- const childIds = await this.documentStorage.getChildren(id, consistencyToken, signal);
94
- if (signal?.aborted) {
95
- throw new AbortError();
96
- }
97
- for (const scope in document.state) {
98
- if (!matchesScope(view, scope)) {
99
- delete document.state[scope];
100
- }
101
- }
102
- return {
103
- document,
104
- childIds,
105
- };
106
- }
107
- else {
108
- const document = await this.documentView.get(id, view, consistencyToken, signal);
109
- if (signal?.aborted) {
110
- throw new AbortError();
111
- }
112
- const relationships = await this._documentIndexer.getOutgoing(id, ["child"], consistencyToken, signal);
113
- if (signal?.aborted) {
114
- throw new AbortError();
115
- }
116
- const childIds = relationships.map((rel) => rel.targetId);
117
- return {
118
- document,
119
- childIds,
120
- };
121
- }
72
+ return await this.documentView.get(id, view, consistencyToken, signal);
122
73
  }
123
- /**
124
- * Retrieves a specific PHDocument by slug
125
- */
126
74
  async getBySlug(slug, view, consistencyToken, signal) {
127
75
  this.logger.verbose("getBySlug(@slug, @view)", slug, view);
128
- if (this.features.legacyStorageEnabled) {
129
- let ids;
130
- try {
131
- ids = await this.documentStorage.resolveIds([slug], consistencyToken, signal);
132
- }
133
- catch (error) {
134
- if (error instanceof Error && error.message.includes("not found")) {
135
- throw new Error(`Document not found with slug: ${slug}`);
136
- }
137
- throw error;
138
- }
139
- if (ids.length === 0 || !ids[0]) {
140
- throw new Error(`Document not found with slug: ${slug}`);
141
- }
142
- return await this.get(ids[0], view, consistencyToken, signal);
143
- }
144
- else {
145
- const documentId = await this.documentView.resolveSlug(slug, view, consistencyToken, signal);
146
- if (!documentId) {
147
- throw new Error(`Document not found with slug: ${slug}`);
148
- }
149
- return await this.get(documentId, view, consistencyToken, signal);
76
+ const documentId = await this.documentView.resolveSlug(slug, view, consistencyToken, signal);
77
+ if (!documentId) {
78
+ throw new Error(`Document not found with slug: ${slug}`);
150
79
  }
80
+ return await this.get(documentId, view, consistencyToken, signal);
151
81
  }
152
- /**
153
- * Retrieves a specific PHDocument by identifier (either id or slug)
154
- */
155
82
  async getByIdOrSlug(identifier, view, consistencyToken, signal) {
156
83
  this.logger.verbose("getByIdOrSlug(@identifier, @view)", identifier, view);
157
- if (this.features.legacyStorageEnabled) {
158
- try {
159
- return await this.get(identifier, view, consistencyToken, signal);
160
- }
161
- catch {
162
- try {
163
- const ids = await this.documentStorage.resolveIds([identifier], consistencyToken, signal);
164
- if (ids.length === 0 || !ids[0]) {
165
- throw new Error(`Document not found: ${identifier}`);
166
- }
167
- return await this.get(ids[0], view, consistencyToken, signal);
168
- }
169
- catch {
170
- throw new Error(`Document not found: ${identifier}`);
171
- }
172
- }
84
+ return await this.documentView.getByIdOrSlug(identifier, view, consistencyToken, signal);
85
+ }
86
+ async getChildren(documentId, consistencyToken, signal) {
87
+ const relationships = await this.documentIndexer.getOutgoing(documentId, ["child"], undefined, consistencyToken, signal);
88
+ if (signal?.aborted) {
89
+ throw new AbortError();
173
90
  }
174
- else {
175
- const document = await this.documentView.getByIdOrSlug(identifier, view, consistencyToken, signal);
176
- if (signal?.aborted) {
177
- throw new AbortError();
178
- }
179
- const relationships = await this._documentIndexer.getOutgoing(document.header.id, ["child"], consistencyToken, signal);
180
- if (signal?.aborted) {
181
- throw new AbortError();
182
- }
183
- const childIds = relationships.map((rel) => rel.targetId);
184
- return {
185
- document,
186
- childIds,
187
- };
91
+ return relationships.results.map((rel) => rel.targetId);
92
+ }
93
+ async getParents(childId, consistencyToken, signal) {
94
+ const relationships = await this.documentIndexer.getIncoming(childId, ["parent"], undefined, consistencyToken, signal);
95
+ if (signal?.aborted) {
96
+ throw new AbortError();
188
97
  }
98
+ return relationships.results.map((rel) => rel.sourceId);
189
99
  }
190
- /**
191
- * Retrieves the operations for a document
192
- */
193
100
  async getOperations(documentId, view, filter, paging, consistencyToken, signal) {
194
101
  this.logger.verbose("getOperations(@documentId, @view, @filter, @paging)", documentId, view, filter, paging);
195
- if (this.features.legacyStorageEnabled) {
196
- const document = await this.documentStorage.get(documentId, consistencyToken, signal);
197
- if (signal?.aborted) {
198
- throw new AbortError();
199
- }
200
- const operations = document.operations;
201
- const result = {};
202
- for (const scope in operations) {
203
- if (matchesScope(view, scope)) {
204
- let scopeOperations = operations[scope];
205
- if (filter) {
206
- if (filter.actionTypes && filter.actionTypes.length > 0) {
207
- scopeOperations = scopeOperations.filter((op) => filter.actionTypes.includes(op.action.type));
208
- }
209
- if (filter.sinceRevision !== undefined) {
210
- scopeOperations = scopeOperations.filter((op) => op.index >= filter.sinceRevision);
211
- }
212
- if (filter.timestampFrom) {
213
- const fromMs = new Date(filter.timestampFrom).getTime();
214
- scopeOperations = scopeOperations.filter((op) => new Date(op.timestampUtcMs).getTime() >= fromMs);
215
- }
216
- if (filter.timestampTo) {
217
- const toMs = new Date(filter.timestampTo).getTime();
218
- scopeOperations = scopeOperations.filter((op) => new Date(op.timestampUtcMs).getTime() <= toMs);
219
- }
220
- }
221
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
222
- const limit = paging?.limit || scopeOperations.length;
223
- const pagedOperations = scopeOperations.slice(startIndex, startIndex + limit);
224
- result[scope] = {
225
- results: pagedOperations,
226
- options: { cursor: String(startIndex + limit), limit },
227
- };
228
- }
229
- }
230
- return Promise.resolve(result);
102
+ const branch = view?.branch || "main";
103
+ const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
104
+ if (signal?.aborted) {
105
+ throw new AbortError();
231
106
  }
232
- else {
233
- const branch = view?.branch || "main";
234
- const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
107
+ const allScopes = Object.keys(revisions.revision);
108
+ const result = {};
109
+ for (const scope of allScopes) {
110
+ if (!matchesScope(view, scope)) {
111
+ continue;
112
+ }
235
113
  if (signal?.aborted) {
236
114
  throw new AbortError();
237
115
  }
238
- const allScopes = Object.keys(revisions.revision);
239
- const result = {};
240
- for (const scope of allScopes) {
241
- if (!matchesScope(view, scope)) {
242
- continue;
243
- }
244
- if (signal?.aborted) {
245
- throw new AbortError();
246
- }
247
- const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, filter, paging, signal);
248
- const currentCursor = paging?.cursor ? parseInt(paging.cursor) : 0;
249
- const currentLimit = paging?.limit || 100;
250
- result[scope] = {
251
- results: scopeResult.items,
252
- options: {
253
- cursor: scopeResult.nextCursor || String(currentCursor),
254
- limit: currentLimit,
255
- },
256
- nextCursor: scopeResult.nextCursor,
257
- next: scopeResult.hasMore
258
- ? async () => {
259
- const nextPage = await this.getOperations(documentId, view, filter, {
260
- cursor: scopeResult.nextCursor,
261
- limit: currentLimit,
262
- }, consistencyToken, signal);
263
- return nextPage[scope];
264
- }
265
- : undefined,
266
- };
267
- }
268
- return result;
116
+ const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, filter, paging, signal);
117
+ result[scope] = {
118
+ results: scopeResult.results,
119
+ options: scopeResult.options,
120
+ nextCursor: scopeResult.nextCursor,
121
+ next: scopeResult.next
122
+ ? async () => {
123
+ const nextPage = await this.getOperations(documentId, view, filter, {
124
+ cursor: scopeResult.nextCursor,
125
+ limit: scopeResult.options.limit,
126
+ }, consistencyToken, signal);
127
+ return nextPage[scope];
128
+ }
129
+ : undefined,
130
+ };
269
131
  }
132
+ return result;
270
133
  }
271
- /**
272
- * Filters documents by criteria and returns a list of them
273
- */
274
134
  async find(search, view, paging, consistencyToken, signal) {
275
135
  this.logger.verbose("find(@search, @view, @paging)", search, view, paging);
276
136
  let results;
@@ -290,7 +150,7 @@ export class Reactor {
290
150
  }
291
151
  }
292
152
  else if (search.parentId) {
293
- results = await this.findByParentId(search.parentId, view, paging, signal);
153
+ results = await this.findByParentId(search.parentId, view, paging, consistencyToken, signal);
294
154
  if (search.type) {
295
155
  results = filterByType(results, search.type);
296
156
  }
@@ -306,9 +166,6 @@ export class Reactor {
306
166
  }
307
167
  return results;
308
168
  }
309
- /**
310
- * Creates a document
311
- */
312
169
  async create(document, signer, signal, meta) {
313
170
  this.logger.verbose("create(@id, @type, @slug)", document.header.id, document.header.documentType, document.header.slug);
314
171
  const createdAtUtcIso = new Date().toISOString();
@@ -342,12 +199,10 @@ export class Reactor {
342
199
  toVersion: document.state.document.version,
343
200
  initialState: document.state,
344
201
  });
345
- // Sign actions if signer is provided
346
202
  let actions = [createAction, upgradeAction];
347
203
  if (signer) {
348
204
  actions = await signActions(actions, signer, signal);
349
205
  }
350
- // Create a single job with both CREATE_DOCUMENT and UPGRADE_DOCUMENT actions
351
206
  const job = {
352
207
  id: uuidv4(),
353
208
  kind: "mutation",
@@ -362,7 +217,6 @@ export class Reactor {
362
217
  errorHistory: [],
363
218
  meta,
364
219
  };
365
- // Create job info and register with tracker
366
220
  const jobInfo = {
367
221
  id: job.id,
368
222
  status: JobStatus.PENDING,
@@ -375,13 +229,10 @@ export class Reactor {
375
229
  meta,
376
230
  };
377
231
  this.jobTracker.registerJob(jobInfo);
378
- // Enqueue the job
232
+ this.emitJobPending(jobInfo.id, meta);
379
233
  await this.queue.enqueue(job);
380
234
  return jobInfo;
381
235
  }
382
- /**
383
- * Deletes a document
384
- */
385
236
  async deleteDocument(id, signer, signal, meta) {
386
237
  this.logger.verbose("deleteDocument(@id)", id);
387
238
  const createdAtUtcIso = new Date().toISOString();
@@ -389,7 +240,6 @@ export class Reactor {
389
240
  throw new AbortError();
390
241
  }
391
242
  let action = deleteDocumentAction(id);
392
- // Sign action if signer is provided
393
243
  if (signer) {
394
244
  action = await signAction(action, signer, signal);
395
245
  }
@@ -419,21 +269,17 @@ export class Reactor {
419
269
  meta,
420
270
  };
421
271
  this.jobTracker.registerJob(jobInfo);
272
+ this.emitJobPending(jobInfo.id, meta);
422
273
  await this.queue.enqueue(job);
423
274
  return jobInfo;
424
275
  }
425
- /**
426
- * Applies a list of actions to a document
427
- */
428
276
  async execute(docId, branch, actions, signal, meta) {
429
277
  this.logger.verbose("execute(@docId, @branch, @actions)", docId, branch, actions);
430
278
  if (signal?.aborted) {
431
279
  throw new AbortError();
432
280
  }
433
281
  const createdAtUtcIso = new Date().toISOString();
434
- // Determine scope from first action (all actions should have the same scope)
435
- const scope = actions.length > 0 ? actions[0].scope || "global" : "global";
436
- // Create a single job with all actions
282
+ const scope = getSharedActionScope(actions);
437
283
  const job = {
438
284
  id: uuidv4(),
439
285
  kind: "mutation",
@@ -448,7 +294,6 @@ export class Reactor {
448
294
  errorHistory: [],
449
295
  meta,
450
296
  };
451
- // Create job info and register with tracker
452
297
  const jobInfo = {
453
298
  id: job.id,
454
299
  status: JobStatus.PENDING,
@@ -461,18 +306,13 @@ export class Reactor {
461
306
  meta,
462
307
  };
463
308
  this.jobTracker.registerJob(jobInfo);
464
- // Enqueue the job
309
+ this.emitJobPending(jobInfo.id, meta);
465
310
  await this.queue.enqueue(job);
466
311
  if (signal?.aborted) {
467
312
  throw new AbortError();
468
313
  }
469
314
  return jobInfo;
470
315
  }
471
- /**
472
- * Imports pre-existing operations that were produced by another reactor.
473
- * This function may cause a reshuffle, which will generate additional
474
- * operations.
475
- */
476
316
  async load(docId, branch, operations, signal, meta) {
477
317
  this.logger.verbose("load(@docId, @branch, @count, @operations)", docId, branch, operations.length, operations);
478
318
  if (signal?.aborted) {
@@ -481,13 +321,7 @@ export class Reactor {
481
321
  if (operations.length === 0) {
482
322
  throw new Error("load requires at least one operation");
483
323
  }
484
- // validate the operations
485
- const scope = getSharedScope(operations);
486
- operations.forEach((operation, index) => {
487
- if (!operation.timestampUtcMs) {
488
- throw new Error(`Operation at position ${index} is missing timestampUtcMs`);
489
- }
490
- });
324
+ const scope = getSharedOperationScope(operations);
491
325
  const createdAtUtcIso = new Date().toISOString();
492
326
  const job = {
493
327
  id: uuidv4(),
@@ -515,15 +349,13 @@ export class Reactor {
515
349
  meta,
516
350
  };
517
351
  this.jobTracker.registerJob(jobInfo);
352
+ this.emitJobPending(jobInfo.id, meta);
518
353
  await this.queue.enqueue(job);
519
354
  if (signal?.aborted) {
520
355
  throw new AbortError();
521
356
  }
522
357
  return jobInfo;
523
358
  }
524
- /**
525
- * Applies multiple mutations across documents with dependency management
526
- */
527
359
  async executeBatch(request, signal, meta) {
528
360
  this.logger.verbose("executeBatch(@count jobs)", request.jobs.length);
529
361
  if (signal?.aborted) {
@@ -553,6 +385,7 @@ export class Reactor {
553
385
  meta,
554
386
  };
555
387
  this.jobTracker.registerJob(jobInfo);
388
+ this.emitJobPending(jobInfo.id, meta);
556
389
  jobInfos.set(jobPlan.key, jobInfo);
557
390
  }
558
391
  const sortedKeys = topologicalSort(request.jobs);
@@ -603,39 +436,28 @@ export class Reactor {
603
436
  };
604
437
  return result;
605
438
  }
606
- /**
607
- * Adds multiple documents as children to another
608
- */
609
439
  async addChildren(parentId, documentIds, branch = "main", signer, signal) {
610
440
  this.logger.verbose("addChildren(@parentId, @count children, @branch)", parentId, documentIds.length, branch);
611
441
  if (signal?.aborted) {
612
442
  throw new AbortError();
613
443
  }
614
444
  let actions = documentIds.map((childId) => addRelationshipAction(parentId, childId, "child"));
615
- // Sign actions if signer is provided
616
445
  if (signer) {
617
446
  actions = await signActions(actions, signer, signal);
618
447
  }
619
448
  return await this.execute(parentId, branch, actions, signal);
620
449
  }
621
- /**
622
- * Removes multiple documents as children from another
623
- */
624
450
  async removeChildren(parentId, documentIds, branch = "main", signer, signal) {
625
451
  this.logger.verbose("removeChildren(@parentId, @count children, @branch)", parentId, documentIds.length, branch);
626
452
  if (signal?.aborted) {
627
453
  throw new AbortError();
628
454
  }
629
455
  let actions = documentIds.map((childId) => removeRelationshipAction(parentId, childId, "child"));
630
- // Sign actions if signer is provided
631
456
  if (signer) {
632
457
  actions = await signActions(actions, signer, signal);
633
458
  }
634
459
  return await this.execute(parentId, branch, actions, signal);
635
460
  }
636
- /**
637
- * Retrieves the status of a job
638
- */
639
461
  getJobStatus(jobId, signal) {
640
462
  this.logger.verbose("getJobStatus(@jobId)", jobId);
641
463
  if (signal?.aborted) {
@@ -643,7 +465,6 @@ export class Reactor {
643
465
  }
644
466
  const jobInfo = this.jobTracker.getJobStatus(jobId);
645
467
  if (!jobInfo) {
646
- // Job not found - return FAILED status with appropriate error
647
468
  const now = new Date().toISOString();
648
469
  return Promise.resolve({
649
470
  id: jobId,
@@ -660,327 +481,43 @@ export class Reactor {
660
481
  }
661
482
  return Promise.resolve(jobInfo);
662
483
  }
663
- /**
664
- * Finds documents by their IDs
665
- */
666
484
  async findByIds(ids, view, paging, consistencyToken, signal) {
667
485
  this.logger.verbose("findByIds(@count ids)", ids.length);
668
- if (consistencyToken) {
669
- await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
670
- }
671
- if (this.features.legacyStorageEnabled) {
672
- const documents = [];
673
- // Fetch each document by ID using storage directly
674
- for (const id of ids) {
675
- if (signal?.aborted) {
676
- throw new AbortError();
677
- }
678
- let document;
679
- try {
680
- document = await this.documentStorage.get(id, consistencyToken, signal);
681
- }
682
- catch {
683
- // Skip documents that don't exist or can't be accessed
684
- // This matches the behavior expected from a search operation
685
- continue;
686
- }
687
- // Apply view filter - This will be removed when we pass the viewfilter along
688
- // to the underlying store, but is here now for the interface.
689
- for (const scope in document.state) {
690
- if (!matchesScope(view, scope)) {
691
- delete document.state[scope];
692
- }
693
- }
694
- documents.push(document);
695
- }
696
- if (signal?.aborted) {
697
- throw new AbortError();
698
- }
699
- // Apply paging
700
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
701
- const limit = paging?.limit || documents.length;
702
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
703
- // Create paged results
704
- const hasMore = startIndex + limit < documents.length;
705
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
706
- return {
707
- results: pagedDocuments,
708
- options: paging || { cursor: "0", limit: documents.length },
709
- nextCursor,
710
- next: hasMore
711
- ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, consistencyToken, signal)
712
- : undefined,
713
- };
714
- }
715
- else {
716
- const documents = [];
717
- // Fetch each document by ID using documentView
718
- for (const id of ids) {
719
- if (signal?.aborted) {
720
- throw new AbortError();
721
- }
722
- try {
723
- const document = await this.documentView.get(id, view, undefined, signal);
724
- documents.push(document);
725
- }
726
- catch {
727
- // Skip documents that don't exist or can't be accessed
728
- continue;
729
- }
730
- }
731
- if (signal?.aborted) {
732
- throw new AbortError();
733
- }
734
- // Apply paging
735
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
736
- const limit = paging?.limit || documents.length;
737
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
738
- // Create paged results
739
- const hasMore = startIndex + limit < documents.length;
740
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
741
- return {
742
- results: pagedDocuments,
743
- options: paging || { cursor: "0", limit: documents.length },
744
- nextCursor,
745
- next: hasMore
746
- ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, consistencyToken, signal)
747
- : undefined,
748
- };
749
- }
486
+ const startIndex = paging?.cursor ? parseInt(paging.cursor) || 0 : 0;
487
+ const limit = paging?.limit || ids.length;
488
+ const pagedIds = ids.slice(startIndex, startIndex + limit);
489
+ const results = await this.documentView.getMany(pagedIds, view, consistencyToken, signal);
490
+ const hasMore = startIndex + limit < ids.length;
491
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
492
+ return {
493
+ results,
494
+ options: paging || { cursor: "0", limit: ids.length },
495
+ nextCursor,
496
+ };
750
497
  }
751
- /**
752
- * Finds documents by their slugs
753
- */
754
498
  async findBySlugs(slugs, view, paging, consistencyToken, signal) {
755
499
  this.logger.verbose("findBySlugs(@count slugs)", slugs.length);
756
- if (consistencyToken) {
757
- await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
758
- }
759
- if (this.features.legacyStorageEnabled) {
760
- const documents = [];
761
- // Use storage to resolve slugs to IDs
762
- let ids;
763
- try {
764
- ids = await this.documentStorage.resolveIds(slugs, consistencyToken, signal);
765
- }
766
- catch {
767
- // If slug resolution fails, return empty results
768
- // This matches the behavior expected from a search operation
769
- ids = [];
770
- }
771
- // Fetch each document by resolved ID
772
- for (const id of ids) {
773
- if (signal?.aborted) {
774
- throw new AbortError();
775
- }
776
- let document;
777
- try {
778
- document = await this.documentStorage.get(id, consistencyToken, signal);
779
- }
780
- catch {
781
- // Skip documents that don't exist or can't be accessed
782
- continue;
783
- }
784
- // Apply view filter - This will be removed when we pass the viewfilter along
785
- // to the underlying store, but is here now for the interface.
786
- for (const scope in document.state) {
787
- if (!matchesScope(view, scope)) {
788
- delete document.state[scope];
789
- }
790
- }
791
- documents.push(document);
792
- }
793
- if (signal?.aborted) {
794
- throw new AbortError();
795
- }
796
- // Apply paging - this will be removed when we pass the paging along
797
- // to the underlying store, but is here now for the interface.
798
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
799
- const limit = paging?.limit || documents.length;
800
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
801
- // Create paged results
802
- const hasMore = startIndex + limit < documents.length;
803
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
804
- return {
805
- results: pagedDocuments,
806
- options: paging || { cursor: "0", limit: documents.length },
807
- nextCursor,
808
- next: hasMore
809
- ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, consistencyToken, signal)
810
- : undefined,
811
- };
812
- }
813
- else {
814
- const documents = [];
815
- // Resolve each slug to a document ID
816
- const documentIds = [];
817
- for (const slug of slugs) {
818
- if (signal?.aborted) {
819
- throw new AbortError();
820
- }
821
- const documentId = await this.documentView.resolveSlug(slug, view, undefined, signal);
822
- if (documentId) {
823
- documentIds.push(documentId);
824
- }
825
- }
826
- // Fetch each document
827
- for (const documentId of documentIds) {
828
- if (signal?.aborted) {
829
- throw new AbortError();
830
- }
831
- try {
832
- const document = await this.documentView.get(documentId, view, undefined, signal);
833
- documents.push(document);
834
- }
835
- catch {
836
- // Skip documents that don't exist or can't be accessed
837
- continue;
838
- }
839
- }
840
- if (signal?.aborted) {
841
- throw new AbortError();
842
- }
843
- // Apply paging
844
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
845
- const limit = paging?.limit || documents.length;
846
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
847
- // Create paged results
848
- const hasMore = startIndex + limit < documents.length;
849
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
850
- return {
851
- results: pagedDocuments,
852
- options: paging || { cursor: "0", limit: documents.length },
853
- nextCursor,
854
- next: hasMore
855
- ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, consistencyToken, signal)
856
- : undefined,
857
- };
858
- }
500
+ const ids = await this.documentView.resolveSlugs(slugs, view, consistencyToken, signal);
501
+ return await this.findByIds(ids, view, paging, consistencyToken, signal);
859
502
  }
860
- /**
861
- * Finds documents by parent ID
862
- */
863
- async findByParentId(parentId, view, paging, signal) {
503
+ async findByParentId(parentId, view, paging, consistencyToken, signal) {
864
504
  this.logger.verbose("findByParentId(@parentId)", parentId);
865
- // Get child relationships from indexer
866
- const relationships = await this._documentIndexer.getOutgoing(parentId, ["child"], undefined, signal);
867
- if (signal?.aborted) {
868
- throw new AbortError();
869
- }
870
- const documents = [];
871
- // Fetch each child document using the appropriate storage method
872
- for (const relationship of relationships) {
873
- if (signal?.aborted) {
874
- throw new AbortError();
875
- }
876
- try {
877
- let document;
878
- if (this.features.legacyStorageEnabled) {
879
- document = await this.documentStorage.get(relationship.targetId);
880
- // Apply view filter for legacy storage
881
- for (const scope in document.state) {
882
- if (!matchesScope(view, scope)) {
883
- delete document.state[scope];
884
- }
885
- }
886
- }
887
- else {
888
- document = await this.documentView.get(relationship.targetId, view, undefined, signal);
889
- }
890
- documents.push(document);
891
- }
892
- catch {
893
- // Skip documents that don't exist or can't be accessed
894
- continue;
895
- }
896
- }
897
- if (signal?.aborted) {
898
- throw new AbortError();
899
- }
900
- // Apply paging
901
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
902
- const limit = paging?.limit || documents.length;
903
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
904
- // Create paged results
905
- const hasMore = startIndex + limit < documents.length;
906
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
907
- return {
908
- results: pagedDocuments,
909
- options: paging || { cursor: "0", limit: documents.length },
910
- nextCursor,
911
- next: hasMore
912
- ? () => this.findByParentId(parentId, view, { cursor: nextCursor, limit }, signal)
913
- : undefined,
914
- };
505
+ const relationships = await this.documentIndexer.getOutgoing(parentId, ["child"], paging, consistencyToken, signal);
506
+ const ids = relationships.results.map((rel) => rel.targetId);
507
+ return await this.findByIds(ids, view, paging, undefined, signal);
915
508
  }
916
- /**
917
- * Finds documents by type
918
- */
919
509
  async findByType(type, view, paging, consistencyToken, signal) {
920
510
  this.logger.verbose("findByType(@type)", type);
921
- if (consistencyToken) {
922
- await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
923
- }
924
- if (this.features.legacyStorageEnabled) {
925
- const documents = [];
926
- // Use storage's findByType method directly
927
- const cursor = paging?.cursor;
928
- const limit = paging?.limit || 100;
929
- // Get document IDs of the specified type
930
- const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor, consistencyToken, signal);
931
- if (signal?.aborted) {
932
- throw new AbortError();
933
- }
934
- // Fetch each document by its ID
935
- for (const documentId of documentIds) {
936
- if (signal?.aborted) {
937
- throw new AbortError();
938
- }
939
- let document;
940
- try {
941
- document = await this.documentStorage.get(documentId, consistencyToken, signal);
942
- }
943
- catch {
944
- // Skip documents that can't be retrieved
945
- continue;
946
- }
947
- // Apply view filter
948
- for (const scope in document.state) {
949
- if (!matchesScope(view, scope)) {
950
- delete document.state[scope];
951
- }
952
- }
953
- documents.push(document);
954
- }
955
- if (signal?.aborted) {
956
- throw new AbortError();
957
- }
958
- // Results are already paged from the storage layer
959
- return {
960
- results: documents,
961
- options: paging || { cursor: cursor || "0", limit },
962
- nextCursor,
963
- next: nextCursor
964
- ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, consistencyToken, signal)
965
- : undefined,
966
- };
967
- }
968
- else {
969
- const result = await this.documentView.findByType(type, view, paging, consistencyToken, signal);
970
- if (signal?.aborted) {
971
- throw new AbortError();
972
- }
973
- const cursor = paging?.cursor;
974
- const limit = paging?.limit || 100;
975
- return {
976
- results: result.items,
977
- options: paging || { cursor: cursor || "0", limit },
978
- nextCursor: result.nextCursor,
979
- next: result.nextCursor
980
- ? async () => this.findByType(type, view, { cursor: result.nextCursor, limit }, consistencyToken, signal)
981
- : undefined,
982
- };
983
- }
511
+ return await this.documentView.findByType(type, view, paging, consistencyToken, signal);
512
+ }
513
+ emitJobPending(jobId, meta) {
514
+ const event = {
515
+ jobId,
516
+ jobMeta: meta,
517
+ };
518
+ this.eventBus.emit(ReactorEventTypes.JOB_PENDING, event).catch(() => {
519
+ // Ignore event emission errors
520
+ });
984
521
  }
985
522
  }
986
523
  //# sourceMappingURL=reactor.js.map