@sixb/core 0.1.9 → 0.1.10

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 (279) hide show
  1. package/dist/actions/index.js +8 -8
  2. package/dist/agents/index.js +16 -15
  3. package/dist/agents/index.js.map +1 -1
  4. package/dist/bootstrap/index.js +20 -19
  5. package/dist/bootstrap/index.js.map +1 -1
  6. package/dist/broker/in-memory.d.ts +8 -2
  7. package/dist/broker/in-memory.d.ts.map +1 -1
  8. package/dist/broker/index.d.ts +4 -2
  9. package/dist/broker/index.d.ts.map +1 -1
  10. package/dist/broker/index.js +7 -3
  11. package/dist/broker/index.js.map +1 -1
  12. package/dist/broker/retention.d.ts +4 -0
  13. package/dist/broker/retention.d.ts.map +1 -0
  14. package/dist/broker/subscriber.d.ts +10 -0
  15. package/dist/broker/subscriber.d.ts.map +1 -0
  16. package/dist/broker/types.d.ts +14 -1
  17. package/dist/broker/types.d.ts.map +1 -1
  18. package/dist/{chunk-k3cqkkn2.js → chunk-1824gssz.js} +30 -65
  19. package/dist/chunk-1824gssz.js.map +12 -0
  20. package/dist/{chunk-h1gfhhbh.js → chunk-28ja2cjs.js} +1 -1
  21. package/dist/chunk-2c4j899j.js.map +1 -1
  22. package/dist/{chunk-ppytyr6w.js → chunk-358caqf5.js} +4 -4
  23. package/dist/chunk-358caqf5.js.map +12 -0
  24. package/dist/{chunk-kxwa347f.js → chunk-3qms4nax.js} +2 -2
  25. package/dist/{chunk-kxwa347f.js.map → chunk-3qms4nax.js.map} +3 -3
  26. package/dist/{chunk-emyyk7dx.js → chunk-5c7ga8zz.js} +22 -145
  27. package/dist/chunk-5c7ga8zz.js.map +12 -0
  28. package/dist/{chunk-hpr1r6av.js → chunk-5p1v802y.js} +46 -39
  29. package/dist/{chunk-hpr1r6av.js.map → chunk-5p1v802y.js.map} +4 -4
  30. package/dist/{chunk-zcnq6105.js → chunk-85cd7e8c.js} +2 -1
  31. package/dist/chunk-85cd7e8c.js.map +10 -0
  32. package/dist/{chunk-1enhpacz.js → chunk-976rxdpx.js} +5 -5
  33. package/dist/{chunk-3f7veca0.js → chunk-9x8dybsb.js} +3 -3
  34. package/dist/chunk-9x8dybsb.js.map +10 -0
  35. package/dist/{chunk-r29fxdhn.js → chunk-aa6z4a19.js} +160 -44
  36. package/dist/chunk-aa6z4a19.js.map +12 -0
  37. package/dist/{chunk-r4tfx1sf.js → chunk-agbbygvh.js} +4 -4
  38. package/dist/{chunk-jjy0fhrn.js → chunk-b0ec55a1.js} +20 -3
  39. package/dist/chunk-b0ec55a1.js.map +12 -0
  40. package/dist/{chunk-xfbydgzy.js → chunk-bn138h9w.js} +7 -8
  41. package/dist/{chunk-xfbydgzy.js.map → chunk-bn138h9w.js.map} +3 -3
  42. package/dist/{chunk-an5tt9y0.js → chunk-ftv470j8.js} +42 -4
  43. package/dist/{chunk-an5tt9y0.js.map → chunk-ftv470j8.js.map} +7 -7
  44. package/dist/{chunk-0w35wavj.js → chunk-g7knyczr.js} +1 -1
  45. package/dist/{chunk-x57ycxq0.js → chunk-gqh45d2n.js} +42 -3
  46. package/dist/{chunk-x57ycxq0.js.map → chunk-gqh45d2n.js.map} +3 -3
  47. package/dist/{chunk-0rdrecsb.js → chunk-kfee1h52.js} +116 -32
  48. package/dist/chunk-kfee1h52.js.map +13 -0
  49. package/dist/{chunk-ypbtykv6.js → chunk-nmbxcaqx.js} +1 -1
  50. package/dist/{chunk-3tgwrcyh.js → chunk-nt99sxd6.js} +2 -2
  51. package/dist/{chunk-3tgwrcyh.js.map → chunk-nt99sxd6.js.map} +3 -3
  52. package/dist/{chunk-vf691g1r.js → chunk-p5vcsw9c.js} +2 -2
  53. package/dist/{chunk-mjbk2djy.js → chunk-p6a4npdx.js} +211 -106
  54. package/dist/{chunk-mjbk2djy.js.map → chunk-p6a4npdx.js.map} +9 -8
  55. package/dist/{chunk-3frsnm7x.js → chunk-qma80ych.js} +4 -6
  56. package/dist/chunk-qma80ych.js.map +11 -0
  57. package/dist/{chunk-qhcpj9wc.js → chunk-qyeptdyv.js} +96 -2
  58. package/dist/chunk-qyeptdyv.js.map +15 -0
  59. package/dist/{chunk-52hbv0ak.js → chunk-r7fj830t.js} +101 -48
  60. package/dist/chunk-r7fj830t.js.map +36 -0
  61. package/dist/{chunk-xyr8kppc.js → chunk-rtk71jzp.js} +1 -1
  62. package/dist/{chunk-m3z3x9v0.js → chunk-t1f4f1ve.js} +1 -1
  63. package/dist/chunk-v2vcg0w6.js +151 -0
  64. package/dist/chunk-v2vcg0w6.js.map +11 -0
  65. package/dist/connectors/connections/authorizations.d.ts.map +1 -1
  66. package/dist/connectors/connections/contracts.d.ts +3 -0
  67. package/dist/connectors/connections/contracts.d.ts.map +1 -1
  68. package/dist/connectors/connections/internal.js +1 -1
  69. package/dist/connectors/connections/request.d.ts +3 -1
  70. package/dist/connectors/connections/request.d.ts.map +1 -1
  71. package/dist/connectors/connections/runs.d.ts.map +1 -1
  72. package/dist/connectors/connections/tokens.d.ts.map +1 -1
  73. package/dist/connectors/connections/validation.d.ts +5 -1
  74. package/dist/connectors/connections/validation.d.ts.map +1 -1
  75. package/dist/connectors/types.d.ts +19 -4
  76. package/dist/connectors/types.d.ts.map +1 -1
  77. package/dist/events/execution.d.ts +1 -1
  78. package/dist/events/execution.d.ts.map +1 -1
  79. package/dist/events/index.js +1 -1
  80. package/dist/events/service.d.ts +2 -2
  81. package/dist/events/service.d.ts.map +1 -1
  82. package/dist/execution/agent.js +16 -15
  83. package/dist/execution/agent.js.map +1 -1
  84. package/dist/execution/primitive.js +16 -15
  85. package/dist/execution/primitive.js.map +2 -2
  86. package/dist/execution/request.js +16 -15
  87. package/dist/execution/request.js.map +2 -2
  88. package/dist/index.d.ts +3 -3
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +30 -28
  91. package/dist/index.js.map +1 -1
  92. package/dist/lake-storage/changes.d.ts +5 -0
  93. package/dist/lake-storage/changes.d.ts.map +1 -0
  94. package/dist/lake-storage/in-memory.d.ts +2 -1
  95. package/dist/lake-storage/in-memory.d.ts.map +1 -1
  96. package/dist/lake-storage/index.d.ts +2 -1
  97. package/dist/lake-storage/index.d.ts.map +1 -1
  98. package/dist/lake-storage/index.js +4 -2
  99. package/dist/lake-storage/index.js.map +1 -1
  100. package/dist/lake-storage/types.d.ts +36 -0
  101. package/dist/lake-storage/types.d.ts.map +1 -1
  102. package/dist/logging/internal.js +1 -1
  103. package/dist/materialization/index.d.ts +1 -1
  104. package/dist/materialization/index.d.ts.map +1 -1
  105. package/dist/materialization/model.d.ts +12 -1
  106. package/dist/materialization/model.d.ts.map +1 -1
  107. package/dist/materializer/index.d.ts +1 -1
  108. package/dist/materializer/index.d.ts.map +1 -1
  109. package/dist/materializer/index.js +5 -5
  110. package/dist/materializer/materializer.d.ts +5 -1
  111. package/dist/materializer/materializer.d.ts.map +1 -1
  112. package/dist/materializer/projections/entry-validator.d.ts +2 -1
  113. package/dist/materializer/projections/entry-validator.d.ts.map +1 -1
  114. package/dist/materializer/projections/replace.d.ts.map +1 -1
  115. package/dist/materializer/projections/source-materialization.d.ts +4 -3
  116. package/dist/materializer/projections/source-materialization.d.ts.map +1 -1
  117. package/dist/materializer/shared/normalize.d.ts +2 -1
  118. package/dist/materializer/shared/normalize.d.ts.map +1 -1
  119. package/dist/models/execution/index.js +5 -5
  120. package/dist/objects/index.js +9 -9
  121. package/dist/objects/query/index.js +5 -5
  122. package/dist/pipelines/index.js +6 -6
  123. package/dist/pipelines/run-dispatch.d.ts.map +1 -1
  124. package/dist/projections/internal.js +6 -8
  125. package/dist/projections/internal.js.map +1 -1
  126. package/dist/projections/run-dispatch.d.ts.map +1 -1
  127. package/dist/queues/in-memory.d.ts +7 -3
  128. package/dist/queues/in-memory.d.ts.map +1 -1
  129. package/dist/queues/index.js +1 -1
  130. package/dist/queues/indexed-heap.d.ts +15 -0
  131. package/dist/queues/indexed-heap.d.ts.map +1 -0
  132. package/dist/runtime/host.d.ts.map +1 -1
  133. package/dist/runtime/internal.js +1 -1
  134. package/dist/runtime/ontology-mutations.d.ts +3 -1
  135. package/dist/runtime/ontology-mutations.d.ts.map +1 -1
  136. package/dist/shares/internal.js +4 -4
  137. package/dist/storage/connector-connections/in-memory.d.ts.map +1 -1
  138. package/dist/storage/connector-connections/provider/runs.d.ts.map +1 -1
  139. package/dist/storage/connector-connections/provider.js +2 -2
  140. package/dist/storage/connector-connections/provider.js.map +3 -3
  141. package/dist/storage/connector-connections/types.d.ts +3 -2
  142. package/dist/storage/connector-connections/types.d.ts.map +1 -1
  143. package/dist/storage/index.d.ts +1 -1
  144. package/dist/storage/index.d.ts.map +1 -1
  145. package/dist/storage/index.js +4 -4
  146. package/dist/storage/internal.js +1 -1
  147. package/dist/storage/ontology/in-memory/materializations-state.d.ts.map +1 -1
  148. package/dist/storage/ontology/in-memory/materializations.d.ts +1 -1
  149. package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
  150. package/dist/storage/ontology/in-memory/shared-state.d.ts +15 -2
  151. package/dist/storage/ontology/in-memory/shared-state.d.ts.map +1 -1
  152. package/dist/storage/ontology/in-memory/source-roots.d.ts +6 -0
  153. package/dist/storage/ontology/in-memory/source-roots.d.ts.map +1 -0
  154. package/dist/storage/ontology/in-memory/sources.d.ts.map +1 -1
  155. package/dist/storage/ontology/index.d.ts +1 -1
  156. package/dist/storage/ontology/index.d.ts.map +1 -1
  157. package/dist/storage/ontology/provider.d.ts +16 -0
  158. package/dist/storage/ontology/provider.d.ts.map +1 -1
  159. package/dist/storage/ontology/provider.js +4 -2
  160. package/dist/storage/ontology/provider.js.map +1 -1
  161. package/dist/storage/ontology/sources.d.ts +13 -4
  162. package/dist/storage/ontology/sources.d.ts.map +1 -1
  163. package/dist/storage/projection-runs/lifecycle.d.ts.map +1 -1
  164. package/dist/storage/projection-runs/provider.js +2 -2
  165. package/dist/storage/projection-runs/types.d.ts +2 -0
  166. package/dist/storage/projection-runs/types.d.ts.map +1 -1
  167. package/dist/syncs/index.js +8 -7
  168. package/dist/syncs/index.js.map +1 -1
  169. package/dist/syncs/run-dispatch.d.ts.map +1 -1
  170. package/dist/testing/broker-contract.d.ts.map +1 -1
  171. package/dist/testing/connector-connection-storage-contract.d.ts.map +1 -1
  172. package/dist/testing/index.d.ts +1 -0
  173. package/dist/testing/index.d.ts.map +1 -1
  174. package/dist/testing/index.js +1459 -951
  175. package/dist/testing/index.js.map +9 -8
  176. package/dist/testing/lake-changes-contract.d.ts +6 -0
  177. package/dist/testing/lake-changes-contract.d.ts.map +1 -0
  178. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  179. package/dist/testing/ontology-storage-contract.d.ts.map +1 -1
  180. package/dist/testing/projection-run-storage-contract.d.ts.map +1 -1
  181. package/dist/webhooks/connections.d.ts +6 -0
  182. package/dist/webhooks/connections.d.ts.map +1 -0
  183. package/dist/webhooks/index.d.ts +1 -1
  184. package/dist/webhooks/index.d.ts.map +1 -1
  185. package/dist/webhooks/internal.d.ts +2 -0
  186. package/dist/webhooks/internal.d.ts.map +1 -1
  187. package/dist/webhooks/internal.js +54 -9
  188. package/dist/webhooks/internal.js.map +4 -3
  189. package/dist/webhooks/registry.d.ts +1 -1
  190. package/dist/webhooks/registry.d.ts.map +1 -1
  191. package/dist/webhooks/types.d.ts +15 -4
  192. package/dist/webhooks/types.d.ts.map +1 -1
  193. package/dist/workflows/index.js +17 -16
  194. package/dist/workflows/index.js.map +1 -1
  195. package/dist/workflows/run-dispatch.d.ts +3 -1
  196. package/dist/workflows/run-dispatch.d.ts.map +1 -1
  197. package/package.json +1 -1
  198. package/src/broker/in-memory.ts +104 -39
  199. package/src/broker/index.ts +4 -1
  200. package/src/broker/retention.ts +28 -0
  201. package/src/broker/subscriber.ts +21 -0
  202. package/src/broker/types.ts +15 -1
  203. package/src/connectors/builders.ts +0 -6
  204. package/src/connectors/connections/authorizations.ts +11 -5
  205. package/src/connectors/connections/contracts.ts +3 -0
  206. package/src/connectors/connections/request.ts +56 -22
  207. package/src/connectors/connections/runs.ts +1 -0
  208. package/src/connectors/connections/tokens.ts +4 -0
  209. package/src/connectors/connections/validation.ts +91 -5
  210. package/src/connectors/types.ts +19 -4
  211. package/src/events/execution.ts +2 -2
  212. package/src/events/service.ts +3 -3
  213. package/src/index.ts +10 -2
  214. package/src/lake-storage/changes.ts +43 -0
  215. package/src/lake-storage/in-memory.ts +73 -0
  216. package/src/lake-storage/index.ts +4 -0
  217. package/src/lake-storage/types.ts +40 -0
  218. package/src/logging/service.ts +1 -1
  219. package/src/materialization/index.ts +2 -0
  220. package/src/materialization/model.ts +14 -1
  221. package/src/materializer/index.ts +2 -0
  222. package/src/materializer/materializer.ts +10 -0
  223. package/src/materializer/projections/entry-validator.ts +19 -1
  224. package/src/materializer/projections/replace.ts +36 -1
  225. package/src/materializer/projections/source-materialization.ts +26 -6
  226. package/src/materializer/shared/normalize.ts +1 -1
  227. package/src/pipelines/run-dispatch.ts +21 -1
  228. package/src/projections/run-dispatch.ts +30 -68
  229. package/src/queues/in-memory.ts +105 -62
  230. package/src/queues/indexed-heap.ts +74 -0
  231. package/src/runtime/host.ts +0 -14
  232. package/src/runtime/ontology-mutations.ts +4 -0
  233. package/src/storage/connector-connections/in-memory.ts +3 -1
  234. package/src/storage/connector-connections/provider/runs.ts +3 -1
  235. package/src/storage/connector-connections/types.ts +3 -2
  236. package/src/storage/index.ts +1 -0
  237. package/src/storage/ontology/in-memory/materializations-state.ts +5 -18
  238. package/src/storage/ontology/in-memory/materializations.ts +73 -92
  239. package/src/storage/ontology/in-memory/shared-state.ts +26 -1
  240. package/src/storage/ontology/in-memory/source-roots.ts +123 -0
  241. package/src/storage/ontology/in-memory/sources.ts +78 -23
  242. package/src/storage/ontology/index.ts +1 -0
  243. package/src/storage/ontology/provider.ts +70 -0
  244. package/src/storage/ontology/sources.ts +14 -3
  245. package/src/storage/projection-runs/lifecycle.ts +5 -2
  246. package/src/storage/projection-runs/types.ts +4 -3
  247. package/src/syncs/run-dispatch.ts +19 -1
  248. package/src/testing/broker-contract.ts +95 -0
  249. package/src/testing/connector-connection-storage-contract.ts +18 -1
  250. package/src/testing/index.ts +1 -0
  251. package/src/testing/lake-changes-contract.ts +240 -0
  252. package/src/testing/materializer-storage-contract.ts +172 -1
  253. package/src/testing/ontology-storage-contract.ts +7 -0
  254. package/src/testing/projection-run-storage-contract.ts +34 -0
  255. package/src/webhooks/connections.ts +56 -0
  256. package/src/webhooks/index.ts +2 -0
  257. package/src/webhooks/internal.ts +2 -0
  258. package/src/webhooks/registry.ts +3 -4
  259. package/src/webhooks/types.ts +17 -4
  260. package/src/workflows/run-dispatch.ts +42 -29
  261. package/dist/chunk-0rdrecsb.js.map +0 -11
  262. package/dist/chunk-3f7veca0.js.map +0 -10
  263. package/dist/chunk-3frsnm7x.js.map +0 -11
  264. package/dist/chunk-52hbv0ak.js.map +0 -36
  265. package/dist/chunk-emyyk7dx.js.map +0 -14
  266. package/dist/chunk-jjy0fhrn.js.map +0 -12
  267. package/dist/chunk-k3cqkkn2.js.map +0 -12
  268. package/dist/chunk-ppytyr6w.js.map +0 -12
  269. package/dist/chunk-qhcpj9wc.js.map +0 -14
  270. package/dist/chunk-r29fxdhn.js.map +0 -11
  271. package/dist/chunk-zcnq6105.js.map +0 -10
  272. /package/dist/{chunk-h1gfhhbh.js.map → chunk-28ja2cjs.js.map} +0 -0
  273. /package/dist/{chunk-1enhpacz.js.map → chunk-976rxdpx.js.map} +0 -0
  274. /package/dist/{chunk-r4tfx1sf.js.map → chunk-agbbygvh.js.map} +0 -0
  275. /package/dist/{chunk-0w35wavj.js.map → chunk-g7knyczr.js.map} +0 -0
  276. /package/dist/{chunk-ypbtykv6.js.map → chunk-nmbxcaqx.js.map} +0 -0
  277. /package/dist/{chunk-vf691g1r.js.map → chunk-p5vcsw9c.js.map} +0 -0
  278. /package/dist/{chunk-xyr8kppc.js.map → chunk-rtk71jzp.js.map} +0 -0
  279. /package/dist/{chunk-m3z3x9v0.js.map → chunk-t1f4f1ve.js.map} +0 -0
@@ -1,11 +1,14 @@
1
1
  import { getInvalidJsonValueReason, type JsonValue } from "../json"
2
2
  import { BrokerCursorExpiredError, BrokerError } from "./errors"
3
+ import { createStreamRetentionResolver } from "./retention"
4
+ import { waitForSubscriber } from "./subscriber"
3
5
  import type {
4
6
  Broker,
5
7
  BrokerPage,
6
8
  BrokerRecord,
7
9
  BrokerRecordInput,
8
10
  BrokerStreamDefinition,
11
+ BrokerStreamRetention,
9
12
  } from "./types"
10
13
 
11
14
  // Payloads are validated by assertBrokerPayload above the call site, so we can
@@ -34,18 +37,30 @@ interface Subscription {
34
37
  readonly streamId: string
35
38
  readonly names?: readonly string[]
36
39
  readonly keys?: readonly string[]
37
- readonly handler: (records: readonly BrokerRecord[]) => void
40
+ readonly handler: (records: readonly BrokerRecord[]) => unknown
41
+ cursor: string
42
+ pumping: boolean
43
+ readonly controller: AbortController
44
+ }
45
+
46
+ export interface InMemoryBrokerOptions {
47
+ readonly streamRetention?: BrokerStreamRetention
38
48
  }
39
49
 
40
50
  export class InMemoryBroker implements Broker {
41
51
  readonly scope = "process" as const
42
52
  private readonly streams = new Map<string, StoredStream>()
43
53
  private readonly subscriptions = new Set<Subscription>()
54
+ private readonly resolveStream: ReturnType<typeof createStreamRetentionResolver>
55
+
56
+ constructor(options: InMemoryBrokerOptions = {}) {
57
+ this.resolveStream = createStreamRetentionResolver(options.streamRetention)
58
+ }
44
59
 
45
60
  async ensureStream(params: { projectId: string; stream: BrokerStreamDefinition }): Promise<void> {
46
61
  assertProjectId(params.projectId)
47
62
  assertStream(params.stream)
48
- this.getOrCreateStream(params.projectId, params.stream)
63
+ this.getOrCreateStream(params.projectId, this.resolveStream(params.stream))
49
64
  }
50
65
 
51
66
  async append(params: {
@@ -90,7 +105,7 @@ export class InMemoryBroker implements Broker {
90
105
  storedStream.retainedBytes += stored.reduce((total, record) => total + record.byteSize, 0)
91
106
  this.applyRetention(storedStream)
92
107
  const records = stored.map(toBrokerRecord)
93
- this.notify(params.projectId, params.streamId, stored)
108
+ this.notify(params.projectId, params.streamId)
94
109
  return records
95
110
  }
96
111
 
@@ -173,7 +188,7 @@ export class InMemoryBroker implements Broker {
173
188
  names?: readonly string[]
174
189
  keys?: readonly string[]
175
190
  },
176
- handler: (records: readonly BrokerRecord[]) => void
191
+ handler: (records: readonly BrokerRecord[]) => unknown
177
192
  ): Promise<() => void> {
178
193
  assertProjectId(params.projectId)
179
194
  assertStreamId(params.streamId)
@@ -188,27 +203,31 @@ export class InMemoryBroker implements Broker {
188
203
  this.applyRetention(storedStream)
189
204
  this.assertCursorInRetainedRange(storedStream, params.afterCursor)
190
205
 
206
+ const startMode = params.afterCursor !== undefined ? undefined : (params.from ?? "latest")
191
207
  const subscription: Subscription = {
192
208
  projectId: params.projectId,
193
209
  streamId: params.streamId,
194
210
  names: params.names,
195
211
  keys: params.keys,
196
212
  handler,
213
+ cursor:
214
+ params.afterCursor ??
215
+ (startMode === "earliest"
216
+ ? BigInt(storedStream.records[0]?.cursor ?? storedStream.nextSequence) - 1n
217
+ : storedStream.nextSequence - 1n
218
+ ).toString(),
219
+ pumping: false,
220
+ controller: new AbortController(),
197
221
  }
198
222
  this.subscriptions.add(subscription)
199
223
 
200
- const startMode = params.afterCursor !== undefined ? undefined : (params.from ?? "latest")
201
224
  if (params.afterCursor !== undefined || startMode === "earliest") {
202
- const initial = this.readForward(storedStream.records, {
203
- afterCursor: params.afterCursor,
204
- names: params.names,
205
- keys: params.keys,
206
- }).records
207
- this.deliver(subscription, initial)
225
+ this.deliver(subscription)
208
226
  }
209
227
 
210
228
  return () => {
211
229
  this.subscriptions.delete(subscription)
230
+ subscription.controller.abort()
212
231
  }
213
232
  }
214
233
 
@@ -335,30 +354,50 @@ export class InMemoryBroker implements Broker {
335
354
  }
336
355
  }
337
356
 
338
- private notify(projectId: string, streamId: string, records: readonly StoredRecord[]): void {
357
+ private notify(projectId: string, streamId: string): void {
339
358
  for (const subscription of this.subscriptions) {
340
359
  if (subscription.projectId !== projectId || subscription.streamId !== streamId) {
341
360
  continue
342
361
  }
343
- this.deliver(
344
- subscription,
345
- this.readForward(records, {
346
- names: subscription.names,
347
- keys: subscription.keys,
348
- }).records
349
- )
362
+ this.deliver(subscription)
350
363
  }
351
364
  }
352
365
 
353
- private deliver(subscription: Subscription, records: readonly BrokerRecord[]): void {
354
- if (records.length === 0) {
355
- return
356
- }
366
+ private deliver(subscription: Subscription): void {
367
+ if (subscription.pumping) return
368
+ subscription.pumping = true
369
+ // Only the retained stream holds the backlog, never a second queue of cloned batches.
370
+ void this.pump(subscription).catch((error) => {
371
+ this.subscriptions.delete(subscription)
372
+ subscription.controller.abort()
373
+ console.error("[InMemoryBroker] Subscription stopped:", error)
374
+ })
375
+ }
357
376
 
358
- try {
359
- subscription.handler(records)
360
- } catch {
361
- // Broker subscriptions are live fan-out; handler failures should not break publishers.
377
+ private async pump(subscription: Subscription): Promise<void> {
378
+ while (this.subscriptions.has(subscription)) {
379
+ const stream = this.getEnsuredStream(subscription.projectId, subscription.streamId)
380
+ this.applyRetention(stream)
381
+ this.assertCursorInRetainedRange(stream, subscription.cursor)
382
+ const page = this.readForward(stream.records, {
383
+ afterCursor: subscription.cursor,
384
+ names: subscription.names,
385
+ keys: subscription.keys,
386
+ limit: 100,
387
+ })
388
+ if (page.records.length === 0) {
389
+ subscription.cursor = page.cursor ?? subscription.cursor
390
+ // Clear synchronously: an append must not miss the wake-up between return and finally.
391
+ subscription.pumping = false
392
+ return
393
+ }
394
+ try {
395
+ await waitForSubscriber(subscription.handler(page.records), subscription.controller.signal)
396
+ } catch {
397
+ // Preserve observer error isolation, including rejected async callbacks.
398
+ }
399
+ if (!this.subscriptions.has(subscription)) return
400
+ subscription.cursor = page.cursor ?? subscription.cursor
362
401
  }
363
402
  }
364
403
 
@@ -378,11 +417,9 @@ export class InMemoryBroker implements Broker {
378
417
  let cursor = filters.afterCursor
379
418
  let stoppedAt = records.length
380
419
 
381
- for (let index = 0; index < records.length; index += 1) {
420
+ const start = afterCursor === undefined ? 0 : cursorBoundary(records, afterCursor, true)
421
+ for (let index = start; index < records.length; index += 1) {
382
422
  const record = records[index]!
383
- if (afterCursor !== undefined && BigInt(record.cursor) <= afterCursor) {
384
- continue
385
- }
386
423
  cursor = record.cursor
387
424
  if (!matchesFilters(record, names, keys)) {
388
425
  continue
@@ -397,7 +434,7 @@ export class InMemoryBroker implements Broker {
397
434
  return {
398
435
  records: result,
399
436
  cursor,
400
- hasMore: records.slice(stoppedAt).some((record) => matchesFilters(record, names, keys)),
437
+ hasMore: hasMatchingRecord(records, stoppedAt, records.length, names, keys),
401
438
  }
402
439
  }
403
440
 
@@ -417,11 +454,12 @@ export class InMemoryBroker implements Broker {
417
454
  let cursor = filters.beforeCursor
418
455
  let stoppedAt = -1
419
456
 
420
- for (let index = records.length - 1; index >= 0; index -= 1) {
457
+ const start =
458
+ beforeCursor === undefined
459
+ ? records.length - 1
460
+ : cursorBoundary(records, beforeCursor, false) - 1
461
+ for (let index = start; index >= 0; index -= 1) {
421
462
  const record = records[index]!
422
- if (beforeCursor !== undefined && BigInt(record.cursor) >= beforeCursor) {
423
- continue
424
- }
425
463
  cursor = record.cursor
426
464
  if (!matchesFilters(record, names, keys)) {
427
465
  continue
@@ -436,9 +474,7 @@ export class InMemoryBroker implements Broker {
436
474
  return {
437
475
  records: reversed.reverse(),
438
476
  cursor,
439
- hasMore:
440
- stoppedAt >= 0 &&
441
- records.slice(0, stoppedAt + 1).some((record) => matchesFilters(record, names, keys)),
477
+ hasMore: stoppedAt >= 0 && hasMatchingRecord(records, 0, stoppedAt + 1, names, keys),
442
478
  }
443
479
  }
444
480
  }
@@ -516,3 +552,32 @@ function assertCursor(cursor: string | undefined): void {
516
552
  })
517
553
  }
518
554
  }
555
+
556
+ function cursorBoundary(records: readonly StoredRecord[], cursor: bigint, after: boolean): number {
557
+ // Retained cursors remain sorted even after retention removes older records.
558
+ let low = 0
559
+ let high = records.length
560
+ while (low < high) {
561
+ const middle = Math.floor((low + high) / 2)
562
+ const value = BigInt(records[middle]!.cursor)
563
+ if (value < cursor || (after && value === cursor)) {
564
+ low = middle + 1
565
+ } else {
566
+ high = middle
567
+ }
568
+ }
569
+ return low
570
+ }
571
+
572
+ function hasMatchingRecord(
573
+ records: readonly StoredRecord[],
574
+ start: number,
575
+ end: number,
576
+ names?: ReadonlySet<string>,
577
+ keys?: ReadonlySet<string>
578
+ ): boolean {
579
+ for (let i = start; i < end; i++) {
580
+ if (matchesFilters(records[i]!, names, keys)) return true
581
+ }
582
+ return false
583
+ }
@@ -1,6 +1,8 @@
1
1
  export type { ProviderScope } from "../provider-scope"
2
2
  export { BrokerCursorExpiredError, BrokerError } from "./errors"
3
- export { InMemoryBroker } from "./in-memory"
3
+ export { InMemoryBroker, type InMemoryBrokerOptions } from "./in-memory"
4
+ export { createStreamRetentionResolver } from "./retention"
5
+ export { waitForSubscriber } from "./subscriber"
4
6
  export type {
5
7
  Broker,
6
8
  BrokerCursor,
@@ -9,4 +11,5 @@ export type {
9
11
  BrokerRecordInput,
10
12
  BrokerRetention,
11
13
  BrokerStreamDefinition,
14
+ BrokerStreamRetention,
12
15
  } from "./types"
@@ -0,0 +1,28 @@
1
+ import { BrokerError } from "./errors"
2
+ import type { BrokerRetention, BrokerStreamDefinition, BrokerStreamRetention } from "./types"
3
+
4
+ /** Provider utility: snapshot and validate configuration before opening any connections. */
5
+ export function createStreamRetentionResolver(
6
+ configuration: BrokerStreamRetention = {}
7
+ ): (stream: BrokerStreamDefinition) => BrokerStreamDefinition {
8
+ const overrides = new Map<string, BrokerRetention>()
9
+ for (const [id, retention] of Object.entries(configuration)) {
10
+ if (!id.trim()) throw new BrokerError("Retention stream id must not be empty")
11
+ const limits: Partial<Record<keyof BrokerRetention, number>> = {}
12
+ for (const field of ["maxAgeMs", "maxRecords", "maxBytes"] as const) {
13
+ const value = retention[field]
14
+ if (value === undefined) continue
15
+ if (!Number.isSafeInteger(value) || value <= 0) {
16
+ throw new BrokerError(
17
+ `Retention ${field} for stream '${id}' must be a positive safe integer`
18
+ )
19
+ }
20
+ limits[field] = value
21
+ }
22
+ overrides.set(id, limits)
23
+ }
24
+ return (stream) => {
25
+ const retention = overrides.get(stream.id)
26
+ return retention ? { ...stream, retention: { ...stream.retention, ...retention } } : stream
27
+ }
28
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Await one observer delivery, or detach it on unsubscribe. The observer's owner
3
+ * remains responsible for cancellation/draining. Rejections are observed even
4
+ * after detaching; each completed delivery removes its abort listener.
5
+ *
6
+ * Do not race every batch against one permanent stop promise: that retains a
7
+ * promise reaction per batch for the subscription's entire lifetime.
8
+ */
9
+ export async function waitForSubscriber(result: unknown, signal: AbortSignal): Promise<void> {
10
+ let onAbort: (() => void) | undefined
11
+ try {
12
+ await new Promise<void>((resolve, reject) => {
13
+ onAbort = resolve
14
+ signal.addEventListener("abort", onAbort, { once: true })
15
+ Promise.resolve(result).then(() => resolve(), reject)
16
+ if (signal.aborted) resolve()
17
+ })
18
+ } finally {
19
+ if (onAbort) signal.removeEventListener("abort", onAbort)
20
+ }
21
+ }
@@ -21,6 +21,9 @@ export interface BrokerStreamDefinition {
21
21
  readonly retention?: BrokerRetention
22
22
  }
23
23
 
24
+ /** Per-stream overrides at creation; omitted limits keep the stream's own defaults. */
25
+ export type BrokerStreamRetention = Readonly<Record<string, BrokerRetention>>
26
+
24
27
  export interface BrokerRecordInput {
25
28
  readonly name?: string
26
29
  readonly key?: string
@@ -102,6 +105,17 @@ export interface Broker {
102
105
  * range rather than silently advancing to the first retained record, so a
103
106
  * resuming subscriber learns the gap exists instead of mistaking a truncated
104
107
  * stream for a complete one.
108
+ *
109
+ * A returned handler promise is awaited before the next delivery. Providers
110
+ * bound read-ahead instead of accumulating pending handler calls. Return the
111
+ * processing promise to apply backpressure; detached work cannot be bounded.
112
+ * Handler errors remain isolated from publishers and other subscribers; they
113
+ * are not a negative acknowledgement. Reliable consumers must retry within
114
+ * their handler before returning. No progress survives a process restart.
115
+ *
116
+ * Unsubscribe stops further delivery, not an already running callback. Its
117
+ * owner must cancel/drain that work; provider close must not wait indefinitely
118
+ * for an uncooperative handler. Return values other than promises are ignored.
105
119
  */
106
120
  subscribe(
107
121
  params: {
@@ -112,7 +126,7 @@ export interface Broker {
112
126
  names?: readonly string[]
113
127
  keys?: readonly string[]
114
128
  },
115
- handler: (records: readonly BrokerRecord[]) => void
129
+ handler: (records: readonly BrokerRecord[]) => unknown
116
130
  ): Promise<() => void>
117
131
 
118
132
  /**
@@ -52,12 +52,6 @@ export function defineConnector<TId extends string, TAdapter extends AnyConnecto
52
52
  "OAuth connector adapters must define oauth2 authentication and implement authorizationUrl, exchangeCode, refresh, and discoverAccounts."
53
53
  )
54
54
  }
55
- if (adapter.webhooks !== undefined) {
56
- throw createConnectorCodedError(
57
- "connector.configuration_invalid",
58
- "OAuth connector adapters cannot register webhooks until connection routing is defined."
59
- )
60
- }
61
55
  }
62
56
 
63
57
  return {
@@ -160,7 +160,8 @@ export class DefaultConnectorAuthorizationLifecycle implements ConnectorAuthoriz
160
160
  input.definition,
161
161
  input.code,
162
162
  input.codeVerifier,
163
- input.redirectUri
163
+ input.redirectUri,
164
+ input.callbackParameters
164
165
  )
165
166
  const credentialsEnvelope = await this.credentials.seal(
166
167
  input.definition.id,
@@ -482,7 +483,11 @@ export class DefaultConnectorAuthorizationLifecycle implements ConnectorAuthoriz
482
483
  const credentials = validateCredentials(
483
484
  await input.definition.adapter.authentication.exchangeCode(
484
485
  this.authorizationAdapterContext(input.definition.id, input.redirectUri, signal),
485
- { code: input.code, codeVerifier: input.codeVerifier }
486
+ {
487
+ code: input.code,
488
+ ...(input.codeVerifier === undefined ? {} : { codeVerifier: input.codeVerifier }),
489
+ callbackParameters: input.callbackParameters,
490
+ }
486
491
  )
487
492
  )
488
493
  assertCredentialMutationOperationActive(signal)
@@ -544,15 +549,16 @@ export class DefaultConnectorAuthorizationLifecycle implements ConnectorAuthoriz
544
549
  private async exchangeAuthorizationCode(
545
550
  definition: OAuthConnectorDefinition,
546
551
  code: string,
547
- codeVerifier: string,
548
- redirectUri: string
552
+ codeVerifier: string | undefined,
553
+ redirectUri: string,
554
+ callbackParameters: Readonly<Record<string, string>> | undefined
549
555
  ): Promise<ConnectorOAuthCredentials> {
550
556
  try {
551
557
  return validateCredentials(
552
558
  await this.mutations.withBoundedProviderSignal((signal) =>
553
559
  definition.adapter.authentication.exchangeCode(
554
560
  this.authorizationAdapterContext(definition.id, redirectUri, signal),
555
- { code, codeVerifier }
561
+ { code, ...(codeVerifier === undefined ? {} : { codeVerifier }), callbackParameters }
556
562
  )
557
563
  )
558
564
  )
@@ -46,6 +46,8 @@ export interface CompleteConnectorAuthorizationInput {
46
46
  readonly state: string
47
47
  readonly code: string
48
48
  readonly redirectUri: string
49
+ /** Raw provider callback values; duplicate values are rejected for allowlisted parameters. */
50
+ readonly callbackParameters?: Readonly<Record<string, string | readonly string[]>>
49
51
  }
50
52
 
51
53
  export type CompleteConnectorConnectionRunInput =
@@ -54,6 +56,7 @@ export type CompleteConnectorConnectionRunInput =
54
56
  readonly code: string
55
57
  readonly redirectUri: string
56
58
  readonly callbackBinding: string
59
+ readonly callbackParameters?: Readonly<Record<string, string | readonly string[]>>
57
60
  }
58
61
  | {
59
62
  readonly state: string
@@ -34,11 +34,14 @@ import type {
34
34
  import { runBoundedConnectorProviderOperation } from "./provider-operation"
35
35
  import {
36
36
  assertAuthorizationUrlParameters,
37
+ connectorPkceEnabled,
37
38
  hashSecret,
38
39
  nonblank,
39
40
  normalizedHttpUrl,
40
41
  parseAttemptId,
41
42
  positiveDuration,
43
+ selectCallbackParameters,
44
+ validateCallbackParameterNames,
42
45
  } from "./validation"
43
46
  import { assertConnectorConnectionSelector, connectorConnectionView } from "./views"
44
47
 
@@ -64,8 +67,9 @@ export interface CompleteNewConnectorAuthorizationInput {
64
67
  readonly definition: OAuthConnectorDefinition
65
68
  readonly principal: AuthorizablePrincipal
66
69
  readonly code: string
67
- readonly codeVerifier: string
70
+ readonly codeVerifier?: string
68
71
  readonly redirectUri: string
72
+ readonly callbackParameters?: Readonly<Record<string, string>>
69
73
  /** Called immediately after the encrypted grant is durable, before account discovery. */
70
74
  readonly onAuthorizationPersisted?: (authorization: ConnectorAuthorizationRecord) => Promise<void>
71
75
  }
@@ -172,6 +176,7 @@ export class ConnectorAuthorizationRequestHandler {
172
176
  const definition = this.resolveDefinition(nonblank(connectorId, "connector id"))
173
177
  const actor = requireConnectorConnectionCommandActor(command.execution, this.projectId)
174
178
  assertConnectorConnectionSelector(input)
179
+ validateCallbackParameterNames(definition.adapter.authentication.callbackParameters)
175
180
  const redirectUri = normalizedHttpUrl(input.redirectUri, "OAuth callback URL")
176
181
  const reauthorization = await this.prepareReauthorization(
177
182
  definition,
@@ -181,8 +186,13 @@ export class ConnectorAuthorizationRequestHandler {
181
186
 
182
187
  const attemptId = `cat_${randomUUID()}`
183
188
  const state = `${attemptId}.${randomBytes(32).toString("base64url")}`
184
- const codeVerifier = randomBytes(32).toString("base64url")
185
- const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url")
189
+ const codeVerifier = connectorPkceEnabled(definition.adapter.authentication.pkce)
190
+ ? randomBytes(32).toString("base64url")
191
+ : undefined
192
+ const codeChallenge =
193
+ codeVerifier === undefined
194
+ ? undefined
195
+ : createHash("sha256").update(codeVerifier).digest("base64url")
186
196
  let authorizationUrlInput: string | URL
187
197
  try {
188
198
  authorizationUrlInput = await runBoundedConnectorProviderOperation(
@@ -198,7 +208,12 @@ export class ConnectorAuthorizationRequestHandler {
198
208
  redirectUri,
199
209
  signal,
200
210
  },
201
- { state, codeChallenge, codeChallengeMethod: "S256" }
211
+ {
212
+ state,
213
+ ...(codeChallenge === undefined
214
+ ? {}
215
+ : { codeChallenge, codeChallengeMethod: "S256" }),
216
+ }
202
217
  ),
203
218
  () =>
204
219
  createConnectorCodedError(
@@ -220,15 +235,15 @@ export class ConnectorAuthorizationRequestHandler {
220
235
  "connector.adapter_invalid"
221
236
  )
222
237
  assertAuthorizationUrlParameters(authorizationUrl, { state, codeChallenge })
223
- const codeVerifierEnvelope = await this.credentialProtector.seal(
224
- textEncoder.encode(codeVerifier),
225
- {
226
- projectId: this.projectId,
227
- connectorId: definition.id,
228
- recordId: attemptId,
229
- purpose: "pkce-verifier",
230
- }
231
- )
238
+ const codeVerifierEnvelope =
239
+ codeVerifier === undefined
240
+ ? undefined
241
+ : await this.credentialProtector.seal(textEncoder.encode(codeVerifier), {
242
+ projectId: this.projectId,
243
+ connectorId: definition.id,
244
+ recordId: attemptId,
245
+ purpose: "pkce-verifier",
246
+ })
232
247
 
233
248
  return {
234
249
  definition,
@@ -243,7 +258,7 @@ export class ConnectorAuthorizationRequestHandler {
243
258
  slot: input.slot,
244
259
  initiatedByExecutionId: command.execution.id,
245
260
  stateHash: hashSecret(state),
246
- codeVerifier: codeVerifierEnvelope,
261
+ ...(codeVerifierEnvelope === undefined ? {} : { codeVerifier: codeVerifierEnvelope }),
247
262
  redirectUri,
248
263
  ...(reauthorization === undefined
249
264
  ? {}
@@ -275,6 +290,7 @@ export class ConnectorAuthorizationRequestHandler {
275
290
  principal: actor.principal,
276
291
  code,
277
292
  redirectUri,
293
+ callbackParameters: input.callbackParameters,
278
294
  })
279
295
 
280
296
  return {
@@ -291,19 +307,34 @@ export class ConnectorAuthorizationRequestHandler {
291
307
  readonly principal: AuthorizablePrincipal
292
308
  readonly code: string
293
309
  readonly redirectUri: string
310
+ readonly callbackParameters?: Readonly<Record<string, string | readonly string[]>>
294
311
  readonly onAuthorizationPersisted?: (
295
312
  authorization: ConnectorAuthorizationRecord
296
313
  ) => Promise<void>
297
314
  }): Promise<ConnectorAuthorizationRecord> {
298
- const verifierBytes = await this.credentialProtector.open(input.attempt.codeVerifier, {
299
- projectId: this.projectId,
300
- connectorId: input.definition.id,
301
- recordId: input.attempt.id,
302
- purpose: "pkce-verifier",
303
- })
315
+ const enabled = connectorPkceEnabled(input.definition.adapter.authentication.pkce)
316
+ if (enabled !== (input.attempt.codeVerifier !== undefined)) {
317
+ throw createConnectorCodedError(
318
+ "connector.authorization_invalid",
319
+ "OAuth connector PKCE configuration changed; restart authorization."
320
+ )
321
+ }
322
+ const verifierBytes =
323
+ input.attempt.codeVerifier === undefined
324
+ ? undefined
325
+ : await this.credentialProtector.open(input.attempt.codeVerifier, {
326
+ projectId: this.projectId,
327
+ connectorId: input.definition.id,
328
+ recordId: input.attempt.id,
329
+ purpose: "pkce-verifier",
330
+ })
304
331
  return this.completeGrant({
305
332
  ...input,
306
- codeVerifier: textDecoder.decode(verifierBytes),
333
+ callbackParameters: selectCallbackParameters(
334
+ input.definition.adapter.authentication.callbackParameters,
335
+ input.callbackParameters
336
+ ),
337
+ ...(verifierBytes === undefined ? {} : { codeVerifier: textDecoder.decode(verifierBytes) }),
307
338
  })
308
339
  }
309
340
 
@@ -399,8 +430,9 @@ export class ConnectorAuthorizationRequestHandler {
399
430
  readonly attempt: ConnectorAuthorizationAttemptRecord
400
431
  readonly principal: AuthorizablePrincipal
401
432
  readonly code: string
402
- readonly codeVerifier: string
433
+ readonly codeVerifier?: string
403
434
  readonly redirectUri: string
435
+ readonly callbackParameters: Readonly<Record<string, string>>
404
436
  readonly onAuthorizationPersisted?: (
405
437
  authorization: ConnectorAuthorizationRecord
406
438
  ) => Promise<void>
@@ -411,6 +443,7 @@ export class ConnectorAuthorizationRequestHandler {
411
443
  principal: input.principal,
412
444
  code: input.code,
413
445
  codeVerifier: input.codeVerifier,
446
+ callbackParameters: input.callbackParameters,
414
447
  redirectUri: input.redirectUri,
415
448
  ...(input.onAuthorizationPersisted === undefined
416
449
  ? {}
@@ -433,6 +466,7 @@ export class ConnectorAuthorizationRequestHandler {
433
466
  principal: input.principal,
434
467
  code: input.code,
435
468
  codeVerifier: input.codeVerifier,
469
+ callbackParameters: input.callbackParameters,
436
470
  redirectUri: input.redirectUri,
437
471
  })
438
472
  }
@@ -207,6 +207,7 @@ export class ConnectorConnectionRunService {
207
207
  attempt,
208
208
  principal,
209
209
  code: nonblank(input.code, "OAuth authorization code", "connector.authorization_invalid"),
210
+ callbackParameters: input.callbackParameters,
210
211
  redirectUri,
211
212
  onAuthorizationPersisted: async (persisted) => {
212
213
  authorizationId = persisted.id
@@ -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,