@sixb/core 0.1.9 → 0.1.11

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 (328) hide show
  1. package/dist/actions/index.js +9 -9
  2. package/dist/agents/index.js +17 -16
  3. package/dist/agents/index.js.map +1 -1
  4. package/dist/blob-storage/types.d.ts +4 -1
  5. package/dist/blob-storage/types.d.ts.map +1 -1
  6. package/dist/bootstrap/index.js +21 -20
  7. package/dist/bootstrap/index.js.map +1 -1
  8. package/dist/broker/in-memory.d.ts +8 -2
  9. package/dist/broker/in-memory.d.ts.map +1 -1
  10. package/dist/broker/index.d.ts +4 -2
  11. package/dist/broker/index.d.ts.map +1 -1
  12. package/dist/broker/index.js +7 -3
  13. package/dist/broker/index.js.map +1 -1
  14. package/dist/broker/retention.d.ts +4 -0
  15. package/dist/broker/retention.d.ts.map +1 -0
  16. package/dist/broker/subscriber.d.ts +10 -0
  17. package/dist/broker/subscriber.d.ts.map +1 -0
  18. package/dist/broker/types.d.ts +14 -1
  19. package/dist/broker/types.d.ts.map +1 -1
  20. package/dist/{chunk-ypbtykv6.js → chunk-01kj2pbh.js} +1 -1
  21. package/dist/{chunk-k3cqkkn2.js → chunk-1824gssz.js} +30 -65
  22. package/dist/chunk-1824gssz.js.map +12 -0
  23. package/dist/{chunk-xyr8kppc.js → chunk-1yefycb1.js} +1 -1
  24. package/dist/chunk-2c4j899j.js.map +1 -1
  25. package/dist/{chunk-ppytyr6w.js → chunk-358caqf5.js} +4 -4
  26. package/dist/chunk-358caqf5.js.map +12 -0
  27. package/dist/{chunk-m3z3x9v0.js → chunk-3ckk0xfj.js} +2 -2
  28. package/dist/{chunk-kxwa347f.js → chunk-3qms4nax.js} +2 -2
  29. package/dist/{chunk-kxwa347f.js.map → chunk-3qms4nax.js.map} +3 -3
  30. package/dist/{chunk-52hbv0ak.js → chunk-4zhmdsx6.js} +141 -55
  31. package/dist/chunk-4zhmdsx6.js.map +36 -0
  32. package/dist/{chunk-zcnq6105.js → chunk-85cd7e8c.js} +2 -1
  33. package/dist/chunk-85cd7e8c.js.map +10 -0
  34. package/dist/{chunk-3f7veca0.js → chunk-9x8dybsb.js} +3 -3
  35. package/dist/chunk-9x8dybsb.js.map +10 -0
  36. package/dist/{chunk-0w35wavj.js → chunk-a0e6x5hd.js} +1 -1
  37. package/dist/{chunk-r29fxdhn.js → chunk-aa6z4a19.js} +160 -44
  38. package/dist/chunk-aa6z4a19.js.map +12 -0
  39. package/dist/{chunk-emyyk7dx.js → chunk-cfvakeax.js} +22 -145
  40. package/dist/chunk-cfvakeax.js.map +12 -0
  41. package/dist/chunk-d62kjtbg.js.map +1 -1
  42. package/dist/{chunk-h1gfhhbh.js → chunk-d63nnr5b.js} +2 -2
  43. package/dist/{chunk-r4tfx1sf.js → chunk-fh8kgex1.js} +5 -5
  44. package/dist/{chunk-an5tt9y0.js → chunk-ftv470j8.js} +42 -4
  45. package/dist/{chunk-an5tt9y0.js.map → chunk-ftv470j8.js.map} +7 -7
  46. package/dist/{chunk-jjy0fhrn.js → chunk-g91qxjj0.js} +20 -3
  47. package/dist/chunk-g91qxjj0.js.map +12 -0
  48. package/dist/{chunk-hpr1r6av.js → chunk-gq8vhakp.js} +51 -40
  49. package/dist/{chunk-hpr1r6av.js.map → chunk-gq8vhakp.js.map} +6 -6
  50. package/dist/{chunk-x57ycxq0.js → chunk-gqh45d2n.js} +42 -3
  51. package/dist/{chunk-x57ycxq0.js.map → chunk-gqh45d2n.js.map} +3 -3
  52. package/dist/{chunk-mjbk2djy.js → chunk-gqm76kse.js} +231 -125
  53. package/dist/{chunk-mjbk2djy.js.map → chunk-gqm76kse.js.map} +9 -8
  54. package/dist/{chunk-e0d1rzef.js → chunk-jsy70480.js} +16 -3
  55. package/dist/chunk-jsy70480.js.map +15 -0
  56. package/dist/{chunk-0rdrecsb.js → chunk-kfee1h52.js} +116 -32
  57. package/dist/chunk-kfee1h52.js.map +13 -0
  58. package/dist/{chunk-3frsnm7x.js → chunk-qma80ych.js} +4 -6
  59. package/dist/chunk-qma80ych.js.map +11 -0
  60. package/dist/{chunk-vf691g1r.js → chunk-sepyxgpc.js} +3 -3
  61. package/dist/chunk-v2vcg0w6.js +151 -0
  62. package/dist/chunk-v2vcg0w6.js.map +11 -0
  63. package/dist/{chunk-3tgwrcyh.js → chunk-wf88w4kp.js} +17 -23
  64. package/dist/chunk-wf88w4kp.js.map +14 -0
  65. package/dist/{chunk-xfbydgzy.js → chunk-x9m9x2eq.js} +7 -8
  66. package/dist/{chunk-xfbydgzy.js.map → chunk-x9m9x2eq.js.map} +3 -3
  67. package/dist/{chunk-qhcpj9wc.js → chunk-z0271qkp.js} +98 -2
  68. package/dist/chunk-z0271qkp.js.map +15 -0
  69. package/dist/{chunk-1enhpacz.js → chunk-z0s4axeh.js} +6 -6
  70. package/dist/connectors/connections/authorizations.d.ts.map +1 -1
  71. package/dist/connectors/connections/command-context.d.ts +2 -0
  72. package/dist/connectors/connections/command-context.d.ts.map +1 -1
  73. package/dist/connectors/connections/contracts.d.ts +4 -1
  74. package/dist/connectors/connections/contracts.d.ts.map +1 -1
  75. package/dist/connectors/connections/execution.d.ts +1 -0
  76. package/dist/connectors/connections/execution.d.ts.map +1 -1
  77. package/dist/connectors/connections/internal.js +1 -1
  78. package/dist/connectors/connections/request.d.ts +3 -1
  79. package/dist/connectors/connections/request.d.ts.map +1 -1
  80. package/dist/connectors/connections/runs.d.ts +1 -0
  81. package/dist/connectors/connections/runs.d.ts.map +1 -1
  82. package/dist/connectors/connections/service.d.ts +2 -0
  83. package/dist/connectors/connections/service.d.ts.map +1 -1
  84. package/dist/connectors/connections/tokens.d.ts.map +1 -1
  85. package/dist/connectors/connections/validation.d.ts +5 -1
  86. package/dist/connectors/connections/validation.d.ts.map +1 -1
  87. package/dist/connectors/types.d.ts +19 -4
  88. package/dist/connectors/types.d.ts.map +1 -1
  89. package/dist/events/execution.d.ts +1 -1
  90. package/dist/events/execution.d.ts.map +1 -1
  91. package/dist/events/index.js +1 -1
  92. package/dist/events/outbox-dispatcher.d.ts +2 -2
  93. package/dist/events/outbox-dispatcher.d.ts.map +1 -1
  94. package/dist/events/service.d.ts +2 -2
  95. package/dist/events/service.d.ts.map +1 -1
  96. package/dist/execution/agent.js +17 -16
  97. package/dist/execution/agent.js.map +1 -1
  98. package/dist/execution/primitive.js +17 -16
  99. package/dist/execution/primitive.js.map +2 -2
  100. package/dist/execution/request.js +17 -16
  101. package/dist/execution/request.js.map +2 -2
  102. package/dist/index.d.ts +3 -3
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +31 -29
  105. package/dist/index.js.map +1 -1
  106. package/dist/lake-storage/changes.d.ts +5 -0
  107. package/dist/lake-storage/changes.d.ts.map +1 -0
  108. package/dist/lake-storage/in-memory.d.ts +2 -1
  109. package/dist/lake-storage/in-memory.d.ts.map +1 -1
  110. package/dist/lake-storage/index.d.ts +2 -1
  111. package/dist/lake-storage/index.d.ts.map +1 -1
  112. package/dist/lake-storage/index.js +4 -2
  113. package/dist/lake-storage/index.js.map +1 -1
  114. package/dist/lake-storage/types.d.ts +38 -0
  115. package/dist/lake-storage/types.d.ts.map +1 -1
  116. package/dist/logging/internal.js +1 -1
  117. package/dist/maintenance/ontology-maintenance.d.ts.map +1 -1
  118. package/dist/maintenance/types.d.ts +1 -1
  119. package/dist/maintenance/types.d.ts.map +1 -1
  120. package/dist/materialization/index.d.ts +1 -1
  121. package/dist/materialization/index.d.ts.map +1 -1
  122. package/dist/materialization/model.d.ts +12 -1
  123. package/dist/materialization/model.d.ts.map +1 -1
  124. package/dist/materializer/index.d.ts +1 -1
  125. package/dist/materializer/index.d.ts.map +1 -1
  126. package/dist/materializer/index.js +6 -6
  127. package/dist/materializer/materializer.d.ts +5 -1
  128. package/dist/materializer/materializer.d.ts.map +1 -1
  129. package/dist/materializer/projections/entry-validator.d.ts +2 -1
  130. package/dist/materializer/projections/entry-validator.d.ts.map +1 -1
  131. package/dist/materializer/projections/replace.d.ts.map +1 -1
  132. package/dist/materializer/projections/source-materialization.d.ts +4 -3
  133. package/dist/materializer/projections/source-materialization.d.ts.map +1 -1
  134. package/dist/materializer/shared/normalize.d.ts +2 -1
  135. package/dist/materializer/shared/normalize.d.ts.map +1 -1
  136. package/dist/models/definitions.d.ts +7 -0
  137. package/dist/models/definitions.d.ts.map +1 -1
  138. package/dist/models/execution/index.js +6 -6
  139. package/dist/models/index.js +1 -1
  140. package/dist/objects/index.js +10 -10
  141. package/dist/objects/query/index.js +6 -6
  142. package/dist/pipelines/index.js +7 -7
  143. package/dist/pipelines/run-dispatch.d.ts.map +1 -1
  144. package/dist/projections/internal.js +7 -9
  145. package/dist/projections/internal.js.map +1 -1
  146. package/dist/projections/run-dispatch.d.ts.map +1 -1
  147. package/dist/queues/in-memory.d.ts +7 -3
  148. package/dist/queues/in-memory.d.ts.map +1 -1
  149. package/dist/queues/index.js +1 -1
  150. package/dist/queues/indexed-heap.d.ts +15 -0
  151. package/dist/queues/indexed-heap.d.ts.map +1 -0
  152. package/dist/runtime/host.d.ts.map +1 -1
  153. package/dist/runtime/internal.js +1 -1
  154. package/dist/runtime/ontology-mutations.d.ts +3 -1
  155. package/dist/runtime/ontology-mutations.d.ts.map +1 -1
  156. package/dist/shares/internal.js +5 -5
  157. package/dist/storage/agents/provider.js +1 -1
  158. package/dist/storage/connector-connections/in-memory.d.ts +1 -0
  159. package/dist/storage/connector-connections/in-memory.d.ts.map +1 -1
  160. package/dist/storage/connector-connections/provider/connections.d.ts.map +1 -1
  161. package/dist/storage/connector-connections/provider/runs.d.ts +1 -0
  162. package/dist/storage/connector-connections/provider/runs.d.ts.map +1 -1
  163. package/dist/storage/connector-connections/provider/shared.d.ts +1 -0
  164. package/dist/storage/connector-connections/provider/shared.d.ts.map +1 -1
  165. package/dist/storage/connector-connections/provider.d.ts +1 -0
  166. package/dist/storage/connector-connections/provider.d.ts.map +1 -1
  167. package/dist/storage/connector-connections/provider.js +11 -17
  168. package/dist/storage/connector-connections/provider.js.map +6 -6
  169. package/dist/storage/connector-connections/types.d.ts +6 -3
  170. package/dist/storage/connector-connections/types.d.ts.map +1 -1
  171. package/dist/storage/index.d.ts +1 -1
  172. package/dist/storage/index.d.ts.map +1 -1
  173. package/dist/storage/index.js +5 -5
  174. package/dist/storage/internal.js +1 -1
  175. package/dist/storage/ontology/in-memory/materializations-state.d.ts.map +1 -1
  176. package/dist/storage/ontology/in-memory/materializations.d.ts +1 -1
  177. package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
  178. package/dist/storage/ontology/in-memory/shared-state.d.ts +15 -2
  179. package/dist/storage/ontology/in-memory/shared-state.d.ts.map +1 -1
  180. package/dist/storage/ontology/in-memory/source-roots.d.ts +6 -0
  181. package/dist/storage/ontology/in-memory/source-roots.d.ts.map +1 -0
  182. package/dist/storage/ontology/in-memory/sources.d.ts.map +1 -1
  183. package/dist/storage/ontology/index.d.ts +1 -1
  184. package/dist/storage/ontology/index.d.ts.map +1 -1
  185. package/dist/storage/ontology/outbox.d.ts +1 -0
  186. package/dist/storage/ontology/outbox.d.ts.map +1 -1
  187. package/dist/storage/ontology/provider.d.ts +16 -0
  188. package/dist/storage/ontology/provider.d.ts.map +1 -1
  189. package/dist/storage/ontology/provider.js +4 -2
  190. package/dist/storage/ontology/provider.js.map +1 -1
  191. package/dist/storage/ontology/sources.d.ts +13 -4
  192. package/dist/storage/ontology/sources.d.ts.map +1 -1
  193. package/dist/storage/projection-runs/lifecycle.d.ts.map +1 -1
  194. package/dist/storage/projection-runs/provider.js +2 -2
  195. package/dist/storage/projection-runs/types.d.ts +2 -0
  196. package/dist/storage/projection-runs/types.d.ts.map +1 -1
  197. package/dist/syncs/index.js +9 -8
  198. package/dist/syncs/index.js.map +1 -1
  199. package/dist/syncs/run-dispatch.d.ts.map +1 -1
  200. package/dist/testing/broker-contract.d.ts.map +1 -1
  201. package/dist/testing/connector-connection-storage-contract.d.ts.map +1 -1
  202. package/dist/testing/index.d.ts +1 -0
  203. package/dist/testing/index.d.ts.map +1 -1
  204. package/dist/testing/index.js +1499 -963
  205. package/dist/testing/index.js.map +10 -9
  206. package/dist/testing/lake-changes-contract.d.ts +6 -0
  207. package/dist/testing/lake-changes-contract.d.ts.map +1 -0
  208. package/dist/testing/lake-storage-contract.d.ts.map +1 -1
  209. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  210. package/dist/testing/ontology-storage-contract.d.ts.map +1 -1
  211. package/dist/testing/projection-run-storage-contract.d.ts.map +1 -1
  212. package/dist/webhooks/connections.d.ts +6 -0
  213. package/dist/webhooks/connections.d.ts.map +1 -0
  214. package/dist/webhooks/index.d.ts +1 -1
  215. package/dist/webhooks/index.d.ts.map +1 -1
  216. package/dist/webhooks/internal.d.ts +2 -0
  217. package/dist/webhooks/internal.d.ts.map +1 -1
  218. package/dist/webhooks/internal.js +55 -10
  219. package/dist/webhooks/internal.js.map +4 -3
  220. package/dist/webhooks/registry.d.ts +1 -1
  221. package/dist/webhooks/registry.d.ts.map +1 -1
  222. package/dist/webhooks/types.d.ts +15 -4
  223. package/dist/webhooks/types.d.ts.map +1 -1
  224. package/dist/workflows/index.js +18 -17
  225. package/dist/workflows/index.js.map +1 -1
  226. package/dist/workflows/run-dispatch.d.ts +3 -1
  227. package/dist/workflows/run-dispatch.d.ts.map +1 -1
  228. package/package.json +1 -1
  229. package/src/agents/model-loop.ts +7 -0
  230. package/src/blob-storage/types.ts +4 -1
  231. package/src/broker/in-memory.ts +104 -39
  232. package/src/broker/index.ts +4 -1
  233. package/src/broker/retention.ts +28 -0
  234. package/src/broker/subscriber.ts +21 -0
  235. package/src/broker/types.ts +15 -1
  236. package/src/connectors/builders.ts +0 -6
  237. package/src/connectors/connections/authorizations.ts +11 -5
  238. package/src/connectors/connections/command-context.ts +21 -4
  239. package/src/connectors/connections/contracts.ts +7 -1
  240. package/src/connectors/connections/execution.ts +3 -0
  241. package/src/connectors/connections/request.ts +56 -22
  242. package/src/connectors/connections/runs.ts +39 -1
  243. package/src/connectors/connections/service.ts +6 -0
  244. package/src/connectors/connections/tokens.ts +4 -0
  245. package/src/connectors/connections/validation.ts +91 -5
  246. package/src/connectors/types.ts +19 -4
  247. package/src/events/execution.ts +2 -2
  248. package/src/events/outbox-dispatcher.ts +20 -26
  249. package/src/events/service.ts +3 -3
  250. package/src/index.ts +10 -2
  251. package/src/lake-storage/changes.ts +43 -0
  252. package/src/lake-storage/in-memory.ts +75 -0
  253. package/src/lake-storage/index.ts +4 -0
  254. package/src/lake-storage/types.ts +42 -0
  255. package/src/logging/service.ts +1 -1
  256. package/src/maintenance/ontology-maintenance.ts +6 -2
  257. package/src/maintenance/types.ts +1 -1
  258. package/src/materialization/index.ts +2 -0
  259. package/src/materialization/model.ts +14 -1
  260. package/src/materializer/README.md +1 -1
  261. package/src/materializer/index.ts +2 -0
  262. package/src/materializer/materializer.ts +10 -0
  263. package/src/materializer/projections/entry-validator.ts +19 -1
  264. package/src/materializer/projections/replace.ts +36 -1
  265. package/src/materializer/projections/source-materialization.ts +26 -6
  266. package/src/materializer/shared/normalize.ts +1 -1
  267. package/src/models/definitions.ts +19 -0
  268. package/src/models/pricing.ts +6 -2
  269. package/src/pipelines/run-dispatch.ts +21 -1
  270. package/src/projections/run-dispatch.ts +30 -68
  271. package/src/queues/in-memory.ts +105 -62
  272. package/src/queues/indexed-heap.ts +74 -0
  273. package/src/runtime/host.ts +0 -14
  274. package/src/runtime/ontology-mutations.ts +4 -0
  275. package/src/storage/connector-connections/in-memory.ts +19 -19
  276. package/src/storage/connector-connections/provider/connections.ts +1 -15
  277. package/src/storage/connector-connections/provider/runs.ts +9 -1
  278. package/src/storage/connector-connections/provider/shared.ts +2 -3
  279. package/src/storage/connector-connections/provider.ts +6 -0
  280. package/src/storage/connector-connections/types.ts +8 -3
  281. package/src/storage/index.ts +1 -0
  282. package/src/storage/ontology/in-memory/materializations-state.ts +5 -18
  283. package/src/storage/ontology/in-memory/materializations.ts +73 -92
  284. package/src/storage/ontology/in-memory/shared-state.ts +36 -4
  285. package/src/storage/ontology/in-memory/source-roots.ts +123 -0
  286. package/src/storage/ontology/in-memory/sources.ts +78 -23
  287. package/src/storage/ontology/index.ts +1 -0
  288. package/src/storage/ontology/outbox.ts +1 -0
  289. package/src/storage/ontology/provider.ts +70 -0
  290. package/src/storage/ontology/sources.ts +14 -3
  291. package/src/storage/projection-runs/lifecycle.ts +5 -2
  292. package/src/storage/projection-runs/types.ts +4 -3
  293. package/src/syncs/run-dispatch.ts +19 -1
  294. package/src/testing/broker-contract.ts +95 -0
  295. package/src/testing/connector-connection-storage-contract.ts +29 -12
  296. package/src/testing/index.ts +1 -0
  297. package/src/testing/lake-changes-contract.ts +240 -0
  298. package/src/testing/lake-storage-contract.ts +29 -0
  299. package/src/testing/materializer-storage-contract.ts +172 -1
  300. package/src/testing/ontology-storage-contract.ts +15 -3
  301. package/src/testing/projection-run-storage-contract.ts +34 -0
  302. package/src/webhooks/connections.ts +56 -0
  303. package/src/webhooks/index.ts +2 -0
  304. package/src/webhooks/internal.ts +2 -0
  305. package/src/webhooks/registry.ts +3 -4
  306. package/src/webhooks/types.ts +17 -4
  307. package/src/workflows/run-dispatch.ts +42 -29
  308. package/dist/chunk-0rdrecsb.js.map +0 -11
  309. package/dist/chunk-3f7veca0.js.map +0 -10
  310. package/dist/chunk-3frsnm7x.js.map +0 -11
  311. package/dist/chunk-3tgwrcyh.js.map +0 -14
  312. package/dist/chunk-52hbv0ak.js.map +0 -36
  313. package/dist/chunk-e0d1rzef.js.map +0 -15
  314. package/dist/chunk-emyyk7dx.js.map +0 -14
  315. package/dist/chunk-jjy0fhrn.js.map +0 -12
  316. package/dist/chunk-k3cqkkn2.js.map +0 -12
  317. package/dist/chunk-ppytyr6w.js.map +0 -12
  318. package/dist/chunk-qhcpj9wc.js.map +0 -14
  319. package/dist/chunk-r29fxdhn.js.map +0 -11
  320. package/dist/chunk-zcnq6105.js.map +0 -10
  321. /package/dist/{chunk-ypbtykv6.js.map → chunk-01kj2pbh.js.map} +0 -0
  322. /package/dist/{chunk-xyr8kppc.js.map → chunk-1yefycb1.js.map} +0 -0
  323. /package/dist/{chunk-m3z3x9v0.js.map → chunk-3ckk0xfj.js.map} +0 -0
  324. /package/dist/{chunk-0w35wavj.js.map → chunk-a0e6x5hd.js.map} +0 -0
  325. /package/dist/{chunk-h1gfhhbh.js.map → chunk-d63nnr5b.js.map} +0 -0
  326. /package/dist/{chunk-r4tfx1sf.js.map → chunk-fh8kgex1.js.map} +0 -0
  327. /package/dist/{chunk-vf691g1r.js.map → chunk-sepyxgpc.js.map} +0 -0
  328. /package/dist/{chunk-1enhpacz.js.map → chunk-z0s4axeh.js.map} +0 -0
@@ -16,6 +16,7 @@ import {
16
16
  } from "./authorizations"
17
17
  import {
18
18
  assertConnectorConnectionRunInitiator,
19
+ isConnectorConnectionRunInitiator,
19
20
  requireConnectorConnectionCommandActor,
20
21
  } from "./command-context"
21
22
  import type {
@@ -207,6 +208,7 @@ export class ConnectorConnectionRunService {
207
208
  attempt,
208
209
  principal,
209
210
  code: nonblank(input.code, "OAuth authorization code", "connector.authorization_invalid"),
211
+ callbackParameters: input.callbackParameters,
210
212
  redirectUri,
211
213
  onAuthorizationPersisted: async (persisted) => {
212
214
  authorizationId = persisted.id
@@ -237,6 +239,42 @@ export class ConnectorConnectionRunService {
237
239
  }
238
240
  }
239
241
 
242
+ async listPending(
243
+ command: ConnectorConnectionCommandContext,
244
+ connectorId: string
245
+ ): Promise<readonly ConnectorConnectionRunView[]> {
246
+ const actor = requireConnectorConnectionCommandActor(command.execution, this.projectId)
247
+ const candidates = await withConnectorStorageBoundary(
248
+ "Pending connector connection runs could not be read.",
249
+ () =>
250
+ this.connectionStorage.listPendingConnectionRuns({ projectId: this.projectId, connectorId })
251
+ )
252
+ const runs: ConnectorConnectionRunView[] = []
253
+ for (const candidate of candidates) {
254
+ const initiating = await withConnectorStorageBoundary(
255
+ "Connector connection run initiating execution could not be read.",
256
+ () =>
257
+ this.storage.executions.getById({
258
+ projectId: this.projectId,
259
+ id: candidate.initiatedByExecutionId,
260
+ })
261
+ )
262
+ if (!isConnectorConnectionRunInitiator(initiating, actor)) continue
263
+ // Re-read through the normal expiry/cleanup path before exposing accounts.
264
+ const run = await this.get(command, connectorId, candidate.id)
265
+ if (
266
+ run &&
267
+ (run.status === "running" ||
268
+ (run.status === "waiting" && run.waitingFor === "account_selection"))
269
+ )
270
+ runs.push(run)
271
+ }
272
+ return runs.sort(
273
+ (left, right) =>
274
+ right.createdAt.getTime() - left.createdAt.getTime() || left.id.localeCompare(right.id)
275
+ )
276
+ }
277
+
240
278
  async get(
241
279
  command: ConnectorConnectionCommandContext,
242
280
  connectorId: string,
@@ -580,7 +618,6 @@ export class ConnectorConnectionRunService {
580
618
  status: "waiting",
581
619
  waitingFor: "account_selection",
582
620
  accounts: authorization.accounts,
583
- expiresAt: run.expiresAt,
584
621
  }
585
622
  }
586
623
  if (run.status === "succeeded") {
@@ -644,6 +681,7 @@ export class ConnectorConnectionRunService {
644
681
  : CLEANUP_RETRY_DELAY_MS
645
682
  ): void {
646
683
  this.cancelRunMaintenance(run.id)
684
+ if (run.status === "waiting" && run.waitingFor === "account_selection") return
647
685
  const timer = setTimeout(
648
686
  () => void this.maintainRun(run.connectorId, run.id),
649
687
  Math.min(delayMs, MAX_TIMER_DELAY_MS)
@@ -66,6 +66,7 @@ export interface ConnectorConnectionServiceOptions {
66
66
  readonly storage?: Storage
67
67
  readonly credentialProtector?: ConnectorCredentialProtector
68
68
  readonly authorizationAttemptTtlMs?: number
69
+ /** @deprecated Selection no longer expires; retained for legacy storage metadata only. */
69
70
  readonly accountSelectionTtlMs?: number
70
71
  readonly credentialMutationLeaseMs?: number
71
72
  readonly providerOperationTimeoutMs?: number
@@ -322,6 +323,11 @@ export class ConnectorConnectionService implements ConnectorConnectionProcess {
322
323
  })
323
324
  }
324
325
 
326
+ listPendingConnectionRuns(command: ConnectorConnectionCommandContext, connectorId: string) {
327
+ this.requireOAuthDefinition(connectorId)
328
+ return this.runs.listPending(command, connectorId)
329
+ }
330
+
325
331
  getConnectionRun(
326
332
  command: ConnectorConnectionCommandContext,
327
333
  connectorId: string,
@@ -169,6 +169,10 @@ export class ConnectorTokenAccess {
169
169
  ? { tokenType: current.tokenType }
170
170
  : {}),
171
171
  scopes: refreshed.scopes ?? current.scopes ?? authorization.scopes,
172
+ ...(refreshed.authorizationContext === undefined &&
173
+ current.authorizationContext !== undefined
174
+ ? { authorizationContext: current.authorizationContext }
175
+ : {}),
172
176
  } satisfies ConnectorOAuthCredentials
173
177
  const envelope = await this.credentials.seal(
174
178
  definition.id,
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto"
2
+ import { isJsonObject } from "../../json"
2
3
  import type { ConnectorAuthorizationRecord } from "../../storage"
3
4
  import { createConnectorCodedError } from "../errors"
4
5
  import type { ConnectorAccountCandidate, ConnectorOAuthCredentials } from "../types"
@@ -26,6 +27,7 @@ export function validateCredentials(
26
27
  tokenType?: string
27
28
  scopes?: string[]
28
29
  expiresAt?: Date
30
+ authorizationContext?: ConnectorOAuthCredentials["authorizationContext"]
29
31
  } = {
30
32
  accessToken: nonblank(
31
33
  credentials.accessToken,
@@ -76,6 +78,16 @@ export function validateCredentials(
76
78
  normalized.expiresAt = new Date(credentials.expiresAt)
77
79
  }
78
80
 
81
+ if (credentials.authorizationContext !== undefined) {
82
+ if (!isJsonObject(credentials.authorizationContext)) {
83
+ throw createConnectorCodedError(
84
+ "connector.adapter_invalid",
85
+ "OAuth connector adapter returned invalid authorization context; expected a JSON object."
86
+ )
87
+ }
88
+ normalized.authorizationContext = structuredClone(credentials.authorizationContext)
89
+ }
90
+
79
91
  return normalized
80
92
  }
81
93
 
@@ -135,6 +147,9 @@ export function serializeCredentials(credentials: ConnectorOAuthCredentials): st
135
147
  ...(credentials.expiresAt === undefined
136
148
  ? {}
137
149
  : { expiresAt: credentials.expiresAt.toISOString() }),
150
+ ...(credentials.authorizationContext === undefined
151
+ ? {}
152
+ : { authorizationContext: credentials.authorizationContext }),
138
153
  })
139
154
  }
140
155
 
@@ -160,7 +175,8 @@ export function parseCredentials(serialized: string): ConnectorOAuthCredentials
160
175
  (value.tokenType !== undefined && typeof value.tokenType !== "string") ||
161
176
  (value.scopes !== undefined &&
162
177
  (!Array.isArray(value.scopes) || value.scopes.some((scope) => typeof scope !== "string"))) ||
163
- (value.expiresAt !== undefined && typeof value.expiresAt !== "string")
178
+ (value.expiresAt !== undefined && typeof value.expiresAt !== "string") ||
179
+ (value.authorizationContext !== undefined && !isJsonObject(value.authorizationContext))
164
180
  ) {
165
181
  throw createConnectorCodedError(
166
182
  "connector.credentials_unavailable",
@@ -175,6 +191,9 @@ export function parseCredentials(serialized: string): ConnectorOAuthCredentials
175
191
  ...(value.tokenType === undefined ? {} : { tokenType: value.tokenType }),
176
192
  ...(value.scopes === undefined ? {} : { scopes: value.scopes as string[] }),
177
193
  ...(expiresAt === undefined ? {} : { expiresAt }),
194
+ ...(value.authorizationContext === undefined
195
+ ? {}
196
+ : { authorizationContext: value.authorizationContext }),
178
197
  })
179
198
  } catch (error) {
180
199
  throw createConnectorCodedError(
@@ -238,21 +257,88 @@ export function normalizedHttpUrl(
238
257
 
239
258
  export function assertAuthorizationUrlParameters(
240
259
  authorizationUrl: string,
241
- expected: { readonly state: string; readonly codeChallenge: string }
260
+ expected: { readonly state: string; readonly codeChallenge?: string }
242
261
  ): void {
243
262
  const parameters = new URL(authorizationUrl).searchParams
244
263
  if (
245
264
  !sameSingleParameter(parameters, "state", expected.state) ||
246
- !sameSingleParameter(parameters, "code_challenge", expected.codeChallenge) ||
247
- !sameSingleParameter(parameters, "code_challenge_method", "S256")
265
+ (expected.codeChallenge === undefined
266
+ ? parameters.has("code_challenge") || parameters.has("code_challenge_method")
267
+ : !sameSingleParameter(parameters, "code_challenge", expected.codeChallenge) ||
268
+ !sameSingleParameter(parameters, "code_challenge_method", "S256"))
248
269
  ) {
249
270
  throw createConnectorCodedError(
250
271
  "connector.adapter_invalid",
251
- "OAuth connector authorization URL must preserve the framework-provided state and PKCE S256 parameters."
272
+ expected.codeChallenge === undefined
273
+ ? "OAuth connector authorization URL must preserve the framework-provided state and omit disabled PKCE parameters."
274
+ : "OAuth connector authorization URL must preserve the framework-provided state and PKCE S256 parameters."
252
275
  )
253
276
  }
254
277
  }
255
278
 
279
+ export function connectorPkceEnabled(pkce: "S256" | "disabled" | undefined): boolean {
280
+ if (pkce === undefined || pkce === "S256") return true
281
+ if (pkce === "disabled") return false
282
+ throw createConnectorCodedError(
283
+ "connector.adapter_invalid",
284
+ "OAuth connector PKCE must be S256 or disabled."
285
+ )
286
+ }
287
+
288
+ const reservedCallbackParameters = new Set([
289
+ "state",
290
+ "code",
291
+ "auth_code",
292
+ "error",
293
+ "error_description",
294
+ "error_uri",
295
+ ])
296
+
297
+ export function validateCallbackParameterNames(names: readonly string[] = []): void {
298
+ if (
299
+ !Array.isArray(names) ||
300
+ names.some(
301
+ (name) =>
302
+ typeof name !== "string" ||
303
+ !/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(name) ||
304
+ reservedCallbackParameters.has(name)
305
+ ) ||
306
+ new Set(names).size !== names.length
307
+ ) {
308
+ throw createConnectorCodedError(
309
+ "connector.adapter_invalid",
310
+ "OAuth callback parameter names must be unique, non-reserved parameter names."
311
+ )
312
+ }
313
+ }
314
+
315
+ /** Select only declared provider values after the callback's state and browser binding are proved. */
316
+ export function selectCallbackParameters(
317
+ names: readonly string[] = [],
318
+ parameters: Readonly<Record<string, string | readonly string[]>> = {}
319
+ ): Readonly<Record<string, string>> {
320
+ validateCallbackParameterNames(names)
321
+ if (!isRecord(parameters)) {
322
+ throw createConnectorCodedError(
323
+ "connector.authorization_invalid",
324
+ "OAuth provider callback parameters must be an object."
325
+ )
326
+ }
327
+ return Object.fromEntries(
328
+ names.flatMap((name) => {
329
+ if (!Object.hasOwn(parameters, name)) return []
330
+ const value = parameters[name]
331
+ if (typeof value !== "string") {
332
+ throw createConnectorCodedError(
333
+ "connector.authorization_invalid",
334
+ "OAuth provider callback parameters must have a single string value."
335
+ )
336
+ }
337
+ return [[name, value]]
338
+ })
339
+ )
340
+ }
341
+
256
342
  export function nonblank(
257
343
  value: unknown,
258
344
  field: string,
@@ -1,3 +1,4 @@
1
+ import type { ReadonlyJsonObject } from "../json"
1
2
  import type { WebhookDefinition } from "../webhooks/types"
2
3
 
3
4
  /**
@@ -33,6 +34,12 @@ export interface ConnectorOAuthCredentials {
33
34
  readonly tokenType?: string
34
35
  readonly scopes?: readonly string[]
35
36
  readonly expiresAt?: Date
37
+ /**
38
+ * Provider-owned grant context, encrypted with the tokens and never exposed in connection views.
39
+ * Available to account discovery, refresh, and revoke. Refresh preserves it when omitted;
40
+ * returning an object replaces it (use {} to clear it). Code exchange starts fresh on reauthorization.
41
+ */
42
+ readonly authorizationContext?: ReadonlyJsonObject
36
43
  }
37
44
 
38
45
  /** Public, non-secret account information returned for framework-owned selection UI. */
@@ -75,13 +82,17 @@ export interface OAuthConnectorAuthorizationContext extends ConnectorContext {
75
82
 
76
83
  export interface OAuthConnectorAuthorizationUrlInput {
77
84
  readonly state: string
78
- readonly codeChallenge: string
79
- readonly codeChallengeMethod: "S256"
85
+ /** Present only when the adapter enables PKCE (S256 by default). */
86
+ readonly codeChallenge?: string
87
+ readonly codeChallengeMethod?: "S256"
80
88
  }
81
89
 
82
90
  export interface OAuthConnectorCodeExchangeInput {
83
91
  readonly code: string
84
- readonly codeVerifier: string
92
+ /** Present only when this authorization attempt used PKCE. */
93
+ readonly codeVerifier?: string
94
+ /** Allowlisted callback values. Untrusted until the adapter validates them with the provider. */
95
+ readonly callbackParameters?: Readonly<Record<string, string>>
85
96
  }
86
97
 
87
98
  export interface ConnectorConnectionClientContext extends ConnectorContext {
@@ -109,6 +120,10 @@ export interface ConnectorTokenSource {
109
120
  */
110
121
  export interface ConnectorOAuth2Authentication {
111
122
  readonly type: "oauth2"
123
+ /** Defaults to S256. Disable only for providers whose flow does not support PKCE. */
124
+ readonly pkce?: "S256" | "disabled"
125
+ /** Additional callback parameter names accepted by exchangeCode; framework OAuth fields are reserved. */
126
+ readonly callbackParameters?: readonly string[]
112
127
  authorizationUrl(
113
128
  context: OAuthConnectorAuthorizationContext,
114
129
  input: OAuthConnectorAuthorizationUrlInput
@@ -131,7 +146,7 @@ export interface ConnectorOAuth2Authentication {
131
146
  export interface OAuthConnectorAdapter<TType extends string = string, TClient = unknown> {
132
147
  readonly type: TType
133
148
  readonly authentication: ConnectorOAuth2Authentication
134
- readonly webhooks?: undefined
149
+ readonly webhooks?: readonly WebhookDefinition<unknown, TClient>[]
135
150
  discoverAccounts(
136
151
  context: ConnectorContext,
137
152
  credentials: ConnectorOAuthCredentials
@@ -17,7 +17,7 @@ export interface EventsRuntime {
17
17
  latestCursor(): Promise<string | undefined>
18
18
  subscribe(
19
19
  input: EventsSubscribeInput,
20
- handler: (events: readonly StoredDomainEvent[]) => void
20
+ handler: (events: readonly StoredDomainEvent[]) => unknown
21
21
  ): Promise<() => void>
22
22
  }
23
23
 
@@ -88,7 +88,7 @@ export function createEventsRuntime(runtime: SixbRuntimeContext): EventsRuntime
88
88
  }
89
89
  return runtime.events.subscribe(input, (events) => {
90
90
  const visible = visibleEvents(events)
91
- if (visible.length > 0) handler(visible)
91
+ if (visible.length > 0) return handler(visible)
92
92
  })
93
93
  },
94
94
  }
@@ -46,6 +46,7 @@ export interface OntologyOutboxDispatcherOptions {
46
46
  readonly maxIsolationAttempts?: number
47
47
  /** Maximum claimed batches handled by one drain pass. */
48
48
  readonly maxClaimsPerDrain?: number
49
+ /** Broker grace period. Shutdown still waits for outstanding storage operations afterward. */
49
50
  readonly shutdownTimeoutMs?: number
50
51
  readonly now?: () => Date
51
52
  readonly random?: () => number
@@ -100,10 +101,10 @@ export class OntologyOutboxDispatcher {
100
101
  private readonly createLeaseId: () => string
101
102
  private readonly onDeliveryFailure?: OntologyOutboxDispatcherOptions["onDeliveryFailure"]
102
103
  private readonly onError: (error: unknown) => void
103
- private readonly inFlight = new Set<ClaimedBatch>()
104
104
  private draining: Promise<void> | null = null
105
105
  private pendingDrain: Promise<void> | null = null
106
106
  private stopping: Promise<void> | null = null
107
+ private closing = false
107
108
  private stopRequested = false
108
109
  private readonly forcedStop = new AbortController()
109
110
 
@@ -166,7 +167,7 @@ export class OntologyOutboxDispatcher {
166
167
  * mutation.
167
168
  */
168
169
  drain(): Promise<void> {
169
- if (this.stopRequested) return Promise.resolve()
170
+ if (this.closing || this.stopRequested) return Promise.resolve()
170
171
  if (!this.draining) return this.startDrainPass()
171
172
 
172
173
  this.pendingDrain ??= settled(this.draining).then(() => {
@@ -178,7 +179,7 @@ export class OntologyOutboxDispatcher {
178
179
 
179
180
  /** Starts a tracked, best-effort drain without extending mutation latency. */
180
181
  notify(): void {
181
- if (this.stopRequested) return
182
+ if (this.closing || this.stopRequested) return
182
183
  void this.drain().catch((error) => this.reportError(error))
183
184
  }
184
185
 
@@ -193,19 +194,16 @@ export class OntologyOutboxDispatcher {
193
194
  // Include one final bounded pass so commits whose notifications were coalesced immediately
194
195
  // before shutdown are not left pending merely because the process is stopping.
195
196
  const gracefulDrain = this.drain().catch((error) => this.reportError(error))
196
- if (await settlesWithin(gracefulDrain, this.shutdownTimeoutMs)) {
197
- this.stopRequested = true
198
- return
199
- }
200
-
197
+ this.closing = true
198
+ const finished = await settlesWithin(gracefulDrain, this.shutdownTimeoutMs)
201
199
  this.stopRequested = true
202
- this.forcedStop.abort()
203
- await settlesWithin(
204
- this.rescheduleUnsettledForShutdown(),
205
- Math.min(this.shutdownTimeoutMs, 1_000)
206
- )
207
- this.draining = null
208
- this.pendingDrain = null
200
+ if (!finished) this.forcedStop.abort()
201
+ // Storage operations cannot be cancelled by this contract. A late claim must release its
202
+ // lease, and an acknowledgement already started must finish before callers close storage.
203
+ // publishClaim owns settlement, including forced stop; never race a second cleanup against it.
204
+ await gracefulDrain
205
+ if (this.draining) await settled(this.draining)
206
+ if (this.pendingDrain) await settled(this.pendingDrain)
209
207
  }
210
208
 
211
209
  private startDrainPass(): Promise<void> {
@@ -224,11 +222,14 @@ export class OntologyOutboxDispatcher {
224
222
  if (rows.length === 0) return
225
223
  const publishedCount = await this.publishClaim(rows)
226
224
  if (this.stopRequested || publishedCount === 0 || rows.length < this.batchSize) return
225
+ // In-memory/SQLite can resolve without I/O. Yield between full batches so catch-up cannot
226
+ // monopolize the event loop, while keeping small post-commit deliveries immediate.
227
+ await new Promise<void>((resolve) => setImmediate(resolve))
227
228
  }
228
229
 
229
230
  // A healthy full slice means more due rows may remain. Reuse the existing coalescing path so
230
231
  // catch-up continues without recursion, an idle polling loop, or an unbounded individual pass.
231
- if (!this.stopRequested) this.notify()
232
+ if (!this.closing && !this.stopRequested) this.notify()
232
233
  }
233
234
 
234
235
  private claimBatch(): Promise<readonly ClaimedOntologyOutboxRow[]> {
@@ -247,7 +248,6 @@ export class OntologyOutboxDispatcher {
247
248
  private async publishClaim(rows: readonly ClaimedOntologyOutboxRow[]): Promise<number> {
248
249
  const claim = new ClaimedBatch(rows)
249
250
  const failures = new DeliveryFailureAccumulator()
250
- this.inFlight.add(claim)
251
251
  const pending: PublicationGroup[] = [{ rows }]
252
252
  let publishAttempts = 0
253
253
  let publishedCount = 0
@@ -288,7 +288,6 @@ export class OntologyOutboxDispatcher {
288
288
  if (this.stopRequested) {
289
289
  await this.rescheduleClaimedForShutdown(claim.close())
290
290
  }
291
- this.inFlight.delete(claim)
292
291
  for (const failure of failures.list()) {
293
292
  this.reportDeliveryFailure(failure.error, failure.context)
294
293
  }
@@ -378,15 +377,10 @@ export class OntologyOutboxDispatcher {
378
377
  }
379
378
  }
380
379
 
381
- private async rescheduleUnsettledForShutdown(): Promise<void> {
382
- for (const claim of [...this.inFlight]) {
383
- await this.rescheduleClaimedForShutdown(claim.close())
384
- this.inFlight.delete(claim)
385
- }
386
- }
387
-
388
380
  private withOutbox<T>(run: (outbox: OntologyOutboxStorage) => Promise<T> | T): Promise<T> {
389
- return this.storage.transaction((tx) => run(tx.ontology.outbox))
381
+ // Every outbox method owns its atomic provider operation. A framework-wide transaction
382
+ // adds no guarantee here and copies the entire InMemoryStorage state for every batch.
383
+ return Promise.resolve(run(this.storage.ontology.outbox))
390
384
  }
391
385
 
392
386
  private reportDeliveryFailure(error: unknown, failure: OntologyOutboxDeliveryFailure): void {
@@ -79,7 +79,7 @@ export interface DomainEventLog {
79
79
  latestCursor(): Promise<string | undefined>
80
80
  subscribe(
81
81
  input: EventsSubscribeInput,
82
- handler: (events: readonly StoredDomainEvent[]) => void
82
+ handler: (events: readonly StoredDomainEvent[]) => unknown
83
83
  ): Promise<() => void>
84
84
  }
85
85
 
@@ -251,7 +251,7 @@ export class DomainEventService implements DomainEventLog, StableEventPublisher
251
251
 
252
252
  async subscribe(
253
253
  input: EventsSubscribeInput,
254
- handler: (events: readonly StoredDomainEvent[]) => void
254
+ handler: (events: readonly StoredDomainEvent[]) => unknown
255
255
  ): Promise<() => void> {
256
256
  await this.ensureStream()
257
257
  return this.broker.subscribe(
@@ -269,7 +269,7 @@ export class DomainEventService implements DomainEventLog, StableEventPublisher
269
269
  }
270
270
 
271
271
  try {
272
- handler(events)
272
+ return handler(events)
273
273
  } catch {
274
274
  // Preserve fire-and-forget subscriber semantics.
275
275
  }
package/src/index.ts CHANGED
@@ -163,9 +163,15 @@ export {
163
163
 
164
164
  // ── Broker ─────────────────────────────────────────────────
165
165
 
166
- export type { Broker, BrokerCursor } from "./broker"
166
+ export type {
167
+ Broker,
168
+ BrokerCursor,
169
+ BrokerRetention,
170
+ BrokerStreamRetention,
171
+ InMemoryBrokerOptions,
172
+ } from "./broker"
167
173
  export { InMemoryBroker } from "./broker"
168
- export type { JsonValue, ReadonlyJsonValue } from "./json"
174
+ export type { JsonValue, ReadonlyJsonObject, ReadonlyJsonValue } from "./json"
169
175
  export {
170
176
  assertJsonValue,
171
177
  cloneJsonValue,
@@ -681,6 +687,8 @@ export type {
681
687
  WebhookBodyFormat,
682
688
  WebhookBodyParser,
683
689
  WebhookBodySchema,
690
+ WebhookConnection,
691
+ WebhookConnections,
684
692
  WebhookConnectorClient,
685
693
  WebhookDefinition,
686
694
  WebhookHandlerContext,
@@ -0,0 +1,43 @@
1
+ import type { DatasetColumnDefinition } from "../datasets"
2
+ import { LakeStorageError } from "./errors"
3
+ import type { DatasetVersion, ReadDatasetChangesInput } from "./types"
4
+
5
+ /** Shared eligibility checks for providers comparing pinned row images. */
6
+ export function resolveDatasetChangeColumns(
7
+ input: ReadDatasetChangesInput,
8
+ before: DatasetVersion | null,
9
+ after: DatasetVersion | null
10
+ ): readonly DatasetColumnDefinition[] | null {
11
+ for (const value of [input.datasetId, input.fromVersionId, input.toVersionId]) {
12
+ if (typeof value !== "string" || value.trim().length === 0) {
13
+ throw new LakeStorageError(
14
+ "[LakeStorage] Change reads require explicit dataset and version ids."
15
+ )
16
+ }
17
+ }
18
+ if (
19
+ input.keyColumns.length === 0 ||
20
+ new Set(input.keyColumns).size !== input.keyColumns.length ||
21
+ input.columns.length === 0 ||
22
+ new Set(input.columns).size !== input.columns.length ||
23
+ input.columns.some((column) => typeof column !== "string" || column.length === 0) ||
24
+ input.keyColumns.some((column) => !input.columns.includes(column))
25
+ ) {
26
+ throw new LakeStorageError(
27
+ "[LakeStorage] Change reads require distinct columns including every key column."
28
+ )
29
+ }
30
+ if (!before || !after) return null
31
+ const oldColumns = new Map(before.schema.columns.map((column) => [column.name, column]))
32
+ const newColumns = new Map(after.schema.columns.map((column) => [column.name, column]))
33
+ const columns: DatasetColumnDefinition[] = []
34
+ for (const name of input.columns) {
35
+ const previous = oldColumns.get(name)
36
+ const next = newColumns.get(name)
37
+ if (!previous || !next || previous.type !== next.type) return null
38
+ // Projection identities are nonblank strings. Other key types keep the full-read path.
39
+ if (input.keyColumns.includes(name) && next.type !== "string") return null
40
+ columns.push(next)
41
+ }
42
+ return columns
43
+ }
@@ -1,6 +1,8 @@
1
1
  import { randomUUID } from "node:crypto"
2
+ import { isDeepStrictEqual } from "node:util"
2
3
  import type { DatasetDefinition, DatasetSchema, MergeChange } from "../datasets"
3
4
  import { getDatasetRowValidationError } from "../datasets/validation"
5
+ import { resolveDatasetChangeColumns } from "./changes"
4
6
  import { mergeStrictDatasetDefinition } from "./definition-updates"
5
7
  import { LakeConcurrencyError, LakeStorageError } from "./errors"
6
8
  import type {
@@ -26,12 +28,15 @@ import type {
26
28
  BeginDatasetWriteInput,
27
29
  CommitDatasetWriteInput,
28
30
  DatasetCatalogState,
31
+ DatasetChanges,
29
32
  DatasetRow,
33
+ DatasetRowChange,
30
34
  DatasetVersion,
31
35
  DatasetWriteCommitResult,
32
36
  DatasetWriteMode,
33
37
  LakeStorage,
34
38
  LakeWriteSession,
39
+ ReadDatasetChangesInput,
35
40
  ReadDatasetRowsInput,
36
41
  } from "./types"
37
42
  import { hasDatasetInputChanges } from "./version-inputs"
@@ -362,6 +367,7 @@ export class InMemoryLakeStorage implements LakeStorage {
362
367
  }
363
368
 
364
369
  async *readRows(input: ReadDatasetRowsInput): AsyncIterable<DatasetRow> {
370
+ input.signal?.throwIfAborted()
365
371
  const definition = this.datasets.get(input.datasetId)
366
372
  if (!definition) {
367
373
  throw new LakeStorageError(`[LakeStorage] Unknown dataset '${input.datasetId}'`)
@@ -385,10 +391,79 @@ export class InMemoryLakeStorage implements LakeStorage {
385
391
  ?.slice(offset, limit === undefined ? undefined : offset + limit)
386
392
 
387
393
  for (const row of rows ?? []) {
394
+ input.signal?.throwIfAborted()
388
395
  yield selectColumns(row, input.columns)
389
396
  }
390
397
  }
391
398
 
399
+ async readChanges(input: ReadDatasetChangesInput): Promise<DatasetChanges | null> {
400
+ input.signal?.throwIfAborted()
401
+ const before = await this.getVersion(input.datasetId, input.fromVersionId)
402
+ const after = await this.getVersion(input.datasetId, input.toVersionId)
403
+ if (!resolveDatasetChangeColumns(input, before, after)) return null
404
+ const previous = this.rowsByVersionId.get(input.fromVersionId)!
405
+ const next = this.rowsByVersionId.get(input.toVersionId)!
406
+ const index = (rows: readonly DatasetRow[]) => {
407
+ const result = new Map<string, DatasetRow>()
408
+ for (const row of rows) {
409
+ input.signal?.throwIfAborted()
410
+ const values = input.keyColumns.map((column) => row[column])
411
+ if (values.some((value) => typeof value !== "string" || value.trim().length === 0))
412
+ return null
413
+ const key = JSON.stringify(values)
414
+ if (result.has(key)) return null
415
+ result.set(key, row)
416
+ }
417
+ return result
418
+ }
419
+ const oldRows = index(previous)
420
+ const newRows = index(next)
421
+ if (!oldRows || !newRows) return null
422
+ let pending: DatasetRowChange[] = []
423
+ for (const [key, row] of oldRows) {
424
+ const newRow = newRows.get(key)
425
+ if (
426
+ !newRow ||
427
+ input.columns.some(
428
+ (column) => !isDeepStrictEqual(row[column] ?? null, newRow[column] ?? null)
429
+ )
430
+ ) {
431
+ pending.push({
432
+ before: selectColumns(row, input.columns),
433
+ after: newRow ? selectColumns(newRow, input.columns) : null,
434
+ })
435
+ }
436
+ }
437
+ for (const [key, row] of newRows) {
438
+ if (!oldRows.has(key))
439
+ pending.push({ before: null, after: selectColumns(row, input.columns) })
440
+ }
441
+ let closed = false
442
+ const changeCount = pending.length
443
+ return {
444
+ fromRowCount: previous.length,
445
+ toRowCount: next.length,
446
+ changeCount,
447
+ changes: (async function* () {
448
+ if (closed) throw new LakeStorageError("[LakeStorage] Change reader is closed.")
449
+ try {
450
+ for (let i = 0; i < changeCount; i++) {
451
+ input.signal?.throwIfAborted()
452
+ if (closed) throw new LakeStorageError("[LakeStorage] Change reader is closed.")
453
+ yield structuredClone(pending[i]!)
454
+ }
455
+ } finally {
456
+ closed = true
457
+ pending = []
458
+ }
459
+ })(),
460
+ async close() {
461
+ closed = true
462
+ pending = []
463
+ },
464
+ }
465
+ }
466
+
392
467
  async commitWrite(options: {
393
468
  write: BeginDatasetWriteInput
394
469
  rows: readonly DatasetRow[]