@powerhousedao/reactor 6.0.0-dev.6 → 6.0.0-dev.60

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 (324) hide show
  1. package/dist/src/cache/collection-membership-cache.d.ts +13 -0
  2. package/dist/src/cache/collection-membership-cache.d.ts.map +1 -0
  3. package/dist/src/cache/collection-membership-cache.js +33 -0
  4. package/dist/src/cache/collection-membership-cache.js.map +1 -0
  5. package/dist/src/cache/document-meta-cache.d.ts.map +1 -1
  6. package/dist/src/cache/document-meta-cache.js +6 -5
  7. package/dist/src/cache/document-meta-cache.js.map +1 -1
  8. package/dist/src/cache/kysely-operation-index.d.ts +6 -1
  9. package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
  10. package/dist/src/cache/kysely-operation-index.js +102 -7
  11. package/dist/src/cache/kysely-operation-index.js.map +1 -1
  12. package/dist/src/cache/kysely-write-cache.d.ts +9 -2
  13. package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
  14. package/dist/src/cache/kysely-write-cache.js +38 -15
  15. package/dist/src/cache/kysely-write-cache.js.map +1 -1
  16. package/dist/src/cache/operation-index-types.d.ts +16 -2
  17. package/dist/src/cache/operation-index-types.d.ts.map +1 -1
  18. package/dist/src/cache/operation-index-types.js.map +1 -1
  19. package/dist/src/cache/write/interfaces.d.ts +7 -2
  20. package/dist/src/cache/write/interfaces.d.ts.map +1 -1
  21. package/dist/src/client/reactor-client.d.ts +13 -10
  22. package/dist/src/client/reactor-client.d.ts.map +1 -1
  23. package/dist/src/client/reactor-client.js +134 -43
  24. package/dist/src/client/reactor-client.js.map +1 -1
  25. package/dist/src/client/types.d.ts +25 -6
  26. package/dist/src/client/types.d.ts.map +1 -1
  27. package/dist/src/client/types.js.map +1 -1
  28. package/dist/src/core/reactor-builder.d.ts +23 -7
  29. package/dist/src/core/reactor-builder.d.ts.map +1 -1
  30. package/dist/src/core/reactor-builder.js +99 -24
  31. package/dist/src/core/reactor-builder.js.map +1 -1
  32. package/dist/src/core/reactor-client-builder.d.ts +5 -4
  33. package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
  34. package/dist/src/core/reactor-client-builder.js +14 -5
  35. package/dist/src/core/reactor-client-builder.js.map +1 -1
  36. package/dist/src/core/reactor.d.ts +20 -80
  37. package/dist/src/core/reactor.d.ts.map +1 -1
  38. package/dist/src/core/reactor.js +235 -576
  39. package/dist/src/core/reactor.js.map +1 -1
  40. package/dist/src/core/types.d.ts +64 -28
  41. package/dist/src/core/types.d.ts.map +1 -1
  42. package/dist/src/core/utils.d.ts +39 -3
  43. package/dist/src/core/utils.d.ts.map +1 -1
  44. package/dist/src/core/utils.js +63 -9
  45. package/dist/src/core/utils.js.map +1 -1
  46. package/dist/src/events/types.d.ts +35 -10
  47. package/dist/src/events/types.d.ts.map +1 -1
  48. package/dist/src/events/types.js +7 -5
  49. package/dist/src/events/types.js.map +1 -1
  50. package/dist/src/executor/document-action-handler.d.ts +37 -0
  51. package/dist/src/executor/document-action-handler.d.ts.map +1 -0
  52. package/dist/src/executor/document-action-handler.js +356 -0
  53. package/dist/src/executor/document-action-handler.js.map +1 -0
  54. package/dist/src/executor/signature-verifier.d.ts +9 -0
  55. package/dist/src/executor/signature-verifier.d.ts.map +1 -0
  56. package/dist/src/executor/signature-verifier.js +70 -0
  57. package/dist/src/executor/signature-verifier.js.map +1 -0
  58. package/dist/src/executor/simple-job-executor-manager.d.ts +6 -1
  59. package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
  60. package/dist/src/executor/simple-job-executor-manager.js +125 -13
  61. package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
  62. package/dist/src/executor/simple-job-executor.d.ts +6 -46
  63. package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
  64. package/dist/src/executor/simple-job-executor.js +113 -588
  65. package/dist/src/executor/simple-job-executor.js.map +1 -1
  66. package/dist/src/executor/types.d.ts +1 -3
  67. package/dist/src/executor/types.d.ts.map +1 -1
  68. package/dist/src/executor/types.js.map +1 -1
  69. package/dist/src/executor/util.d.ts +12 -2
  70. package/dist/src/executor/util.d.ts.map +1 -1
  71. package/dist/src/executor/util.js +47 -1
  72. package/dist/src/executor/util.js.map +1 -1
  73. package/dist/src/index.d.ts +11 -9
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/index.js +7 -6
  76. package/dist/src/index.js.map +1 -1
  77. package/dist/src/job-tracker/in-memory-job-tracker.d.ts +4 -3
  78. package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
  79. package/dist/src/job-tracker/in-memory-job-tracker.js +20 -18
  80. package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
  81. package/dist/src/job-tracker/interfaces.d.ts +3 -1
  82. package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
  83. package/dist/src/logging/console.d.ts +1 -22
  84. package/dist/src/logging/console.d.ts.map +1 -1
  85. package/dist/src/logging/console.js +1 -107
  86. package/dist/src/logging/console.js.map +1 -1
  87. package/dist/src/logging/types.d.ts +1 -11
  88. package/dist/src/logging/types.d.ts.map +1 -1
  89. package/dist/src/processors/index.d.ts +1 -1
  90. package/dist/src/processors/index.d.ts.map +1 -1
  91. package/dist/src/processors/index.js.map +1 -1
  92. package/dist/src/processors/processor-manager.d.ts +2 -2
  93. package/dist/src/processors/processor-manager.d.ts.map +1 -1
  94. package/dist/src/processors/processor-manager.js.map +1 -1
  95. package/dist/src/processors/relational/types.d.ts +2 -0
  96. package/dist/src/processors/relational/types.d.ts.map +1 -0
  97. package/dist/src/processors/relational/types.js +2 -0
  98. package/dist/src/processors/relational/types.js.map +1 -0
  99. package/dist/src/processors/relational/utils.d.ts +2 -0
  100. package/dist/src/processors/relational/utils.d.ts.map +1 -0
  101. package/dist/src/processors/relational/utils.js +2 -0
  102. package/dist/src/processors/relational/utils.js.map +1 -0
  103. package/dist/src/processors/utils.d.ts +2 -2
  104. package/dist/src/processors/utils.d.ts.map +1 -1
  105. package/dist/src/processors/utils.js +2 -1
  106. package/dist/src/processors/utils.js.map +1 -1
  107. package/dist/src/queue/job-execution-handle.d.ts +3 -0
  108. package/dist/src/queue/job-execution-handle.d.ts.map +1 -1
  109. package/dist/src/queue/job-execution-handle.js +9 -0
  110. package/dist/src/queue/job-execution-handle.js.map +1 -1
  111. package/dist/src/queue/queue.d.ts +30 -1
  112. package/dist/src/queue/queue.d.ts.map +1 -1
  113. package/dist/src/queue/queue.js +110 -1
  114. package/dist/src/queue/queue.js.map +1 -1
  115. package/dist/src/queue/types.d.ts +4 -3
  116. package/dist/src/queue/types.d.ts.map +1 -1
  117. package/dist/src/queue/types.js.map +1 -1
  118. package/dist/src/read-models/base-read-model.d.ts +1 -1
  119. package/dist/src/read-models/base-read-model.d.ts.map +1 -1
  120. package/dist/src/read-models/base-read-model.js +4 -4
  121. package/dist/src/read-models/base-read-model.js.map +1 -1
  122. package/dist/src/read-models/coordinator.d.ts +2 -2
  123. package/dist/src/read-models/coordinator.d.ts.map +1 -1
  124. package/dist/src/read-models/coordinator.js +8 -8
  125. package/dist/src/read-models/coordinator.js.map +1 -1
  126. package/dist/src/read-models/document-view.d.ts +6 -3
  127. package/dist/src/read-models/document-view.d.ts.map +1 -1
  128. package/dist/src/read-models/document-view.js +130 -48
  129. package/dist/src/read-models/document-view.js.map +1 -1
  130. package/dist/src/read-models/interfaces.d.ts +1 -1
  131. package/dist/src/read-models/interfaces.d.ts.map +1 -1
  132. package/dist/src/registry/document-model-resolver.d.ts +29 -0
  133. package/dist/src/registry/document-model-resolver.d.ts.map +1 -0
  134. package/dist/src/registry/document-model-resolver.js +81 -0
  135. package/dist/src/registry/document-model-resolver.js.map +1 -0
  136. package/dist/src/registry/implementation.d.ts +4 -0
  137. package/dist/src/registry/implementation.d.ts.map +1 -1
  138. package/dist/src/registry/implementation.js +10 -0
  139. package/dist/src/registry/implementation.js.map +1 -1
  140. package/dist/src/registry/index.d.ts +3 -1
  141. package/dist/src/registry/index.d.ts.map +1 -1
  142. package/dist/src/registry/index.js +1 -0
  143. package/dist/src/registry/index.js.map +1 -1
  144. package/dist/src/registry/interfaces.d.ts +8 -0
  145. package/dist/src/registry/interfaces.d.ts.map +1 -1
  146. package/dist/src/shared/awaiter.d.ts +2 -2
  147. package/dist/src/shared/awaiter.d.ts.map +1 -1
  148. package/dist/src/shared/awaiter.js +11 -11
  149. package/dist/src/shared/awaiter.js.map +1 -1
  150. package/dist/src/shared/collect-all-pages.d.ts +7 -0
  151. package/dist/src/shared/collect-all-pages.d.ts.map +1 -0
  152. package/dist/src/shared/collect-all-pages.js +17 -0
  153. package/dist/src/shared/collect-all-pages.js.map +1 -0
  154. package/dist/src/shared/drive-url.d.ts +15 -0
  155. package/dist/src/shared/drive-url.d.ts.map +1 -0
  156. package/dist/src/shared/drive-url.js +17 -0
  157. package/dist/src/shared/drive-url.js.map +1 -0
  158. package/dist/src/shared/errors.d.ts +9 -0
  159. package/dist/src/shared/errors.d.ts.map +1 -1
  160. package/dist/src/shared/errors.js +18 -0
  161. package/dist/src/shared/errors.js.map +1 -1
  162. package/dist/src/shared/factories.d.ts +6 -2
  163. package/dist/src/shared/factories.d.ts.map +1 -1
  164. package/dist/src/shared/factories.js +10 -2
  165. package/dist/src/shared/factories.js.map +1 -1
  166. package/dist/src/shared/types.d.ts +32 -6
  167. package/dist/src/shared/types.d.ts.map +1 -1
  168. package/dist/src/shared/types.js +4 -4
  169. package/dist/src/shared/types.js.map +1 -1
  170. package/dist/src/signer/passthrough-signer.d.ts +1 -1
  171. package/dist/src/signer/passthrough-signer.d.ts.map +1 -1
  172. package/dist/src/signer/passthrough-signer.js +1 -3
  173. package/dist/src/signer/passthrough-signer.js.map +1 -1
  174. package/dist/src/storage/interfaces.d.ts +238 -124
  175. package/dist/src/storage/interfaces.d.ts.map +1 -1
  176. package/dist/src/storage/interfaces.js +10 -0
  177. package/dist/src/storage/interfaces.js.map +1 -1
  178. package/dist/src/storage/kysely/document-indexer.d.ts +8 -7
  179. package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -1
  180. package/dist/src/storage/kysely/document-indexer.js +123 -52
  181. package/dist/src/storage/kysely/document-indexer.js.map +1 -1
  182. package/dist/src/storage/kysely/store.d.ts +5 -4
  183. package/dist/src/storage/kysely/store.d.ts.map +1 -1
  184. package/dist/src/storage/kysely/store.js +52 -21
  185. package/dist/src/storage/kysely/store.js.map +1 -1
  186. package/dist/src/storage/kysely/sync-cursor-storage.d.ts +1 -1
  187. package/dist/src/storage/kysely/sync-cursor-storage.d.ts.map +1 -1
  188. package/dist/src/storage/kysely/sync-cursor-storage.js +6 -2
  189. package/dist/src/storage/kysely/sync-cursor-storage.js.map +1 -1
  190. package/dist/src/storage/kysely/sync-dead-letter-storage.d.ts +17 -0
  191. package/dist/src/storage/kysely/sync-dead-letter-storage.d.ts.map +1 -0
  192. package/dist/src/storage/kysely/sync-dead-letter-storage.js +110 -0
  193. package/dist/src/storage/kysely/sync-dead-letter-storage.js.map +1 -0
  194. package/dist/src/storage/kysely/sync-remote-storage.js +1 -1
  195. package/dist/src/storage/kysely/sync-remote-storage.js.map +1 -1
  196. package/dist/src/storage/kysely/types.d.ts +22 -0
  197. package/dist/src/storage/kysely/types.d.ts.map +1 -1
  198. package/dist/src/storage/migrations/011_add_cursor_type_column.d.ts +3 -0
  199. package/dist/src/storage/migrations/011_add_cursor_type_column.d.ts.map +1 -0
  200. package/dist/src/storage/migrations/011_add_cursor_type_column.js +29 -0
  201. package/dist/src/storage/migrations/011_add_cursor_type_column.js.map +1 -0
  202. package/dist/src/storage/migrations/012_add_source_remote_column.d.ts +3 -0
  203. package/dist/src/storage/migrations/012_add_source_remote_column.d.ts.map +1 -0
  204. package/dist/src/storage/migrations/012_add_source_remote_column.js +7 -0
  205. package/dist/src/storage/migrations/012_add_source_remote_column.js.map +1 -0
  206. package/dist/src/storage/migrations/013_create_sync_dead_letters_table.d.ts +3 -0
  207. package/dist/src/storage/migrations/013_create_sync_dead_letters_table.d.ts.map +1 -0
  208. package/dist/src/storage/migrations/013_create_sync_dead_letters_table.js +24 -0
  209. package/dist/src/storage/migrations/013_create_sync_dead_letters_table.js.map +1 -0
  210. package/dist/src/storage/migrations/migrator.d.ts.map +1 -1
  211. package/dist/src/storage/migrations/migrator.js +6 -0
  212. package/dist/src/storage/migrations/migrator.js.map +1 -1
  213. package/dist/src/subs/default-error-handler.d.ts.map +1 -1
  214. package/dist/src/subs/default-error-handler.js.map +1 -1
  215. package/dist/src/subs/subscription-notification-read-model.d.ts +3 -2
  216. package/dist/src/subs/subscription-notification-read-model.d.ts.map +1 -1
  217. package/dist/src/subs/subscription-notification-read-model.js +1 -1
  218. package/dist/src/subs/subscription-notification-read-model.js.map +1 -1
  219. package/dist/src/sync/batch-aggregator.d.ts +25 -0
  220. package/dist/src/sync/batch-aggregator.d.ts.map +1 -0
  221. package/dist/src/sync/batch-aggregator.js +94 -0
  222. package/dist/src/sync/batch-aggregator.js.map +1 -0
  223. package/dist/src/sync/buffered-mailbox.d.ts +36 -0
  224. package/dist/src/sync/buffered-mailbox.d.ts.map +1 -0
  225. package/dist/src/sync/buffered-mailbox.js +164 -0
  226. package/dist/src/sync/buffered-mailbox.js.map +1 -0
  227. package/dist/src/sync/channels/{gql-channel.d.ts → gql-req-channel.d.ts} +49 -42
  228. package/dist/src/sync/channels/gql-req-channel.d.ts.map +1 -0
  229. package/dist/src/sync/channels/gql-req-channel.js +548 -0
  230. package/dist/src/sync/channels/gql-req-channel.js.map +1 -0
  231. package/dist/src/sync/channels/gql-request-channel-factory.d.ts +32 -0
  232. package/dist/src/sync/channels/gql-request-channel-factory.d.ts.map +1 -0
  233. package/dist/src/sync/channels/gql-request-channel-factory.js +105 -0
  234. package/dist/src/sync/channels/gql-request-channel-factory.js.map +1 -0
  235. package/dist/src/sync/channels/gql-res-channel.d.ts +25 -0
  236. package/dist/src/sync/channels/gql-res-channel.d.ts.map +1 -0
  237. package/dist/src/sync/channels/gql-res-channel.js +79 -0
  238. package/dist/src/sync/channels/gql-res-channel.js.map +1 -0
  239. package/dist/src/sync/channels/gql-response-channel-factory.d.ts +13 -0
  240. package/dist/src/sync/channels/gql-response-channel-factory.d.ts.map +1 -0
  241. package/dist/src/sync/channels/gql-response-channel-factory.js +14 -0
  242. package/dist/src/sync/channels/gql-response-channel-factory.js.map +1 -0
  243. package/dist/src/sync/channels/index.d.ts +6 -4
  244. package/dist/src/sync/channels/index.d.ts.map +1 -1
  245. package/dist/src/sync/channels/index.js +6 -4
  246. package/dist/src/sync/channels/index.js.map +1 -1
  247. package/dist/src/sync/channels/interval-poll-timer.d.ts +40 -0
  248. package/dist/src/sync/channels/interval-poll-timer.d.ts.map +1 -0
  249. package/dist/src/sync/channels/interval-poll-timer.js +123 -0
  250. package/dist/src/sync/channels/interval-poll-timer.js.map +1 -0
  251. package/dist/src/sync/channels/poll-timer.d.ts +14 -0
  252. package/dist/src/sync/channels/poll-timer.d.ts.map +1 -0
  253. package/dist/src/sync/channels/poll-timer.js +2 -0
  254. package/dist/src/sync/channels/poll-timer.js.map +1 -0
  255. package/dist/src/sync/channels/utils.d.ts +15 -1
  256. package/dist/src/sync/channels/utils.d.ts.map +1 -1
  257. package/dist/src/sync/channels/utils.js +67 -2
  258. package/dist/src/sync/channels/utils.js.map +1 -1
  259. package/dist/src/sync/index.d.ts +10 -6
  260. package/dist/src/sync/index.d.ts.map +1 -1
  261. package/dist/src/sync/index.js +7 -5
  262. package/dist/src/sync/index.js.map +1 -1
  263. package/dist/src/sync/interfaces.d.ts +34 -21
  264. package/dist/src/sync/interfaces.d.ts.map +1 -1
  265. package/dist/src/sync/mailbox.d.ts +51 -12
  266. package/dist/src/sync/mailbox.d.ts.map +1 -1
  267. package/dist/src/sync/mailbox.js +89 -6
  268. package/dist/src/sync/mailbox.js.map +1 -1
  269. package/dist/src/sync/sync-awaiter.d.ts +34 -0
  270. package/dist/src/sync/sync-awaiter.d.ts.map +1 -0
  271. package/dist/src/sync/sync-awaiter.js +124 -0
  272. package/dist/src/sync/sync-awaiter.js.map +1 -0
  273. package/dist/src/sync/sync-builder.d.ts +5 -1
  274. package/dist/src/sync/sync-builder.d.ts.map +1 -1
  275. package/dist/src/sync/sync-builder.js +14 -1
  276. package/dist/src/sync/sync-builder.js.map +1 -1
  277. package/dist/src/sync/sync-manager.d.ts +21 -8
  278. package/dist/src/sync/sync-manager.d.ts.map +1 -1
  279. package/dist/src/sync/sync-manager.js +274 -93
  280. package/dist/src/sync/sync-manager.js.map +1 -1
  281. package/dist/src/sync/sync-operation.d.ts +4 -2
  282. package/dist/src/sync/sync-operation.d.ts.map +1 -1
  283. package/dist/src/sync/sync-operation.js +8 -1
  284. package/dist/src/sync/sync-operation.js.map +1 -1
  285. package/dist/src/sync/sync-status-tracker.d.ts +31 -0
  286. package/dist/src/sync/sync-status-tracker.d.ts.map +1 -0
  287. package/dist/src/sync/sync-status-tracker.js +137 -0
  288. package/dist/src/sync/sync-status-tracker.js.map +1 -0
  289. package/dist/src/sync/types.d.ts +79 -2
  290. package/dist/src/sync/types.d.ts.map +1 -1
  291. package/dist/src/sync/types.js +15 -0
  292. package/dist/src/sync/types.js.map +1 -1
  293. package/dist/src/sync/utils.d.ts +37 -2
  294. package/dist/src/sync/utils.d.ts.map +1 -1
  295. package/dist/src/sync/utils.js +205 -0
  296. package/dist/src/sync/utils.js.map +1 -1
  297. package/dist/src/utils/reshuffle.d.ts +22 -5
  298. package/dist/src/utils/reshuffle.d.ts.map +1 -1
  299. package/dist/src/utils/reshuffle.js +50 -6
  300. package/dist/src/utils/reshuffle.js.map +1 -1
  301. package/package.json +12 -15
  302. package/dist/src/processors/types.d.ts +0 -63
  303. package/dist/src/processors/types.d.ts.map +0 -1
  304. package/dist/src/processors/types.js +0 -2
  305. package/dist/src/processors/types.js.map +0 -1
  306. package/dist/src/storage/consistency-aware-legacy-storage.d.ts +0 -33
  307. package/dist/src/storage/consistency-aware-legacy-storage.d.ts.map +0 -1
  308. package/dist/src/storage/consistency-aware-legacy-storage.js +0 -65
  309. package/dist/src/storage/consistency-aware-legacy-storage.js.map +0 -1
  310. package/dist/src/sync/channels/composite-channel-factory.d.ts +0 -30
  311. package/dist/src/sync/channels/composite-channel-factory.d.ts.map +0 -1
  312. package/dist/src/sync/channels/composite-channel-factory.js +0 -87
  313. package/dist/src/sync/channels/composite-channel-factory.js.map +0 -1
  314. package/dist/src/sync/channels/gql-channel-factory.d.ts +0 -25
  315. package/dist/src/sync/channels/gql-channel-factory.d.ts.map +0 -1
  316. package/dist/src/sync/channels/gql-channel-factory.js +0 -76
  317. package/dist/src/sync/channels/gql-channel-factory.js.map +0 -1
  318. package/dist/src/sync/channels/gql-channel.d.ts.map +0 -1
  319. package/dist/src/sync/channels/gql-channel.js +0 -423
  320. package/dist/src/sync/channels/gql-channel.js.map +0 -1
  321. package/dist/src/sync/channels/polling-channel.d.ts +0 -39
  322. package/dist/src/sync/channels/polling-channel.d.ts.map +0 -1
  323. package/dist/src/sync/channels/polling-channel.js +0 -72
  324. package/dist/src/sync/channels/polling-channel.js.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import { KyselySyncCursorStorage } from "../storage/kysely/sync-cursor-storage.js";
2
+ import { KyselySyncDeadLetterStorage } from "../storage/kysely/sync-dead-letter-storage.js";
2
3
  import { KyselySyncRemoteStorage } from "../storage/kysely/sync-remote-storage.js";
3
4
  import { SyncManager } from "./sync-manager.js";
4
5
  export class SyncBuilder {
5
6
  channelFactory;
6
7
  remoteStorage;
7
8
  cursorStorage;
9
+ deadLetterStorage;
10
+ maxDeadLettersPerRemote = 100;
8
11
  withChannelFactory(factory) {
9
12
  this.channelFactory = factory;
10
13
  return this;
@@ -17,6 +20,14 @@ export class SyncBuilder {
17
20
  this.cursorStorage = storage;
18
21
  return this;
19
22
  }
23
+ withDeadLetterStorage(storage) {
24
+ this.deadLetterStorage = storage;
25
+ return this;
26
+ }
27
+ withMaxDeadLettersPerRemote(limit) {
28
+ this.maxDeadLettersPerRemote = limit;
29
+ return this;
30
+ }
20
31
  build(reactor, logger, operationIndex, eventBus, db) {
21
32
  const module = this.buildModule(reactor, logger, operationIndex, eventBus, db);
22
33
  return module.syncManager;
@@ -27,10 +38,12 @@ export class SyncBuilder {
27
38
  }
28
39
  const remoteStorage = this.remoteStorage ?? new KyselySyncRemoteStorage(db);
29
40
  const cursorStorage = this.cursorStorage ?? new KyselySyncCursorStorage(db);
30
- const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, this.channelFactory, operationIndex, reactor, eventBus);
41
+ const deadLetterStorage = this.deadLetterStorage ?? new KyselySyncDeadLetterStorage(db);
42
+ const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, this.maxDeadLettersPerRemote);
31
43
  return {
32
44
  remoteStorage,
33
45
  cursorStorage,
46
+ deadLetterStorage,
34
47
  channelFactory: this.channelFactory,
35
48
  syncManager,
36
49
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sync-builder.js","sourceRoot":"","sources":["../../../src/sync/sync-builder.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAGnF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,WAAW;IACd,cAAc,CAAmB;IACjC,aAAa,CAAsB;IACnC,aAAa,CAAsB;IAE3C,kBAAkB,CAAC,OAAwB;QACzC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB,CAAC,OAA2B;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB,CAAC,OAA2B;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CACH,OAAiB,EACjB,MAAe,EACf,cAA+B,EAC/B,QAAmB,EACnB,EAAoB;QAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAC7B,OAAO,EACP,MAAM,EACN,cAAc,EACd,QAAQ,EACR,EAAE,CACH,CAAC;QACF,OAAO,MAAM,CAAC,WAAW,CAAC;IAC5B,CAAC;IAED,WAAW,CACT,OAAiB,EACjB,MAAe,EACf,cAA+B,EAC/B,QAAmB,EACnB,EAAoB;QAEpB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAE5E,MAAM,WAAW,GAAG,IAAI,WAAW,CACjC,MAAM,EACN,aAAa,EACb,aAAa,EACb,IAAI,CAAC,cAAc,EACnB,cAAc,EACd,OAAO,EACP,QAAQ,CACT,CAAC;QAEF,OAAO;YACL,aAAa;YACb,aAAa;YACb,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,WAAW;SACZ,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"sync-builder.js","sourceRoot":"","sources":["../../../src/sync/sync-builder.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AAC5F,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAGnF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,WAAW;IACd,cAAc,CAAmB;IACjC,aAAa,CAAsB;IACnC,aAAa,CAAsB;IACnC,iBAAiB,CAA0B;IAC3C,uBAAuB,GAAW,GAAG,CAAC;IAE9C,kBAAkB,CAAC,OAAwB;QACzC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB,CAAC,OAA2B;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB,CAAC,OAA2B;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qBAAqB,CAAC,OAA+B;QACnD,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2BAA2B,CAAC,KAAa;QACvC,IAAI,CAAC,uBAAuB,GAAG,KAAK,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CACH,OAAiB,EACjB,MAAe,EACf,cAA+B,EAC/B,QAAmB,EACnB,EAAoB;QAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAC7B,OAAO,EACP,MAAM,EACN,cAAc,EACd,QAAQ,EACR,EAAE,CACH,CAAC;QACF,OAAO,MAAM,CAAC,WAAW,CAAC;IAC5B,CAAC;IAED,WAAW,CACT,OAAiB,EACjB,MAAe,EACf,cAA+B,EAC/B,QAAmB,EACnB,EAAoB;QAEpB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,iBAAiB,GACrB,IAAI,CAAC,iBAAiB,IAAI,IAAI,2BAA2B,CAAC,EAAE,CAAC,CAAC;QAEhE,MAAM,WAAW,GAAG,IAAI,WAAW,CACjC,MAAM,EACN,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,IAAI,CAAC,cAAc,EACnB,cAAc,EACd,OAAO,EACP,QAAQ,EACR,IAAI,CAAC,uBAAuB,CAC7B,CAAC;QAEF,OAAO;YACL,aAAa;YACb,aAAa;YACb,iBAAiB;YACjB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,WAAW;SACZ,CAAC;IACJ,CAAC;CACF"}
@@ -3,35 +3,48 @@ import type { IReactor } from "../core/types.js";
3
3
  import type { IEventBus } from "../events/interfaces.js";
4
4
  import type { ILogger } from "../logging/types.js";
5
5
  import { type JobInfo, type ShutdownStatus } from "../shared/types.js";
6
- import type { ISyncCursorStorage, ISyncRemoteStorage } from "../storage/interfaces.js";
6
+ import type { ISyncCursorStorage, ISyncDeadLetterStorage, ISyncRemoteStorage } from "../storage/interfaces.js";
7
7
  import type { IChannelFactory, ISyncManager, Remote } from "./interfaces.js";
8
- import type { ChannelConfig, RemoteFilter, RemoteOptions } from "./types.js";
8
+ import { type SyncStatus, type SyncStatusChangeCallback } from "./sync-status-tracker.js";
9
+ import type { ChannelConfig, RemoteFilter, RemoteOptions, SyncResult } from "./types.js";
9
10
  export declare class SyncManager implements ISyncManager {
10
11
  private readonly logger;
11
12
  private readonly remoteStorage;
12
13
  private readonly cursorStorage;
14
+ private readonly deadLetterStorage;
13
15
  private readonly channelFactory;
14
- private readonly _operationIndex;
16
+ private readonly operationIndex;
15
17
  private readonly reactor;
16
18
  private readonly eventBus;
17
19
  private readonly remotes;
18
20
  private readonly awaiter;
21
+ private readonly syncAwaiter;
19
22
  private isShutdown;
20
23
  private eventUnsubscribe?;
24
+ private failedEventUnsubscribe?;
25
+ private readonly batchAggregator;
26
+ private readonly syncStatusTracker;
27
+ private readonly maxDeadLettersPerRemote;
21
28
  loadJobs: Map<string, JobInfo>;
22
- constructor(logger: ILogger, remoteStorage: ISyncRemoteStorage, cursorStorage: ISyncCursorStorage, channelFactory: IChannelFactory, operationIndex: IOperationIndex, reactor: IReactor, eventBus: IEventBus);
29
+ constructor(logger: ILogger, remoteStorage: ISyncRemoteStorage, cursorStorage: ISyncCursorStorage, deadLetterStorage: ISyncDeadLetterStorage, channelFactory: IChannelFactory, operationIndex: IOperationIndex, reactor: IReactor, eventBus: IEventBus, maxDeadLettersPerRemote?: number);
23
30
  startup(): Promise<void>;
24
31
  shutdown(): ShutdownStatus;
25
32
  getByName(name: string): Remote;
26
33
  getById(id: string): Remote;
27
34
  add(name: string, collectionId: string, channelConfig: ChannelConfig, filter?: RemoteFilter, options?: RemoteOptions, id?: string): Promise<Remote>;
28
- private backfillOutbox;
29
35
  remove(name: string): Promise<void>;
30
36
  list(): Remote[];
37
+ waitForSync(jobId: string, signal?: AbortSignal): Promise<SyncResult>;
38
+ getSyncStatus(documentId: string): SyncStatus | undefined;
39
+ onSyncStatusChange(callback: SyncStatusChangeCallback): () => void;
31
40
  private wireChannelCallbacks;
32
- private handleOperationWritten;
33
- private handleInboxJob;
34
- private handleOutboxJob;
41
+ private loadDeadLetters;
42
+ private getRemotesForCollection;
43
+ private processCompleteBatch;
44
+ private handleInboxAdded;
35
45
  private applyInboxJob;
46
+ private applyInboxBatch;
47
+ private updateOutbox;
48
+ private getOperationsForRemote;
36
49
  }
37
50
  //# sourceMappingURL=sync-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAKzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE7E,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EAGd,MAAM,YAAY,CAAC;AAQpB,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkB;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IACrC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,gBAAgB,CAAC,CAAa;IAE/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAa;gBAGhD,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,kBAAkB,EACjC,aAAa,EAAE,kBAAkB,EACjC,cAAc,EAAE,eAAe,EAC/B,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,QAAQ,EACjB,QAAQ,EAAE,SAAS;IAgBf,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6C9B,QAAQ,IAAI,cAAc;IA2B1B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQ/B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IASrB,GAAG,CACP,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,aAAa,EAC5B,MAAM,GAAE,YAAwD,EAChE,OAAO,GAAE,aAAkB,EAC3B,EAAE,CAAC,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC;YAkEJ,cAAc;IAsDtB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzC,IAAI,IAAI,MAAM,EAAE;IAIhB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,sBAAsB;IAiC9B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,eAAe;YAUT,aAAa;CAsD5B"}
1
+ {"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-manager.ts"],"names":[],"mappings":"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"}
@@ -1,34 +1,48 @@
1
- import { OperationEventTypes, } from "../events/types.js";
1
+ import { ReactorEventTypes, } from "../events/types.js";
2
2
  import { JobAwaiter } from "../shared/awaiter.js";
3
3
  import { JobStatus, } from "../shared/types.js";
4
+ import { BatchAggregator } from "./batch-aggregator.js";
4
5
  import { ChannelError } from "./errors.js";
6
+ import { SyncAwaiter } from "./sync-awaiter.js";
5
7
  import { SyncOperation } from "./sync-operation.js";
6
- import { ChannelErrorSource, SyncOperationStatus } from "./types.js";
7
- import { batchOperationsByDocument, createIdleHealth, filterOperations, } from "./utils.js";
8
+ import { SyncStatusTracker, } from "./sync-status-tracker.js";
9
+ import { ChannelErrorSource } from "./types.js";
10
+ import { batchOperationsByDocument, createIdleHealth, filterOperations, toOperationWithContext, trimMailboxFromBatch, } from "./utils.js";
8
11
  export class SyncManager {
9
12
  logger;
10
13
  remoteStorage;
11
14
  cursorStorage;
15
+ deadLetterStorage;
12
16
  channelFactory;
13
- _operationIndex;
17
+ operationIndex;
14
18
  reactor;
15
19
  eventBus;
16
20
  remotes;
17
21
  awaiter;
22
+ syncAwaiter;
18
23
  isShutdown;
19
24
  eventUnsubscribe;
25
+ failedEventUnsubscribe;
26
+ batchAggregator;
27
+ syncStatusTracker;
28
+ maxDeadLettersPerRemote;
20
29
  loadJobs = new Map();
21
- constructor(logger, remoteStorage, cursorStorage, channelFactory, operationIndex, reactor, eventBus) {
30
+ constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, maxDeadLettersPerRemote = 100) {
22
31
  this.logger = logger;
23
32
  this.remoteStorage = remoteStorage;
24
33
  this.cursorStorage = cursorStorage;
34
+ this.deadLetterStorage = deadLetterStorage;
25
35
  this.channelFactory = channelFactory;
26
- this._operationIndex = operationIndex;
36
+ this.operationIndex = operationIndex;
27
37
  this.reactor = reactor;
28
38
  this.eventBus = eventBus;
39
+ this.maxDeadLettersPerRemote = maxDeadLettersPerRemote;
29
40
  this.remotes = new Map();
30
41
  this.awaiter = new JobAwaiter(eventBus, (jobId, signal) => reactor.getJobStatus(jobId, signal));
42
+ this.syncAwaiter = new SyncAwaiter(eventBus);
31
43
  this.isShutdown = false;
44
+ this.batchAggregator = new BatchAggregator(logger, (batch) => this.processCompleteBatch(batch));
45
+ this.syncStatusTracker = new SyncStatusTracker();
32
46
  }
33
47
  async startup() {
34
48
  if (this.isShutdown) {
@@ -36,14 +50,7 @@ export class SyncManager {
36
50
  }
37
51
  const remoteRecords = await this.remoteStorage.list();
38
52
  for (const record of remoteRecords) {
39
- const channel = this.channelFactory.instance(record.id, record.name, record.channelConfig, this.cursorStorage, record.collectionId, record.filter);
40
- try {
41
- await channel.init();
42
- }
43
- catch (error) {
44
- console.error(`Error initializing channel for remote ${record.name}: ${error instanceof Error ? error.message : String(error)}`);
45
- continue;
46
- }
53
+ const channel = this.channelFactory.instance(record.id, record.name, record.channelConfig, this.cursorStorage, record.collectionId, record.filter, this.operationIndex);
47
54
  const remote = {
48
55
  id: record.id,
49
56
  name: record.name,
@@ -53,28 +60,47 @@ export class SyncManager {
53
60
  channel,
54
61
  };
55
62
  this.remotes.set(record.name, remote);
63
+ await this.loadDeadLetters(remote);
56
64
  this.wireChannelCallbacks(remote);
65
+ try {
66
+ await channel.init();
67
+ }
68
+ catch (error) {
69
+ this.logger.error("Error initializing channel for remote (@name, @error)", record.name, error instanceof Error ? error.message : String(error));
70
+ this.remotes.delete(record.name);
71
+ continue;
72
+ }
73
+ // backfill channels
74
+ const outboxAckOrdinal = remote.channel.outbox.ackOrdinal;
75
+ if (outboxAckOrdinal > 0) {
76
+ await this.updateOutbox(remote, outboxAckOrdinal);
77
+ }
57
78
  }
58
- this.eventUnsubscribe = this.eventBus.subscribe(OperationEventTypes.OPERATION_WRITTEN, (_type, event) => this.handleOperationWritten(event));
79
+ this.eventUnsubscribe = this.eventBus.subscribe(ReactorEventTypes.JOB_WRITE_READY, async (_type, event) => this.batchAggregator.enqueueWriteReady(event));
80
+ this.failedEventUnsubscribe = this.eventBus.subscribe(ReactorEventTypes.JOB_FAILED, async (_type, event) => this.batchAggregator.handleJobFailed(event));
59
81
  }
60
82
  shutdown() {
61
83
  this.isShutdown = true;
84
+ this.batchAggregator.clear();
62
85
  if (this.eventUnsubscribe) {
63
86
  this.eventUnsubscribe();
64
87
  this.eventUnsubscribe = undefined;
65
88
  }
89
+ if (this.failedEventUnsubscribe) {
90
+ this.failedEventUnsubscribe();
91
+ this.failedEventUnsubscribe = undefined;
92
+ }
66
93
  this.awaiter.shutdown();
94
+ this.syncAwaiter.shutdown();
95
+ this.syncStatusTracker.clear();
96
+ const promises = [];
67
97
  for (const remote of this.remotes.values()) {
68
- try {
69
- remote.channel.shutdown();
70
- }
71
- catch (error) {
72
- console.error(`Error shutting down channel for remote ${remote.name}: ${error instanceof Error ? error.message : String(error)}`);
73
- }
98
+ promises.push(remote.channel.shutdown());
74
99
  }
75
100
  this.remotes.clear();
76
101
  return {
77
102
  isShutdown: true,
103
+ completed: Promise.all(promises).then(() => undefined),
78
104
  };
79
105
  }
80
106
  getByName(name) {
@@ -92,7 +118,7 @@ export class SyncManager {
92
118
  }
93
119
  throw new Error(`Remote with id '${id}' does not exist`);
94
120
  }
95
- async add(name, collectionId, channelConfig, filter = { documentId: [], scope: [], branch: "" }, options = {}, id) {
121
+ async add(name, collectionId, channelConfig, filter = { documentId: [], scope: [], branch: "" }, options = { sinceTimestampUtcMs: "0" }, id) {
96
122
  if (this.isShutdown) {
97
123
  throw new Error("SyncManager is shutdown and cannot add remotes");
98
124
  }
@@ -115,8 +141,7 @@ export class SyncManager {
115
141
  status,
116
142
  };
117
143
  await this.remoteStorage.upsert(remoteRecord);
118
- const channel = this.channelFactory.instance(remoteId, name, channelConfig, this.cursorStorage, collectionId, filter);
119
- await channel.init();
144
+ const channel = this.channelFactory.instance(remoteId, name, channelConfig, this.cursorStorage, collectionId, filter, this.operationIndex);
120
145
  const remote = {
121
146
  id: remoteId,
122
147
  name,
@@ -126,103 +151,152 @@ export class SyncManager {
126
151
  channel,
127
152
  };
128
153
  this.remotes.set(name, remote);
154
+ await this.loadDeadLetters(remote);
129
155
  this.wireChannelCallbacks(remote);
130
- await this.backfillOutbox(remote, collectionId, filter);
131
- return remote;
132
- }
133
- async backfillOutbox(remote, collectionId, filter) {
134
- let historicalOps;
135
156
  try {
136
- historicalOps = await this._operationIndex.find(collectionId);
137
- }
138
- catch {
139
- return;
157
+ await channel.init();
140
158
  }
141
- if (historicalOps.items.length === 0) {
142
- return;
143
- }
144
- const opsWithContext = historicalOps.items.map((entry) => ({
145
- operation: {
146
- id: entry.id,
147
- index: entry.index,
148
- skip: entry.skip,
149
- hash: entry.hash,
150
- timestampUtcMs: entry.timestampUtcMs,
151
- action: entry.action,
152
- },
153
- context: {
154
- documentId: entry.documentId,
155
- documentType: entry.documentType,
156
- scope: entry.scope,
157
- branch: entry.branch,
158
- ordinal: entry.ordinal ?? 0,
159
- },
160
- }));
161
- const filteredOps = filterOperations(opsWithContext, filter);
162
- if (filteredOps.length === 0) {
163
- return;
164
- }
165
- const batches = batchOperationsByDocument(filteredOps);
166
- for (const batch of batches) {
167
- const syncOp = new SyncOperation(crypto.randomUUID(), remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
168
- remote.channel.outbox.add(syncOp);
159
+ catch (error) {
160
+ this.remotes.delete(name);
161
+ await this.remoteStorage.remove(name);
162
+ throw error;
169
163
  }
164
+ // backfill
165
+ await this.updateOutbox(remote, 0);
166
+ return remote;
170
167
  }
171
168
  async remove(name) {
172
169
  const remote = this.remotes.get(name);
173
170
  if (!remote) {
174
171
  throw new Error(`Remote with name '${name}' does not exist`);
175
172
  }
173
+ // shutdown the channel
174
+ await remote.channel.shutdown();
175
+ // delete the remote's data
176
176
  await this.remoteStorage.remove(name);
177
- remote.channel.shutdown();
177
+ await this.cursorStorage.remove(name);
178
+ this.syncStatusTracker.untrackRemote(name);
178
179
  this.remotes.delete(name);
179
180
  }
180
181
  list() {
181
182
  return Array.from(this.remotes.values());
182
183
  }
184
+ waitForSync(jobId, signal) {
185
+ return this.syncAwaiter.waitForSync(jobId, signal);
186
+ }
187
+ getSyncStatus(documentId) {
188
+ return this.syncStatusTracker.getStatus(documentId);
189
+ }
190
+ onSyncStatusChange(callback) {
191
+ return this.syncStatusTracker.onChange(callback);
192
+ }
183
193
  wireChannelCallbacks(remote) {
184
- remote.channel.inbox.onAdded((syncOp) => {
185
- this.handleInboxJob(remote, syncOp);
186
- });
187
- remote.channel.outbox.onAdded((syncOp) => {
188
- this.handleOutboxJob(remote, syncOp);
194
+ remote.channel.inbox.onAdded((syncOps) => this.handleInboxAdded(remote, syncOps));
195
+ this.syncStatusTracker.trackRemote(remote.name, remote.channel);
196
+ remote.channel.deadLetter.onAdded((syncOps) => {
197
+ for (const syncOp of syncOps) {
198
+ this.logger.error("Dead letter (@remote, @documentId, @jobId, @error, @dependencies)", remote.name, syncOp.documentId, syncOp.jobId, syncOp.error?.message ?? "unknown", syncOp.jobDependencies);
199
+ const record = {
200
+ id: syncOp.id,
201
+ jobId: syncOp.jobId,
202
+ jobDependencies: syncOp.jobDependencies,
203
+ remoteName: syncOp.remoteName,
204
+ documentId: syncOp.documentId,
205
+ scopes: syncOp.scopes,
206
+ branch: syncOp.branch,
207
+ operations: syncOp.operations,
208
+ errorSource: syncOp.error?.source ?? ChannelErrorSource.None,
209
+ errorMessage: syncOp.error?.error.message ?? "unknown",
210
+ };
211
+ void this.deadLetterStorage.add(record).catch((err) => {
212
+ this.logger.error("Failed to persist dead letter (@id, @error)", record.id, err instanceof Error ? err.message : String(err));
213
+ });
214
+ }
215
+ // Evict oldest dead letters from mailbox if over capacity
216
+ const items = remote.channel.deadLetter.items;
217
+ if (items.length > this.maxDeadLettersPerRemote) {
218
+ const excessCount = items.length - this.maxDeadLettersPerRemote;
219
+ const toEvict = items.slice(0, excessCount);
220
+ remote.channel.deadLetter.remove(...toEvict);
221
+ }
189
222
  });
190
223
  }
191
- handleOperationWritten(event) {
192
- if (this.isShutdown) {
224
+ async loadDeadLetters(remote) {
225
+ let records;
226
+ try {
227
+ const page = await this.deadLetterStorage.list(remote.name, {
228
+ cursor: "0",
229
+ limit: this.maxDeadLettersPerRemote,
230
+ });
231
+ records = page.results;
232
+ }
233
+ catch (error) {
234
+ this.logger.error("Failed to load dead letters for remote (@name, @error)", remote.name, error instanceof Error ? error.message : String(error));
193
235
  return;
194
236
  }
195
- const sourceRemote = event.jobMeta?.sourceRemote;
196
- for (const remote of this.remotes.values()) {
197
- if (sourceRemote && remote.name === sourceRemote) {
198
- continue;
199
- }
200
- const filteredOps = filterOperations(event.operations, remote.filter);
201
- if (filteredOps.length === 0) {
202
- continue;
203
- }
204
- const batches = batchOperationsByDocument(filteredOps);
205
- for (const batch of batches) {
206
- const syncOp = new SyncOperation(crypto.randomUUID(), remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
207
- remote.channel.outbox.add(syncOp);
237
+ if (records.length === 0) {
238
+ return;
239
+ }
240
+ // Records come in ordinal DESC order (newest first).
241
+ // Reverse so the Map maintains chronological insertion order (oldest first),
242
+ // which makes eviction (slice from the front) straightforward.
243
+ records.reverse();
244
+ const syncOps = [];
245
+ for (const record of records) {
246
+ const syncOp = new SyncOperation(record.id, record.jobId, record.jobDependencies, record.remoteName, record.documentId, record.scopes, record.branch, record.operations);
247
+ syncOp.failed(new ChannelError(record.errorSource, new Error(record.errorMessage)));
248
+ syncOps.push(syncOp);
249
+ }
250
+ remote.channel.deadLetter.add(...syncOps);
251
+ this.logger.debug("Loaded @count persisted dead letters for remote @name", records.length, remote.name);
252
+ }
253
+ getRemotesForCollection(collectionId) {
254
+ return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
255
+ }
256
+ async processCompleteBatch(batch) {
257
+ // get the unique set of collection ids
258
+ const collectionIds = [
259
+ ...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections)),
260
+ ];
261
+ // get the unique set of affected remotes
262
+ const affectedRemotes = [];
263
+ for (const collectionId of collectionIds) {
264
+ const remotes = this.getRemotesForCollection(collectionId);
265
+ for (const remote of remotes) {
266
+ if (!affectedRemotes.includes(remote)) {
267
+ affectedRemotes.push(remote);
268
+ }
208
269
  }
209
270
  }
271
+ // ack matching inbox items
272
+ for (const remote of affectedRemotes) {
273
+ trimMailboxFromBatch(remote.channel.inbox, batch);
274
+ }
275
+ // finally, work through the affected remotes and backfill based on the last operation in the outbox
276
+ for (const remote of affectedRemotes) {
277
+ await this.updateOutbox(remote, remote.channel.outbox.latestOrdinal);
278
+ }
210
279
  }
211
- handleInboxJob(remote, syncOp) {
280
+ handleInboxAdded(remote, syncOps) {
212
281
  if (this.isShutdown) {
213
282
  return;
214
283
  }
215
- void this.applyInboxJob(remote, syncOp);
216
- }
217
- handleOutboxJob(remote, syncOp) {
218
- syncOp.on((syncOp, _prev, next) => {
219
- if (next === SyncOperationStatus.Applied) {
220
- remote.channel.outbox.remove(syncOp);
284
+ const keyed = [];
285
+ const nonKeyed = [];
286
+ for (const syncOp of syncOps) {
287
+ if (syncOp.jobId) {
288
+ keyed.push(syncOp);
221
289
  }
222
- else if (next === SyncOperationStatus.Error) {
223
- remote.channel.outbox.remove(syncOp);
290
+ else {
291
+ nonKeyed.push(syncOp);
224
292
  }
225
- });
293
+ }
294
+ for (const syncOp of nonKeyed) {
295
+ void this.applyInboxJob(remote, syncOp);
296
+ }
297
+ if (keyed.length > 0) {
298
+ void this.applyInboxBatch(keyed.map((syncOp) => ({ remote, syncOp })));
299
+ }
226
300
  }
227
301
  async applyInboxJob(remote, syncOp) {
228
302
  const operations = syncOp.operations.map((op) => op.operation);
@@ -232,6 +306,7 @@ export class SyncManager {
232
306
  }
233
307
  catch (error) {
234
308
  const err = error instanceof Error ? error : new Error(String(error));
309
+ this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
235
310
  const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
236
311
  syncOp.failed(channelError);
237
312
  remote.channel.deadLetter.add(syncOp);
@@ -244,6 +319,7 @@ export class SyncManager {
244
319
  }
245
320
  catch (error) {
246
321
  const err = error instanceof Error ? error : new Error(String(error));
322
+ this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
247
323
  const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
248
324
  syncOp.failed(channelError);
249
325
  remote.channel.deadLetter.add(syncOp);
@@ -253,7 +329,9 @@ export class SyncManager {
253
329
  const jobKey = `${syncOp.documentId}:${syncOp.branch}`;
254
330
  this.loadJobs.set(jobKey, completedJobInfo);
255
331
  if (completedJobInfo.status === JobStatus.FAILED) {
256
- const error = new ChannelError(ChannelErrorSource.Inbox, new Error(`Failed to apply operations: ${completedJobInfo.error?.message || "Unknown error"}`));
332
+ const errorMessage = completedJobInfo.error?.message || "Unknown error";
333
+ this.logger.error("Failed to apply operations from inbox (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, completedJobInfo.id, errorMessage);
334
+ const error = new ChannelError(ChannelErrorSource.Inbox, new Error(`Failed to apply operations: ${errorMessage}`));
257
335
  syncOp.failed(error);
258
336
  remote.channel.deadLetter.add(syncOp);
259
337
  }
@@ -262,5 +340,108 @@ export class SyncManager {
262
340
  }
263
341
  remote.channel.inbox.remove(syncOp);
264
342
  }
343
+ async applyInboxBatch(items) {
344
+ const sourceRemote = items[0].remote.name;
345
+ const jobs = items.map(({ syncOp }) => ({
346
+ key: syncOp.jobId,
347
+ documentId: syncOp.documentId,
348
+ scope: syncOp.scopes[0],
349
+ branch: syncOp.branch,
350
+ operations: syncOp.operations.map((op) => op.operation),
351
+ dependsOn: syncOp.jobDependencies.filter(Boolean),
352
+ }));
353
+ const request = { jobs };
354
+ let result;
355
+ try {
356
+ result = await this.reactor.loadBatch(request, undefined, {
357
+ sourceRemote,
358
+ });
359
+ }
360
+ catch (error) {
361
+ for (const { remote, syncOp } of items) {
362
+ const err = error instanceof Error ? error : new Error(String(error));
363
+ syncOp.failed(new ChannelError(ChannelErrorSource.Inbox, err));
364
+ remote.channel.deadLetter.add(syncOp);
365
+ remote.channel.inbox.remove(syncOp);
366
+ }
367
+ return;
368
+ }
369
+ for (const { remote, syncOp } of items) {
370
+ if (!(syncOp.jobId in result.jobs)) {
371
+ this.logger.error("Job key missing from batch load result (@remote, @documentId, @jobId)", remote.name, syncOp.documentId, syncOp.jobId);
372
+ const error = new ChannelError(ChannelErrorSource.Inbox, new Error(`Job key '${syncOp.jobId}' missing from batch load result`));
373
+ syncOp.failed(error);
374
+ remote.channel.deadLetter.add(syncOp);
375
+ remote.channel.inbox.remove(syncOp);
376
+ continue;
377
+ }
378
+ const jobInfo = result.jobs[syncOp.jobId];
379
+ let completedJobInfo;
380
+ try {
381
+ completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
382
+ }
383
+ catch (error) {
384
+ const err = error instanceof Error ? error : new Error(String(error));
385
+ syncOp.failed(new ChannelError(ChannelErrorSource.Inbox, err));
386
+ remote.channel.deadLetter.add(syncOp);
387
+ remote.channel.inbox.remove(syncOp);
388
+ continue;
389
+ }
390
+ const jobKey = `${syncOp.documentId}:${syncOp.branch}`;
391
+ this.loadJobs.set(jobKey, completedJobInfo);
392
+ if (completedJobInfo.status === JobStatus.FAILED) {
393
+ const errorMessage = completedJobInfo.error?.message || "Unknown error";
394
+ const channelError = new ChannelError(ChannelErrorSource.Inbox, new Error(`Failed to apply operations: ${errorMessage}`));
395
+ syncOp.failed(channelError);
396
+ remote.channel.deadLetter.add(syncOp);
397
+ }
398
+ else {
399
+ syncOp.executed();
400
+ }
401
+ remote.channel.inbox.remove(syncOp);
402
+ }
403
+ }
404
+ async updateOutbox(remote, ackOrdinal) {
405
+ const operations = await this.getOperationsForRemote(remote, ackOrdinal);
406
+ if (operations.length === 0) {
407
+ return;
408
+ }
409
+ // sort by (documentId, scope, ordinal) so batchOperationsByDocument
410
+ // groups all operations for the same document together
411
+ operations.sort((a, b) => {
412
+ if (a.context.documentId !== b.context.documentId) {
413
+ return a.context.documentId < b.context.documentId ? -1 : 1;
414
+ }
415
+ if (a.context.scope !== b.context.scope) {
416
+ return a.context.scope < b.context.scope ? -1 : 1;
417
+ }
418
+ return a.context.ordinal - b.context.ordinal;
419
+ });
420
+ const batches = batchOperationsByDocument(operations);
421
+ // per-document dependency chain: each batch depends on the previous
422
+ // batch for the same documentId only, allowing independent documents
423
+ // to be processed in parallel
424
+ const lastJobByDoc = new Map();
425
+ const syncOps = [];
426
+ for (const batch of batches) {
427
+ const jobId = crypto.randomUUID();
428
+ const prevJobId = lastJobByDoc.get(batch.documentId);
429
+ const syncOp = new SyncOperation(crypto.randomUUID(), jobId, prevJobId ? [prevJobId] : [], remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
430
+ syncOps.push(syncOp);
431
+ lastJobByDoc.set(batch.documentId, jobId);
432
+ }
433
+ remote.channel.outbox.add(...syncOps);
434
+ }
435
+ async getOperationsForRemote(remote, ackOrdinal) {
436
+ const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
437
+ let operations = results.results.map((entry) => toOperationWithContext(entry));
438
+ // apply the sinceTimestampUtcMs filter
439
+ const sinceTimestamp = remote.options.sinceTimestampUtcMs;
440
+ if (sinceTimestamp && sinceTimestamp !== "0") {
441
+ operations = operations.filter((op) => op.operation.timestampUtcMs >= sinceTimestamp);
442
+ }
443
+ // apply the remote filter
444
+ return filterOperations(operations, remote.filter);
445
+ }
265
446
  }
266
447
  //# sourceMappingURL=sync-manager.js.map