@powerhousedao/reactor 5.0.3 → 5.0.4

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 (232) hide show
  1. package/dist/src/cache/index.d.ts +3 -0
  2. package/dist/src/cache/index.d.ts.map +1 -0
  3. package/dist/src/cache/index.js +2 -0
  4. package/dist/src/cache/index.js.map +1 -0
  5. package/dist/src/cache/kysely-operation-index.d.ts +13 -0
  6. package/dist/src/cache/kysely-operation-index.d.ts.map +1 -0
  7. package/dist/src/cache/kysely-operation-index.js +207 -0
  8. package/dist/src/cache/kysely-operation-index.js.map +1 -0
  9. package/dist/src/cache/kysely-write-cache.d.ts +5 -4
  10. package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
  11. package/dist/src/cache/kysely-write-cache.js +12 -12
  12. package/dist/src/cache/kysely-write-cache.js.map +1 -1
  13. package/dist/src/cache/operation-index-types.d.ts +49 -0
  14. package/dist/src/cache/operation-index-types.d.ts.map +1 -0
  15. package/dist/src/cache/operation-index-types.js +4 -0
  16. package/dist/src/cache/operation-index-types.js.map +1 -0
  17. package/dist/src/cache/{types.d.ts → write-cache-types.d.ts} +1 -1
  18. package/dist/src/cache/write-cache-types.d.ts.map +1 -0
  19. package/dist/src/cache/write-cache-types.js +2 -0
  20. package/dist/src/cache/write-cache-types.js.map +1 -0
  21. package/dist/src/client/reactor-client.d.ts +6 -4
  22. package/dist/src/client/reactor-client.d.ts.map +1 -1
  23. package/dist/src/client/reactor-client.js +118 -37
  24. package/dist/src/client/reactor-client.js.map +1 -1
  25. package/dist/src/client/types.d.ts +4 -4
  26. package/dist/src/client/types.d.ts.map +1 -1
  27. package/dist/src/core/builder.d.ts +15 -2
  28. package/dist/src/core/builder.d.ts.map +1 -1
  29. package/dist/src/core/builder.js +48 -7
  30. package/dist/src/core/builder.js.map +1 -1
  31. package/dist/src/core/reactor-builder.d.ts +40 -0
  32. package/dist/src/core/reactor-builder.d.ts.map +1 -0
  33. package/dist/src/core/reactor-builder.js +141 -0
  34. package/dist/src/core/reactor-builder.js.map +1 -0
  35. package/dist/src/core/reactor.d.ts +36 -11
  36. package/dist/src/core/reactor.d.ts.map +1 -1
  37. package/dist/src/core/reactor.js +609 -279
  38. package/dist/src/core/reactor.js.map +1 -1
  39. package/dist/src/core/types.d.ts +84 -7
  40. package/dist/src/core/types.d.ts.map +1 -1
  41. package/dist/src/core/utils.d.ts +44 -4
  42. package/dist/src/core/utils.d.ts.map +1 -1
  43. package/dist/src/core/utils.js +116 -6
  44. package/dist/src/core/utils.js.map +1 -1
  45. package/dist/src/events/types.d.ts +28 -0
  46. package/dist/src/events/types.d.ts.map +1 -1
  47. package/dist/src/events/types.js +2 -0
  48. package/dist/src/events/types.js.map +1 -1
  49. package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
  50. package/dist/src/executor/simple-job-executor-manager.js +19 -1
  51. package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
  52. package/dist/src/executor/simple-job-executor.d.ts +16 -2
  53. package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
  54. package/dist/src/executor/simple-job-executor.js +458 -252
  55. package/dist/src/executor/simple-job-executor.js.map +1 -1
  56. package/dist/src/executor/types.d.ts +2 -0
  57. package/dist/src/executor/types.d.ts.map +1 -1
  58. package/dist/src/executor/types.js.map +1 -1
  59. package/dist/src/executor/util.d.ts +18 -0
  60. package/dist/src/executor/util.d.ts.map +1 -1
  61. package/dist/src/executor/util.js +42 -1
  62. package/dist/src/executor/util.js.map +1 -1
  63. package/dist/src/index.d.ts +12 -7
  64. package/dist/src/index.d.ts.map +1 -1
  65. package/dist/src/index.js +9 -2
  66. package/dist/src/index.js.map +1 -1
  67. package/dist/src/job-tracker/in-memory-job-tracker.d.ts +10 -1
  68. package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
  69. package/dist/src/job-tracker/in-memory-job-tracker.js +57 -23
  70. package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
  71. package/dist/src/job-tracker/interfaces.d.ts +5 -7
  72. package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
  73. package/dist/src/queue/types.d.ts +6 -1
  74. package/dist/src/queue/types.d.ts.map +1 -1
  75. package/dist/src/queue/types.js.map +1 -1
  76. package/dist/src/read-models/coordinator.d.ts.map +1 -1
  77. package/dist/src/read-models/coordinator.js +11 -0
  78. package/dist/src/read-models/coordinator.js.map +1 -1
  79. package/dist/src/read-models/document-view.d.ts +11 -6
  80. package/dist/src/read-models/document-view.d.ts.map +1 -1
  81. package/dist/src/read-models/document-view.js +156 -113
  82. package/dist/src/read-models/document-view.js.map +1 -1
  83. package/dist/src/read-models/types.d.ts +3 -3
  84. package/dist/src/read-models/types.d.ts.map +1 -1
  85. package/dist/src/shared/awaiter.d.ts +11 -8
  86. package/dist/src/shared/awaiter.d.ts.map +1 -1
  87. package/dist/src/shared/awaiter.js +66 -75
  88. package/dist/src/shared/awaiter.js.map +1 -1
  89. package/dist/src/shared/consistency-tracker.d.ts +48 -0
  90. package/dist/src/shared/consistency-tracker.d.ts.map +1 -0
  91. package/dist/src/shared/consistency-tracker.js +123 -0
  92. package/dist/src/shared/consistency-tracker.js.map +1 -0
  93. package/dist/src/shared/types.d.ts +30 -2
  94. package/dist/src/shared/types.d.ts.map +1 -1
  95. package/dist/src/shared/types.js +4 -2
  96. package/dist/src/shared/types.js.map +1 -1
  97. package/dist/src/storage/index.d.ts +4 -0
  98. package/dist/src/storage/index.d.ts.map +1 -0
  99. package/dist/src/storage/index.js +3 -0
  100. package/dist/src/storage/index.js.map +1 -0
  101. package/dist/src/storage/interfaces.d.ts +227 -2
  102. package/dist/src/storage/interfaces.d.ts.map +1 -1
  103. package/dist/src/storage/interfaces.js.map +1 -1
  104. package/dist/src/storage/kysely/document-indexer.d.ts +28 -0
  105. package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -0
  106. package/dist/src/storage/kysely/document-indexer.js +350 -0
  107. package/dist/src/storage/kysely/document-indexer.js.map +1 -0
  108. package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -1
  109. package/dist/src/storage/kysely/keyframe-store.js +6 -13
  110. package/dist/src/storage/kysely/keyframe-store.js.map +1 -1
  111. package/dist/src/storage/kysely/store.js +1 -1
  112. package/dist/src/storage/kysely/store.js.map +1 -1
  113. package/dist/src/storage/kysely/sync-cursor-storage.d.ts +13 -0
  114. package/dist/src/storage/kysely/sync-cursor-storage.d.ts.map +1 -0
  115. package/dist/src/storage/kysely/sync-cursor-storage.js +93 -0
  116. package/dist/src/storage/kysely/sync-cursor-storage.js.map +1 -0
  117. package/dist/src/storage/kysely/sync-remote-storage.d.ts +13 -0
  118. package/dist/src/storage/kysely/sync-remote-storage.d.ts.map +1 -0
  119. package/dist/src/storage/kysely/sync-remote-storage.js +134 -0
  120. package/dist/src/storage/kysely/sync-remote-storage.js.map +1 -0
  121. package/dist/src/storage/kysely/types.d.ts +98 -2
  122. package/dist/src/storage/kysely/types.d.ts.map +1 -1
  123. package/dist/src/storage/migrations/001_create_operation_table.d.ts +3 -0
  124. package/dist/src/storage/migrations/001_create_operation_table.d.ts.map +1 -0
  125. package/dist/src/storage/migrations/001_create_operation_table.js +40 -0
  126. package/dist/src/storage/migrations/001_create_operation_table.js.map +1 -0
  127. package/dist/src/storage/migrations/002_create_keyframe_table.d.ts +3 -0
  128. package/dist/src/storage/migrations/002_create_keyframe_table.d.ts.map +1 -0
  129. package/dist/src/storage/migrations/002_create_keyframe_table.js +27 -0
  130. package/dist/src/storage/migrations/002_create_keyframe_table.js.map +1 -0
  131. package/dist/src/storage/migrations/003_create_document_table.d.ts +3 -0
  132. package/dist/src/storage/migrations/003_create_document_table.d.ts.map +1 -0
  133. package/dist/src/storage/migrations/003_create_document_table.js +10 -0
  134. package/dist/src/storage/migrations/003_create_document_table.js.map +1 -0
  135. package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts +3 -0
  136. package/dist/src/storage/migrations/004_create_document_relationship_table.d.ts.map +1 -0
  137. package/dist/src/storage/migrations/004_create_document_relationship_table.js +35 -0
  138. package/dist/src/storage/migrations/004_create_document_relationship_table.js.map +1 -0
  139. package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts +3 -0
  140. package/dist/src/storage/migrations/005_create_indexer_state_table.d.ts.map +1 -0
  141. package/dist/src/storage/migrations/005_create_indexer_state_table.js +10 -0
  142. package/dist/src/storage/migrations/005_create_indexer_state_table.js.map +1 -0
  143. package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts +3 -0
  144. package/dist/src/storage/migrations/006_create_document_snapshot_table.d.ts.map +1 -0
  145. package/dist/src/storage/migrations/006_create_document_snapshot_table.js +49 -0
  146. package/dist/src/storage/migrations/006_create_document_snapshot_table.js.map +1 -0
  147. package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts +3 -0
  148. package/dist/src/storage/migrations/007_create_slug_mapping_table.d.ts.map +1 -0
  149. package/dist/src/storage/migrations/007_create_slug_mapping_table.js +24 -0
  150. package/dist/src/storage/migrations/007_create_slug_mapping_table.js.map +1 -0
  151. package/dist/src/storage/migrations/008_create_view_state_table.d.ts +3 -0
  152. package/dist/src/storage/migrations/008_create_view_state_table.d.ts.map +1 -0
  153. package/dist/src/storage/migrations/008_create_view_state_table.js +9 -0
  154. package/dist/src/storage/migrations/008_create_view_state_table.js.map +1 -0
  155. package/dist/src/storage/migrations/009_create_operation_index_tables.d.ts +3 -0
  156. package/dist/src/storage/migrations/009_create_operation_index_tables.d.ts.map +1 -0
  157. package/dist/src/storage/migrations/009_create_operation_index_tables.js +50 -0
  158. package/dist/src/storage/migrations/009_create_operation_index_tables.js.map +1 -0
  159. package/dist/src/storage/migrations/010_create_sync_tables.d.ts +3 -0
  160. package/dist/src/storage/migrations/010_create_sync_tables.d.ts.map +1 -0
  161. package/dist/src/storage/migrations/010_create_sync_tables.js +43 -0
  162. package/dist/src/storage/migrations/010_create_sync_tables.js.map +1 -0
  163. package/dist/src/storage/migrations/index.d.ts +3 -0
  164. package/dist/src/storage/migrations/index.d.ts.map +1 -0
  165. package/dist/src/storage/migrations/index.js +3 -0
  166. package/dist/src/storage/migrations/index.js.map +1 -0
  167. package/dist/src/storage/migrations/migrator.d.ts +5 -0
  168. package/dist/src/storage/migrations/migrator.d.ts.map +1 -0
  169. package/dist/src/storage/migrations/migrator.js +55 -0
  170. package/dist/src/storage/migrations/migrator.js.map +1 -0
  171. package/dist/src/storage/migrations/run-migrations.d.ts +2 -0
  172. package/dist/src/storage/migrations/run-migrations.d.ts.map +1 -0
  173. package/dist/src/storage/migrations/run-migrations.js +58 -0
  174. package/dist/src/storage/migrations/run-migrations.js.map +1 -0
  175. package/dist/src/storage/migrations/types.d.ts +9 -0
  176. package/dist/src/storage/migrations/types.d.ts.map +1 -0
  177. package/dist/src/storage/migrations/types.js.map +1 -0
  178. package/dist/src/storage/txn.d.ts.map +1 -1
  179. package/dist/src/storage/txn.js +2 -0
  180. package/dist/src/storage/txn.js.map +1 -1
  181. package/dist/src/sync/channels/index.d.ts +3 -0
  182. package/dist/src/sync/channels/index.d.ts.map +1 -0
  183. package/dist/src/sync/channels/index.js +3 -0
  184. package/dist/src/sync/channels/index.js.map +1 -0
  185. package/dist/src/sync/channels/internal-channel.d.ts +57 -0
  186. package/dist/src/sync/channels/internal-channel.d.ts.map +1 -0
  187. package/dist/src/sync/channels/internal-channel.js +106 -0
  188. package/dist/src/sync/channels/internal-channel.js.map +1 -0
  189. package/dist/src/sync/channels/utils.d.ts +15 -0
  190. package/dist/src/sync/channels/utils.d.ts.map +1 -0
  191. package/dist/src/sync/channels/utils.js +26 -0
  192. package/dist/src/sync/channels/utils.js.map +1 -0
  193. package/dist/src/sync/errors.d.ts +10 -0
  194. package/dist/src/sync/errors.d.ts.map +1 -0
  195. package/dist/src/sync/errors.js +17 -0
  196. package/dist/src/sync/errors.js.map +1 -0
  197. package/dist/src/sync/index.d.ts +12 -0
  198. package/dist/src/sync/index.d.ts.map +1 -0
  199. package/dist/src/sync/index.js +9 -0
  200. package/dist/src/sync/index.js.map +1 -0
  201. package/dist/src/sync/interfaces.d.ts +150 -0
  202. package/dist/src/sync/interfaces.d.ts.map +1 -0
  203. package/dist/src/sync/interfaces.js +2 -0
  204. package/dist/src/sync/interfaces.js.map +1 -0
  205. package/dist/src/sync/mailbox.d.ts +21 -0
  206. package/dist/src/sync/mailbox.d.ts.map +1 -0
  207. package/dist/src/sync/mailbox.js +59 -0
  208. package/dist/src/sync/mailbox.js.map +1 -0
  209. package/dist/src/sync/sync-builder.d.ts +17 -0
  210. package/dist/src/sync/sync-builder.d.ts.map +1 -0
  211. package/dist/src/sync/sync-builder.js +29 -0
  212. package/dist/src/sync/sync-builder.js.map +1 -0
  213. package/dist/src/sync/sync-manager.d.ts +33 -0
  214. package/dist/src/sync/sync-manager.d.ts.map +1 -0
  215. package/dist/src/sync/sync-manager.js +197 -0
  216. package/dist/src/sync/sync-manager.js.map +1 -0
  217. package/dist/src/sync/sync-operation.d.ts +28 -0
  218. package/dist/src/sync/sync-operation.d.ts.map +1 -0
  219. package/dist/src/sync/sync-operation.js +63 -0
  220. package/dist/src/sync/sync-operation.js.map +1 -0
  221. package/dist/src/sync/types.d.ts +61 -0
  222. package/dist/src/sync/types.d.ts.map +1 -0
  223. package/dist/src/sync/types.js +16 -0
  224. package/dist/src/sync/types.js.map +1 -0
  225. package/dist/src/sync/utils.d.ts +17 -0
  226. package/dist/src/sync/utils.d.ts.map +1 -0
  227. package/dist/src/sync/utils.js +34 -0
  228. package/dist/src/sync/utils.js.map +1 -0
  229. package/package.json +9 -5
  230. package/dist/src/cache/types.d.ts.map +0 -1
  231. package/dist/src/cache/types.js.map +0 -1
  232. /package/dist/src/{cache → storage/migrations}/types.js +0 -0
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
3
3
  import { createMutableShutdownStatus } from "../shared/factories.js";
4
4
  import { JobStatus } from "../shared/types.js";
5
5
  import { matchesScope } from "../shared/utils.js";
6
- import { filterByParentId, filterByType } from "./utils.js";
6
+ import { filterByType, getSharedScope, toErrorInfo, topologicalSort, validateActionScopes, validateBatchRequest, } from "./utils.js";
7
7
  /**
8
8
  * This class implements the IReactor interface and serves as the main entry point
9
9
  * for the new Reactor architecture.
@@ -16,19 +16,33 @@ export class Reactor {
16
16
  queue;
17
17
  jobTracker;
18
18
  readModelCoordinator;
19
- constructor(driveServer, documentStorage, queue, jobTracker, readModelCoordinator) {
19
+ features;
20
+ documentView;
21
+ _documentIndexer;
22
+ operationStore;
23
+ _syncManager;
24
+ constructor(driveServer, documentStorage, queue, jobTracker, readModelCoordinator, features, documentView, documentIndexer, operationStore) {
20
25
  // Store required dependencies
21
26
  this.driveServer = driveServer;
22
27
  this.documentStorage = documentStorage;
23
28
  this.queue = queue;
24
29
  this.jobTracker = jobTracker;
25
30
  this.readModelCoordinator = readModelCoordinator;
26
- // Start the read model coordinator
27
- this.readModelCoordinator.start();
31
+ this.features = features;
32
+ this.documentView = documentView;
33
+ this._documentIndexer = documentIndexer;
34
+ this.operationStore = operationStore;
28
35
  // Create mutable shutdown status using factory method
29
36
  const [status, setter] = createMutableShutdownStatus(false);
30
37
  this.shutdownStatus = status;
31
38
  this.setShutdown = setter;
39
+ this.readModelCoordinator.start();
40
+ }
41
+ get syncManager() {
42
+ return this._syncManager;
43
+ }
44
+ setSync(syncManager) {
45
+ this._syncManager = syncManager;
32
46
  }
33
47
  /**
34
48
  * Signals that the reactor should shutdown.
@@ -36,10 +50,14 @@ export class Reactor {
36
50
  kill() {
37
51
  // Mark the reactor as shutdown
38
52
  this.setShutdown(true);
53
+ // Stop the sync manager if enabled
54
+ if (this._syncManager) {
55
+ this._syncManager.shutdown();
56
+ }
39
57
  // Stop the read model coordinator
40
58
  this.readModelCoordinator.stop();
41
- // TODO: Phase 3+ - Implement graceful shutdown for queue, executors, etc.
42
- // For now, we just mark as shutdown and return status
59
+ // Stop the job tracker
60
+ this.jobTracker.shutdown();
43
61
  return this.shutdownStatus;
44
62
  }
45
63
  /**
@@ -72,99 +90,196 @@ export class Reactor {
72
90
  /**
73
91
  * Retrieves a specific PHDocument by id
74
92
  */
75
- async get(id, view, signal) {
76
- const document = await this.documentStorage.get(id);
77
- if (signal?.aborted) {
78
- throw new AbortError();
79
- }
80
- const childIds = await this.documentStorage.getChildren(id);
81
- if (signal?.aborted) {
82
- throw new AbortError();
93
+ async get(id, view, consistencyToken, signal) {
94
+ if (this.features.legacyStorageEnabled) {
95
+ const document = await this.documentStorage.get(id);
96
+ if (signal?.aborted) {
97
+ throw new AbortError();
98
+ }
99
+ const childIds = await this.documentStorage.getChildren(id);
100
+ if (signal?.aborted) {
101
+ throw new AbortError();
102
+ }
103
+ for (const scope in document.state) {
104
+ if (!matchesScope(view, scope)) {
105
+ delete document.state[scope];
106
+ }
107
+ }
108
+ return {
109
+ document,
110
+ childIds,
111
+ };
83
112
  }
84
- // Apply view filter - This will be removed when we pass the viewfilter along
85
- // to the underlying store, but is here now for the interface.
86
- for (const scope in document.state) {
87
- if (!matchesScope(view, scope)) {
88
- delete document.state[scope];
113
+ else {
114
+ const document = await this.documentView.get(id, view, consistencyToken, signal);
115
+ if (signal?.aborted) {
116
+ throw new AbortError();
89
117
  }
118
+ const relationships = await this._documentIndexer.getOutgoing(id, ["child"], consistencyToken, signal);
119
+ if (signal?.aborted) {
120
+ throw new AbortError();
121
+ }
122
+ const childIds = relationships.map((rel) => rel.targetId);
123
+ return {
124
+ document,
125
+ childIds,
126
+ };
90
127
  }
91
- return {
92
- document,
93
- childIds,
94
- };
95
128
  }
96
129
  /**
97
130
  * Retrieves a specific PHDocument by slug
98
131
  */
99
- async getBySlug(slug, view, signal) {
100
- // Use the storage layer to resolve slug to ID
101
- let ids;
102
- try {
103
- ids = await this.documentStorage.resolveIds([slug], signal);
132
+ async getBySlug(slug, view, consistencyToken, signal) {
133
+ if (this.features.legacyStorageEnabled) {
134
+ let ids;
135
+ try {
136
+ ids = await this.documentStorage.resolveIds([slug], signal);
137
+ }
138
+ catch (error) {
139
+ if (error instanceof Error && error.message.includes("not found")) {
140
+ throw new Error(`Document not found with slug: ${slug}`);
141
+ }
142
+ throw error;
143
+ }
144
+ if (ids.length === 0 || !ids[0]) {
145
+ throw new Error(`Document not found with slug: ${slug}`);
146
+ }
147
+ return await this.get(ids[0], view, consistencyToken, signal);
104
148
  }
105
- catch (error) {
106
- // If the error is from resolveIds (document not found), wrap it with our message
107
- if (error instanceof Error && error.message.includes("not found")) {
149
+ else {
150
+ const documentId = await this.documentView.resolveSlug(slug, view, consistencyToken, signal);
151
+ if (!documentId) {
108
152
  throw new Error(`Document not found with slug: ${slug}`);
109
153
  }
110
- throw error;
154
+ return await this.get(documentId, view, consistencyToken, signal);
111
155
  }
112
- if (ids.length === 0 || !ids[0]) {
113
- throw new Error(`Document not found with slug: ${slug}`);
156
+ }
157
+ /**
158
+ * Retrieves a specific PHDocument by identifier (either id or slug)
159
+ */
160
+ async getByIdOrSlug(identifier, view, consistencyToken, signal) {
161
+ if (this.features.legacyStorageEnabled) {
162
+ try {
163
+ return await this.get(identifier, view, consistencyToken, signal);
164
+ }
165
+ catch {
166
+ try {
167
+ const ids = await this.documentStorage.resolveIds([identifier], signal);
168
+ if (ids.length === 0 || !ids[0]) {
169
+ throw new Error(`Document not found: ${identifier}`);
170
+ }
171
+ return await this.get(ids[0], view, consistencyToken, signal);
172
+ }
173
+ catch {
174
+ throw new Error(`Document not found: ${identifier}`);
175
+ }
176
+ }
177
+ }
178
+ else {
179
+ const document = await this.documentView.getByIdOrSlug(identifier, view, consistencyToken, signal);
180
+ if (signal?.aborted) {
181
+ throw new AbortError();
182
+ }
183
+ const relationships = await this._documentIndexer.getOutgoing(document.header.id, ["child"], consistencyToken, signal);
184
+ if (signal?.aborted) {
185
+ throw new AbortError();
186
+ }
187
+ const childIds = relationships.map((rel) => rel.targetId);
188
+ return {
189
+ document,
190
+ childIds,
191
+ };
114
192
  }
115
- // Now get the document by its resolved ID
116
- return await this.get(ids[0], view, signal);
117
193
  }
118
194
  /**
119
195
  * Retrieves the operations for a document
120
196
  */
121
- async getOperations(documentId, view, paging, signal) {
122
- // Use storage directly to get the document
123
- const document = await this.documentStorage.get(documentId);
124
- if (signal?.aborted) {
125
- throw new AbortError();
197
+ async getOperations(documentId, view, paging, consistencyToken, signal) {
198
+ if (this.features.legacyStorageEnabled) {
199
+ // Use storage directly to get the document
200
+ const document = await this.documentStorage.get(documentId);
201
+ if (signal?.aborted) {
202
+ throw new AbortError();
203
+ }
204
+ const operations = document.operations;
205
+ const result = {};
206
+ // apply view filter, per scope -- this will be removed when we pass the viewfilter along
207
+ // to the underlying store, but is here now for the interface.
208
+ for (const scope in operations) {
209
+ if (matchesScope(view, scope)) {
210
+ const scopeOperations = operations[scope];
211
+ // apply paging too
212
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
213
+ const limit = paging?.limit || scopeOperations.length;
214
+ const pagedOperations = scopeOperations.slice(startIndex, startIndex + limit);
215
+ result[scope] = {
216
+ results: pagedOperations,
217
+ options: { cursor: String(startIndex + limit), limit },
218
+ };
219
+ }
220
+ }
221
+ return Promise.resolve(result);
126
222
  }
127
- const operations = document.operations;
128
- const result = {};
129
- // apply view filter, per scope -- this will be removed when we pass the viewfilter along
130
- // to the underlying store, but is here now for the interface.
131
- for (const scope in operations) {
132
- if (matchesScope(view, scope)) {
133
- const scopeOperations = operations[scope];
134
- // apply paging too
135
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
136
- const limit = paging?.limit || scopeOperations.length;
137
- const pagedOperations = scopeOperations.slice(startIndex, startIndex + limit);
223
+ else {
224
+ // Use operation store to get operations
225
+ const branch = view?.branch || "main";
226
+ // Get all scopes for this document
227
+ const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
228
+ if (signal?.aborted) {
229
+ throw new AbortError();
230
+ }
231
+ const allScopes = Object.keys(revisions.revision);
232
+ const result = {};
233
+ // Filter scopes based on view filter and query operations for each
234
+ for (const scope of allScopes) {
235
+ if (!matchesScope(view, scope)) {
236
+ continue;
237
+ }
238
+ if (signal?.aborted) {
239
+ throw new AbortError();
240
+ }
241
+ // Get operations for this scope
242
+ const scopeResult = await this.operationStore.getSince(documentId, scope, branch, -1, paging, signal);
243
+ // Transform to Reactor's PagedResults format
244
+ const currentCursor = paging?.cursor ? parseInt(paging.cursor) : 0;
245
+ const currentLimit = paging?.limit || 100;
138
246
  result[scope] = {
139
- results: pagedOperations,
140
- options: { cursor: String(startIndex + limit), limit },
247
+ results: scopeResult.items,
248
+ options: {
249
+ cursor: scopeResult.nextCursor || String(currentCursor),
250
+ limit: currentLimit,
251
+ },
252
+ nextCursor: scopeResult.nextCursor,
253
+ next: scopeResult.hasMore
254
+ ? async () => {
255
+ const nextPage = await this.getOperations(documentId, view, {
256
+ cursor: scopeResult.nextCursor,
257
+ limit: currentLimit,
258
+ }, consistencyToken, signal);
259
+ return nextPage[scope];
260
+ }
261
+ : undefined,
141
262
  };
142
263
  }
264
+ return result;
143
265
  }
144
- return Promise.resolve(result);
145
266
  }
146
267
  /**
147
268
  * Filters documents by criteria and returns a list of them
148
269
  */
149
- async find(search, view, paging, signal) {
270
+ async find(search, view, paging, consistencyToken, signal) {
150
271
  let results;
151
272
  if (search.ids) {
152
273
  if (search.slugs && search.slugs.length > 0) {
153
274
  throw new Error("Cannot use both ids and slugs in the same search");
154
275
  }
155
- results = await this.findByIds(search.ids, view, paging, signal);
156
- if (search.parentId) {
157
- results = filterByParentId(results, search.parentId);
158
- }
276
+ results = await this.findByIds(search.ids, view, paging, consistencyToken, signal);
159
277
  if (search.type) {
160
278
  results = filterByType(results, search.type);
161
279
  }
162
280
  }
163
281
  else if (search.slugs) {
164
- results = await this.findBySlugs(search.slugs, view, paging, signal);
165
- if (search.parentId) {
166
- results = filterByParentId(results, search.parentId);
167
- }
282
+ results = await this.findBySlugs(search.slugs, view, paging, consistencyToken, signal);
168
283
  if (search.type) {
169
284
  results = filterByType(results, search.type);
170
285
  }
@@ -176,7 +291,7 @@ export class Reactor {
176
291
  }
177
292
  }
178
293
  else if (search.type) {
179
- results = await this.findByType(search.type, view, paging, signal);
294
+ results = await this.findByType(search.type, view, paging, consistencyToken, signal);
180
295
  }
181
296
  else {
182
297
  throw new Error("No search criteria provided");
@@ -238,10 +353,12 @@ export class Reactor {
238
353
  // Create a single job with both CREATE_DOCUMENT and UPGRADE_DOCUMENT actions
239
354
  const job = {
240
355
  id: uuidv4(),
356
+ kind: "mutation",
241
357
  documentId: document.header.id,
242
358
  scope: "document",
243
359
  branch: "main",
244
360
  actions: [createAction, upgradeAction],
361
+ operations: [],
245
362
  createdAt: new Date().toISOString(),
246
363
  queueHint: [],
247
364
  maxRetries: 3,
@@ -252,6 +369,11 @@ export class Reactor {
252
369
  id: job.id,
253
370
  status: JobStatus.PENDING,
254
371
  createdAtUtcIso,
372
+ consistencyToken: {
373
+ version: 1,
374
+ createdAtUtcIso,
375
+ coordinates: [],
376
+ },
255
377
  };
256
378
  this.jobTracker.registerJob(jobInfo);
257
379
  // Enqueue the job
@@ -279,10 +401,12 @@ export class Reactor {
279
401
  };
280
402
  const job = {
281
403
  id: uuidv4(),
404
+ kind: "mutation",
282
405
  documentId: id,
283
406
  scope: "document",
284
407
  branch: "main",
285
408
  actions: [action],
409
+ operations: [],
286
410
  createdAt: new Date().toISOString(),
287
411
  queueHint: [],
288
412
  maxRetries: 3,
@@ -292,6 +416,11 @@ export class Reactor {
292
416
  id: job.id,
293
417
  status: JobStatus.PENDING,
294
418
  createdAtUtcIso,
419
+ consistencyToken: {
420
+ version: 1,
421
+ createdAtUtcIso,
422
+ coordinates: [],
423
+ },
295
424
  };
296
425
  this.jobTracker.registerJob(jobInfo);
297
426
  await this.queue.enqueue(job);
@@ -300,17 +429,22 @@ export class Reactor {
300
429
  /**
301
430
  * Applies a list of actions to a document
302
431
  */
303
- async mutate(id, actions) {
432
+ async mutate(docId, branch, actions, signal) {
433
+ if (signal?.aborted) {
434
+ throw new AbortError();
435
+ }
304
436
  const createdAtUtcIso = new Date().toISOString();
305
437
  // Determine scope from first action (all actions should have the same scope)
306
438
  const scope = actions.length > 0 ? actions[0].scope || "global" : "global";
307
439
  // Create a single job with all actions
308
440
  const job = {
309
441
  id: uuidv4(),
310
- documentId: id,
442
+ kind: "mutation",
443
+ documentId: docId,
311
444
  scope: scope,
312
- branch: "main", // Default to main branch
445
+ branch: branch,
313
446
  actions: actions,
447
+ operations: [],
314
448
  createdAt: new Date().toISOString(),
315
449
  queueHint: [],
316
450
  maxRetries: 3,
@@ -321,103 +455,190 @@ export class Reactor {
321
455
  id: job.id,
322
456
  status: JobStatus.PENDING,
323
457
  createdAtUtcIso,
458
+ consistencyToken: {
459
+ version: 1,
460
+ createdAtUtcIso,
461
+ coordinates: [],
462
+ },
324
463
  };
325
464
  this.jobTracker.registerJob(jobInfo);
326
465
  // Enqueue the job
327
466
  await this.queue.enqueue(job);
467
+ if (signal?.aborted) {
468
+ throw new AbortError();
469
+ }
328
470
  return jobInfo;
329
471
  }
330
472
  /**
331
- * Adds multiple documents as children to another
473
+ * Imports pre-existing operations that were produced by another reactor.
474
+ * This function may cause a reshuffle, which will generate additional
475
+ * operations.
332
476
  */
333
- async addChildren(parentId, documentIds, view, signal) {
334
- const createdAtUtcIso = new Date().toISOString();
335
- const jobId = uuidv4();
336
- // Check abort signal before starting
477
+ async load(docId, branch, operations, signal) {
337
478
  if (signal?.aborted) {
338
479
  throw new AbortError();
339
480
  }
340
- // TODO: Implement when drive server supports hierarchical documents
341
- // For now, this is a placeholder implementation
342
- // Verify parent exists
343
- try {
344
- await this.driveServer.getDocument(parentId);
481
+ if (operations.length === 0) {
482
+ throw new Error("load requires at least one operation");
345
483
  }
346
- catch (error) {
347
- return {
348
- id: jobId,
349
- status: JobStatus.FAILED,
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
+ });
491
+ const createdAtUtcIso = new Date().toISOString();
492
+ const job = {
493
+ id: uuidv4(),
494
+ kind: "load",
495
+ documentId: docId,
496
+ scope,
497
+ branch,
498
+ actions: [],
499
+ operations,
500
+ createdAt: createdAtUtcIso,
501
+ queueHint: [],
502
+ maxRetries: 3,
503
+ errorHistory: [],
504
+ };
505
+ const jobInfo = {
506
+ id: job.id,
507
+ status: JobStatus.PENDING,
508
+ createdAtUtcIso,
509
+ consistencyToken: {
510
+ version: 1,
350
511
  createdAtUtcIso,
351
- error: this.toErrorInfo(error instanceof Error ? error : "Unknown error"),
352
- };
512
+ coordinates: [],
513
+ },
514
+ };
515
+ this.jobTracker.registerJob(jobInfo);
516
+ await this.queue.enqueue(job);
517
+ if (signal?.aborted) {
518
+ throw new AbortError();
353
519
  }
354
- // Check abort signal after parent verification
520
+ return jobInfo;
521
+ }
522
+ /**
523
+ * Applies multiple mutations across documents with dependency management
524
+ */
525
+ async mutateBatch(request, signal) {
355
526
  if (signal?.aborted) {
356
527
  throw new AbortError();
357
528
  }
358
- // Verify all children exist
359
- for (const childId of documentIds) {
360
- try {
361
- await this.driveServer.getDocument(childId);
362
- }
363
- catch (error) {
364
- return {
365
- id: jobId,
366
- status: JobStatus.FAILED,
529
+ validateBatchRequest(request.jobs);
530
+ for (const jobPlan of request.jobs) {
531
+ validateActionScopes(jobPlan);
532
+ }
533
+ const createdAtUtcIso = new Date().toISOString();
534
+ const planKeyToJobId = new Map();
535
+ for (const jobPlan of request.jobs) {
536
+ planKeyToJobId.set(jobPlan.key, uuidv4());
537
+ }
538
+ const jobInfos = new Map();
539
+ for (const jobPlan of request.jobs) {
540
+ const jobId = planKeyToJobId.get(jobPlan.key);
541
+ const jobInfo = {
542
+ id: jobId,
543
+ status: JobStatus.PENDING,
544
+ createdAtUtcIso,
545
+ consistencyToken: {
546
+ version: 1,
367
547
  createdAtUtcIso,
368
- error: this.toErrorInfo(error instanceof Error ? error : "Unknown error"),
548
+ coordinates: [],
549
+ },
550
+ };
551
+ this.jobTracker.registerJob(jobInfo);
552
+ jobInfos.set(jobPlan.key, jobInfo);
553
+ }
554
+ const sortedKeys = topologicalSort(request.jobs);
555
+ const enqueuedKeys = [];
556
+ try {
557
+ for (const key of sortedKeys) {
558
+ if (signal?.aborted) {
559
+ throw new AbortError();
560
+ }
561
+ const jobPlan = request.jobs.find((j) => j.key === key);
562
+ const jobId = planKeyToJobId.get(key);
563
+ const queueHint = jobPlan.dependsOn.map((depKey) => planKeyToJobId.get(depKey));
564
+ const job = {
565
+ id: jobId,
566
+ kind: "mutation",
567
+ documentId: jobPlan.documentId,
568
+ scope: jobPlan.scope,
569
+ branch: jobPlan.branch,
570
+ actions: jobPlan.actions,
571
+ operations: [],
572
+ createdAt: createdAtUtcIso,
573
+ queueHint,
574
+ maxRetries: 3,
575
+ errorHistory: [],
369
576
  };
577
+ await this.queue.enqueue(job);
578
+ enqueuedKeys.push(key);
370
579
  }
371
- // Check abort signal after each child verification
372
- if (signal?.aborted) {
373
- throw new AbortError();
580
+ }
581
+ catch (error) {
582
+ for (const key of enqueuedKeys) {
583
+ const jobId = planKeyToJobId.get(key);
584
+ try {
585
+ await this.queue.remove(jobId);
586
+ }
587
+ catch {
588
+ // Ignore removal errors during cleanup
589
+ }
590
+ }
591
+ for (const jobInfo of jobInfos.values()) {
592
+ this.jobTracker.markFailed(jobInfo.id, toErrorInfo("Batch enqueue failed"));
374
593
  }
594
+ throw error;
375
595
  }
376
- // TODO: Actually establish parent-child relationships
377
- // Return success job info
378
- return {
379
- id: jobId,
380
- status: JobStatus.COMPLETED,
381
- createdAtUtcIso,
382
- completedAtUtcIso: new Date().toISOString(),
596
+ const result = {
597
+ jobs: Object.fromEntries(jobInfos),
383
598
  };
599
+ return result;
384
600
  }
385
601
  /**
386
- * Removes multiple documents as children from another
602
+ * Adds multiple documents as children to another
387
603
  */
388
- async removeChildren(parentId, documentIds, view, signal) {
389
- const createdAtUtcIso = new Date().toISOString();
390
- const jobId = uuidv4();
391
- // Check abort signal before starting
604
+ async addChildren(parentId, documentIds, _view, signal) {
392
605
  if (signal?.aborted) {
393
606
  throw new AbortError();
394
607
  }
395
- // TODO: Implement when drive server supports hierarchical documents
396
- // For now, this is a placeholder implementation
397
- // Verify parent exists
398
- try {
399
- await this.driveServer.getDocument(parentId);
400
- }
401
- catch (error) {
402
- return {
403
- id: jobId,
404
- status: JobStatus.FAILED,
405
- createdAtUtcIso,
406
- error: this.toErrorInfo(error instanceof Error ? error : "Unknown error"),
407
- };
408
- }
409
- // Check abort signal after parent verification
608
+ const actions = documentIds.map((childId) => ({
609
+ id: uuidv4(),
610
+ type: "ADD_RELATIONSHIP",
611
+ scope: "document",
612
+ timestampUtcMs: new Date().toISOString(),
613
+ input: {
614
+ sourceId: parentId,
615
+ targetId: childId,
616
+ relationshipType: "child",
617
+ },
618
+ }));
619
+ const branch = _view?.branch || "main";
620
+ return await this.mutate(parentId, branch, actions, signal);
621
+ }
622
+ /**
623
+ * Removes multiple documents as children from another
624
+ */
625
+ async removeChildren(parentId, documentIds, _view, signal) {
410
626
  if (signal?.aborted) {
411
627
  throw new AbortError();
412
628
  }
413
- // TODO: Actually remove parent-child relationships
414
- // Return success job info
415
- return {
416
- id: jobId,
417
- status: JobStatus.COMPLETED,
418
- createdAtUtcIso,
419
- completedAtUtcIso: new Date().toISOString(),
420
- };
629
+ const actions = documentIds.map((childId) => ({
630
+ id: uuidv4(),
631
+ type: "REMOVE_RELATIONSHIP",
632
+ scope: "document",
633
+ timestampUtcMs: new Date().toISOString(),
634
+ input: {
635
+ sourceId: parentId,
636
+ targetId: childId,
637
+ relationshipType: "child",
638
+ },
639
+ }));
640
+ const branch = _view?.branch || "main";
641
+ return await this.mutate(parentId, branch, actions, signal);
421
642
  }
422
643
  /**
423
644
  * Retrieves the status of a job
@@ -429,12 +650,18 @@ export class Reactor {
429
650
  const jobInfo = this.jobTracker.getJobStatus(jobId);
430
651
  if (!jobInfo) {
431
652
  // Job not found - return FAILED status with appropriate error
653
+ const now = new Date().toISOString();
432
654
  return Promise.resolve({
433
655
  id: jobId,
434
656
  status: JobStatus.FAILED,
435
- createdAtUtcIso: new Date().toISOString(),
436
- completedAtUtcIso: new Date().toISOString(),
437
- error: this.toErrorInfo("Job not found"),
657
+ createdAtUtcIso: now,
658
+ completedAtUtcIso: now,
659
+ error: toErrorInfo("Job not found"),
660
+ consistencyToken: {
661
+ version: 1,
662
+ createdAtUtcIso: now,
663
+ coordinates: [],
664
+ },
438
665
  });
439
666
  }
440
667
  return Promise.resolve(jobInfo);
@@ -442,139 +669,233 @@ export class Reactor {
442
669
  /**
443
670
  * Finds documents by their IDs
444
671
  */
445
- async findByIds(ids, view, paging, signal) {
446
- const documents = [];
447
- // Fetch each document by ID using storage directly
448
- for (const id of ids) {
672
+ async findByIds(ids, view, paging, consistencyToken, signal) {
673
+ if (consistencyToken) {
674
+ await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
675
+ }
676
+ if (this.features.legacyStorageEnabled) {
677
+ const documents = [];
678
+ // Fetch each document by ID using storage directly
679
+ for (const id of ids) {
680
+ if (signal?.aborted) {
681
+ throw new AbortError();
682
+ }
683
+ let document;
684
+ try {
685
+ document = await this.documentStorage.get(id);
686
+ }
687
+ catch {
688
+ // Skip documents that don't exist or can't be accessed
689
+ // This matches the behavior expected from a search operation
690
+ continue;
691
+ }
692
+ // Apply view filter - This will be removed when we pass the viewfilter along
693
+ // to the underlying store, but is here now for the interface.
694
+ for (const scope in document.state) {
695
+ if (!matchesScope(view, scope)) {
696
+ delete document.state[scope];
697
+ }
698
+ }
699
+ documents.push(document);
700
+ }
449
701
  if (signal?.aborted) {
450
702
  throw new AbortError();
451
703
  }
452
- let document;
453
- try {
454
- document = await this.documentStorage.get(id);
455
- }
456
- catch {
457
- // Skip documents that don't exist or can't be accessed
458
- // This matches the behavior expected from a search operation
459
- continue;
460
- }
461
- // Apply view filter - This will be removed when we pass the viewfilter along
462
- // to the underlying store, but is here now for the interface.
463
- for (const scope in document.state) {
464
- if (!matchesScope(view, scope)) {
465
- delete document.state[scope];
704
+ // Apply paging
705
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
706
+ const limit = paging?.limit || documents.length;
707
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
708
+ // Create paged results
709
+ const hasMore = startIndex + limit < documents.length;
710
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
711
+ return {
712
+ results: pagedDocuments,
713
+ options: paging || { cursor: "0", limit: documents.length },
714
+ nextCursor,
715
+ next: hasMore
716
+ ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, undefined, signal)
717
+ : undefined,
718
+ };
719
+ }
720
+ else {
721
+ const documents = [];
722
+ // Fetch each document by ID using documentView
723
+ for (const id of ids) {
724
+ if (signal?.aborted) {
725
+ throw new AbortError();
726
+ }
727
+ try {
728
+ const document = await this.documentView.get(id, view, undefined, signal);
729
+ documents.push(document);
730
+ }
731
+ catch {
732
+ // Skip documents that don't exist or can't be accessed
733
+ continue;
466
734
  }
467
735
  }
468
- documents.push(document);
469
- }
470
- if (signal?.aborted) {
471
- throw new AbortError();
736
+ if (signal?.aborted) {
737
+ throw new AbortError();
738
+ }
739
+ // Apply paging
740
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
741
+ const limit = paging?.limit || documents.length;
742
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
743
+ // Create paged results
744
+ const hasMore = startIndex + limit < documents.length;
745
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
746
+ return {
747
+ results: pagedDocuments,
748
+ options: paging || { cursor: "0", limit: documents.length },
749
+ nextCursor,
750
+ next: hasMore
751
+ ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, undefined, signal)
752
+ : undefined,
753
+ };
472
754
  }
473
- // Apply paging
474
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
475
- const limit = paging?.limit || documents.length;
476
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
477
- // Create paged results
478
- const hasMore = startIndex + limit < documents.length;
479
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
480
- return {
481
- results: pagedDocuments,
482
- options: paging || { cursor: "0", limit: documents.length },
483
- nextCursor,
484
- next: hasMore
485
- ? () => this.findByIds(ids, view, { cursor: nextCursor, limit }, signal)
486
- : undefined,
487
- };
488
755
  }
489
756
  /**
490
757
  * Finds documents by their slugs
491
758
  */
492
- async findBySlugs(slugs, view, paging, signal) {
493
- const documents = [];
494
- // Use storage to resolve slugs to IDs
495
- let ids;
496
- try {
497
- ids = await this.documentStorage.resolveIds(slugs, signal);
498
- }
499
- catch {
500
- // If slug resolution fails, return empty results
501
- // This matches the behavior expected from a search operation
502
- ids = [];
503
- }
504
- // Fetch each document by resolved ID
505
- for (const id of ids) {
506
- if (signal?.aborted) {
507
- throw new AbortError();
508
- }
509
- let document;
759
+ async findBySlugs(slugs, view, paging, consistencyToken, signal) {
760
+ if (consistencyToken) {
761
+ await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
762
+ }
763
+ if (this.features.legacyStorageEnabled) {
764
+ const documents = [];
765
+ // Use storage to resolve slugs to IDs
766
+ let ids;
510
767
  try {
511
- document = await this.documentStorage.get(id);
768
+ ids = await this.documentStorage.resolveIds(slugs, signal);
512
769
  }
513
770
  catch {
514
- // Skip documents that don't exist or can't be accessed
515
- continue;
771
+ // If slug resolution fails, return empty results
772
+ // This matches the behavior expected from a search operation
773
+ ids = [];
516
774
  }
517
- // Apply view filter - This will be removed when we pass the viewfilter along
518
- // to the underlying store, but is here now for the interface.
519
- for (const scope in document.state) {
520
- if (!matchesScope(view, scope)) {
521
- delete document.state[scope];
775
+ // Fetch each document by resolved ID
776
+ for (const id of ids) {
777
+ if (signal?.aborted) {
778
+ throw new AbortError();
522
779
  }
780
+ let document;
781
+ try {
782
+ document = await this.documentStorage.get(id);
783
+ }
784
+ catch {
785
+ // Skip documents that don't exist or can't be accessed
786
+ continue;
787
+ }
788
+ // Apply view filter - This will be removed when we pass the viewfilter along
789
+ // to the underlying store, but is here now for the interface.
790
+ for (const scope in document.state) {
791
+ if (!matchesScope(view, scope)) {
792
+ delete document.state[scope];
793
+ }
794
+ }
795
+ documents.push(document);
523
796
  }
524
- documents.push(document);
797
+ if (signal?.aborted) {
798
+ throw new AbortError();
799
+ }
800
+ // Apply paging - this will be removed when we pass the paging along
801
+ // to the underlying store, but is here now for the interface.
802
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
803
+ const limit = paging?.limit || documents.length;
804
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
805
+ // Create paged results
806
+ const hasMore = startIndex + limit < documents.length;
807
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
808
+ return {
809
+ results: pagedDocuments,
810
+ options: paging || { cursor: "0", limit: documents.length },
811
+ nextCursor,
812
+ next: hasMore
813
+ ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, undefined, signal)
814
+ : undefined,
815
+ };
525
816
  }
526
- if (signal?.aborted) {
527
- throw new AbortError();
817
+ else {
818
+ const documents = [];
819
+ // Resolve each slug to a document ID
820
+ const documentIds = [];
821
+ for (const slug of slugs) {
822
+ if (signal?.aborted) {
823
+ throw new AbortError();
824
+ }
825
+ const documentId = await this.documentView.resolveSlug(slug, view, undefined, signal);
826
+ if (documentId) {
827
+ documentIds.push(documentId);
828
+ }
829
+ }
830
+ // Fetch each document
831
+ for (const documentId of documentIds) {
832
+ if (signal?.aborted) {
833
+ throw new AbortError();
834
+ }
835
+ try {
836
+ const document = await this.documentView.get(documentId, view, undefined, signal);
837
+ documents.push(document);
838
+ }
839
+ catch {
840
+ // Skip documents that don't exist or can't be accessed
841
+ continue;
842
+ }
843
+ }
844
+ if (signal?.aborted) {
845
+ throw new AbortError();
846
+ }
847
+ // Apply paging
848
+ const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
849
+ const limit = paging?.limit || documents.length;
850
+ const pagedDocuments = documents.slice(startIndex, startIndex + limit);
851
+ // Create paged results
852
+ const hasMore = startIndex + limit < documents.length;
853
+ const nextCursor = hasMore ? String(startIndex + limit) : undefined;
854
+ return {
855
+ results: pagedDocuments,
856
+ options: paging || { cursor: "0", limit: documents.length },
857
+ nextCursor,
858
+ next: hasMore
859
+ ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, undefined, signal)
860
+ : undefined,
861
+ };
528
862
  }
529
- // Apply paging - this will be removed when we pass the paging along
530
- // to the underlying store, but is here now for the interface.
531
- const startIndex = paging ? parseInt(paging.cursor) || 0 : 0;
532
- const limit = paging?.limit || documents.length;
533
- const pagedDocuments = documents.slice(startIndex, startIndex + limit);
534
- // Create paged results
535
- const hasMore = startIndex + limit < documents.length;
536
- const nextCursor = hasMore ? String(startIndex + limit) : undefined;
537
- return {
538
- results: pagedDocuments,
539
- options: paging || { cursor: "0", limit: documents.length },
540
- nextCursor,
541
- next: hasMore
542
- ? () => this.findBySlugs(slugs, view, { cursor: nextCursor, limit }, signal)
543
- : undefined,
544
- };
545
863
  }
546
864
  /**
547
865
  * Finds documents by parent ID
548
866
  */
549
867
  async findByParentId(parentId, view, paging, signal) {
550
- // Get child document IDs from storage
551
- const childIds = await this.documentStorage.getChildren(parentId);
868
+ // Get child relationships from indexer
869
+ const relationships = await this._documentIndexer.getOutgoing(parentId, ["child"], undefined, signal);
552
870
  if (signal?.aborted) {
553
871
  throw new AbortError();
554
872
  }
555
873
  const documents = [];
556
- // Fetch each child document
557
- for (const childId of childIds) {
874
+ // Fetch each child document using the appropriate storage method
875
+ for (const relationship of relationships) {
558
876
  if (signal?.aborted) {
559
877
  throw new AbortError();
560
878
  }
561
- let document;
562
879
  try {
563
- document = await this.documentStorage.get(childId);
880
+ let document;
881
+ if (this.features.legacyStorageEnabled) {
882
+ document = await this.documentStorage.get(relationship.targetId);
883
+ // Apply view filter for legacy storage
884
+ for (const scope in document.state) {
885
+ if (!matchesScope(view, scope)) {
886
+ delete document.state[scope];
887
+ }
888
+ }
889
+ }
890
+ else {
891
+ document = await this.documentView.get(relationship.targetId, view, undefined, signal);
892
+ }
893
+ documents.push(document);
564
894
  }
565
895
  catch {
566
896
  // Skip documents that don't exist or can't be accessed
567
- // This matches the behavior expected from a search operation
568
897
  continue;
569
898
  }
570
- // Apply view filter - This will be removed when we pass the viewfilter along
571
- // to the underlying store, but is here now for the interface.
572
- for (const scope in document.state) {
573
- if (!matchesScope(view, scope)) {
574
- delete document.state[scope];
575
- }
576
- }
577
- documents.push(document);
578
899
  }
579
900
  if (signal?.aborted) {
580
901
  throw new AbortError();
@@ -598,61 +919,70 @@ export class Reactor {
598
919
  /**
599
920
  * Finds documents by type
600
921
  */
601
- async findByType(type, view, paging, signal) {
602
- const documents = [];
603
- // Use storage's findByType method directly
604
- const cursor = paging?.cursor;
605
- const limit = paging?.limit || 100;
606
- // Get document IDs of the specified type
607
- const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor);
608
- if (signal?.aborted) {
609
- throw new AbortError();
610
- }
611
- // Fetch each document by its ID
612
- for (const documentId of documentIds) {
922
+ async findByType(type, view, paging, consistencyToken, signal) {
923
+ if (consistencyToken) {
924
+ await this.documentView.waitForConsistency(consistencyToken, undefined, signal);
925
+ }
926
+ if (this.features.legacyStorageEnabled) {
927
+ const documents = [];
928
+ // Use storage's findByType method directly
929
+ const cursor = paging?.cursor;
930
+ const limit = paging?.limit || 100;
931
+ // Get document IDs of the specified type
932
+ const { documents: documentIds, nextCursor } = await this.documentStorage.findByType(type, limit, cursor);
613
933
  if (signal?.aborted) {
614
934
  throw new AbortError();
615
935
  }
616
- let document;
617
- try {
618
- document = await this.documentStorage.get(documentId);
619
- }
620
- catch {
621
- // Skip documents that can't be retrieved
622
- continue;
623
- }
624
- // Apply view filter
625
- for (const scope in document.state) {
626
- if (!matchesScope(view, scope)) {
627
- delete document.state[scope];
936
+ // Fetch each document by its ID
937
+ for (const documentId of documentIds) {
938
+ if (signal?.aborted) {
939
+ throw new AbortError();
940
+ }
941
+ let document;
942
+ try {
943
+ document = await this.documentStorage.get(documentId);
944
+ }
945
+ catch {
946
+ // Skip documents that can't be retrieved
947
+ continue;
948
+ }
949
+ // Apply view filter
950
+ for (const scope in document.state) {
951
+ if (!matchesScope(view, scope)) {
952
+ delete document.state[scope];
953
+ }
628
954
  }
955
+ documents.push(document);
629
956
  }
630
- documents.push(document);
631
- }
632
- if (signal?.aborted) {
633
- throw new AbortError();
957
+ if (signal?.aborted) {
958
+ throw new AbortError();
959
+ }
960
+ // Results are already paged from the storage layer
961
+ return {
962
+ results: documents,
963
+ options: paging || { cursor: cursor || "0", limit },
964
+ nextCursor,
965
+ next: nextCursor
966
+ ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, undefined, signal)
967
+ : undefined,
968
+ };
634
969
  }
635
- // Results are already paged from the storage layer
636
- return {
637
- results: documents,
638
- options: paging || { cursor: cursor || "0", limit },
639
- nextCursor,
640
- next: nextCursor
641
- ? async () => this.findByType(type, view, { cursor: nextCursor, limit }, signal)
642
- : undefined,
643
- };
644
- }
645
- toErrorInfo(error) {
646
- if (error instanceof Error) {
970
+ else {
971
+ const result = await this.documentView.findByType(type, view, paging, undefined, signal);
972
+ if (signal?.aborted) {
973
+ throw new AbortError();
974
+ }
975
+ const cursor = paging?.cursor;
976
+ const limit = paging?.limit || 100;
647
977
  return {
648
- message: error.message,
649
- stack: error.stack || new Error().stack || "",
978
+ results: result.items,
979
+ options: paging || { cursor: cursor || "0", limit },
980
+ nextCursor: result.nextCursor,
981
+ next: result.nextCursor
982
+ ? async () => this.findByType(type, view, { cursor: result.nextCursor, limit }, undefined, signal)
983
+ : undefined,
650
984
  };
651
985
  }
652
- return {
653
- message: error,
654
- stack: new Error().stack || "",
655
- };
656
986
  }
657
987
  }
658
988
  //# sourceMappingURL=reactor.js.map