@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.
- package/dist/actions/index.js +8 -8
- package/dist/agents/index.js +16 -15
- package/dist/agents/index.js.map +1 -1
- package/dist/bootstrap/index.js +20 -19
- package/dist/bootstrap/index.js.map +1 -1
- package/dist/broker/in-memory.d.ts +8 -2
- package/dist/broker/in-memory.d.ts.map +1 -1
- package/dist/broker/index.d.ts +4 -2
- package/dist/broker/index.d.ts.map +1 -1
- package/dist/broker/index.js +7 -3
- package/dist/broker/index.js.map +1 -1
- package/dist/broker/retention.d.ts +4 -0
- package/dist/broker/retention.d.ts.map +1 -0
- package/dist/broker/subscriber.d.ts +10 -0
- package/dist/broker/subscriber.d.ts.map +1 -0
- package/dist/broker/types.d.ts +14 -1
- package/dist/broker/types.d.ts.map +1 -1
- package/dist/{chunk-k3cqkkn2.js → chunk-1824gssz.js} +30 -65
- package/dist/chunk-1824gssz.js.map +12 -0
- package/dist/{chunk-h1gfhhbh.js → chunk-28ja2cjs.js} +1 -1
- package/dist/chunk-2c4j899j.js.map +1 -1
- package/dist/{chunk-ppytyr6w.js → chunk-358caqf5.js} +4 -4
- package/dist/chunk-358caqf5.js.map +12 -0
- package/dist/{chunk-kxwa347f.js → chunk-3qms4nax.js} +2 -2
- package/dist/{chunk-kxwa347f.js.map → chunk-3qms4nax.js.map} +3 -3
- package/dist/{chunk-emyyk7dx.js → chunk-5c7ga8zz.js} +22 -145
- package/dist/chunk-5c7ga8zz.js.map +12 -0
- package/dist/{chunk-hpr1r6av.js → chunk-5p1v802y.js} +46 -39
- package/dist/{chunk-hpr1r6av.js.map → chunk-5p1v802y.js.map} +4 -4
- package/dist/{chunk-zcnq6105.js → chunk-85cd7e8c.js} +2 -1
- package/dist/chunk-85cd7e8c.js.map +10 -0
- package/dist/{chunk-1enhpacz.js → chunk-976rxdpx.js} +5 -5
- package/dist/{chunk-3f7veca0.js → chunk-9x8dybsb.js} +3 -3
- package/dist/chunk-9x8dybsb.js.map +10 -0
- package/dist/{chunk-r29fxdhn.js → chunk-aa6z4a19.js} +160 -44
- package/dist/chunk-aa6z4a19.js.map +12 -0
- package/dist/{chunk-r4tfx1sf.js → chunk-agbbygvh.js} +4 -4
- package/dist/{chunk-jjy0fhrn.js → chunk-b0ec55a1.js} +20 -3
- package/dist/chunk-b0ec55a1.js.map +12 -0
- package/dist/{chunk-xfbydgzy.js → chunk-bn138h9w.js} +7 -8
- package/dist/{chunk-xfbydgzy.js.map → chunk-bn138h9w.js.map} +3 -3
- package/dist/{chunk-an5tt9y0.js → chunk-ftv470j8.js} +42 -4
- package/dist/{chunk-an5tt9y0.js.map → chunk-ftv470j8.js.map} +7 -7
- package/dist/{chunk-0w35wavj.js → chunk-g7knyczr.js} +1 -1
- package/dist/{chunk-x57ycxq0.js → chunk-gqh45d2n.js} +42 -3
- package/dist/{chunk-x57ycxq0.js.map → chunk-gqh45d2n.js.map} +3 -3
- package/dist/{chunk-0rdrecsb.js → chunk-kfee1h52.js} +116 -32
- package/dist/chunk-kfee1h52.js.map +13 -0
- package/dist/{chunk-ypbtykv6.js → chunk-nmbxcaqx.js} +1 -1
- package/dist/{chunk-3tgwrcyh.js → chunk-nt99sxd6.js} +2 -2
- package/dist/{chunk-3tgwrcyh.js.map → chunk-nt99sxd6.js.map} +3 -3
- package/dist/{chunk-vf691g1r.js → chunk-p5vcsw9c.js} +2 -2
- package/dist/{chunk-mjbk2djy.js → chunk-p6a4npdx.js} +211 -106
- package/dist/{chunk-mjbk2djy.js.map → chunk-p6a4npdx.js.map} +9 -8
- package/dist/{chunk-3frsnm7x.js → chunk-qma80ych.js} +4 -6
- package/dist/chunk-qma80ych.js.map +11 -0
- package/dist/{chunk-qhcpj9wc.js → chunk-qyeptdyv.js} +96 -2
- package/dist/chunk-qyeptdyv.js.map +15 -0
- package/dist/{chunk-52hbv0ak.js → chunk-r7fj830t.js} +101 -48
- package/dist/chunk-r7fj830t.js.map +36 -0
- package/dist/{chunk-xyr8kppc.js → chunk-rtk71jzp.js} +1 -1
- package/dist/{chunk-m3z3x9v0.js → chunk-t1f4f1ve.js} +1 -1
- package/dist/chunk-v2vcg0w6.js +151 -0
- package/dist/chunk-v2vcg0w6.js.map +11 -0
- package/dist/connectors/connections/authorizations.d.ts.map +1 -1
- package/dist/connectors/connections/contracts.d.ts +3 -0
- package/dist/connectors/connections/contracts.d.ts.map +1 -1
- package/dist/connectors/connections/internal.js +1 -1
- package/dist/connectors/connections/request.d.ts +3 -1
- package/dist/connectors/connections/request.d.ts.map +1 -1
- package/dist/connectors/connections/runs.d.ts.map +1 -1
- package/dist/connectors/connections/tokens.d.ts.map +1 -1
- package/dist/connectors/connections/validation.d.ts +5 -1
- package/dist/connectors/connections/validation.d.ts.map +1 -1
- package/dist/connectors/types.d.ts +19 -4
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/events/execution.d.ts +1 -1
- package/dist/events/execution.d.ts.map +1 -1
- package/dist/events/index.js +1 -1
- package/dist/events/service.d.ts +2 -2
- package/dist/events/service.d.ts.map +1 -1
- package/dist/execution/agent.js +16 -15
- package/dist/execution/agent.js.map +1 -1
- package/dist/execution/primitive.js +16 -15
- package/dist/execution/primitive.js.map +2 -2
- package/dist/execution/request.js +16 -15
- package/dist/execution/request.js.map +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -28
- package/dist/index.js.map +1 -1
- package/dist/lake-storage/changes.d.ts +5 -0
- package/dist/lake-storage/changes.d.ts.map +1 -0
- package/dist/lake-storage/in-memory.d.ts +2 -1
- package/dist/lake-storage/in-memory.d.ts.map +1 -1
- package/dist/lake-storage/index.d.ts +2 -1
- package/dist/lake-storage/index.d.ts.map +1 -1
- package/dist/lake-storage/index.js +4 -2
- package/dist/lake-storage/index.js.map +1 -1
- package/dist/lake-storage/types.d.ts +36 -0
- package/dist/lake-storage/types.d.ts.map +1 -1
- package/dist/logging/internal.js +1 -1
- package/dist/materialization/index.d.ts +1 -1
- package/dist/materialization/index.d.ts.map +1 -1
- package/dist/materialization/model.d.ts +12 -1
- package/dist/materialization/model.d.ts.map +1 -1
- package/dist/materializer/index.d.ts +1 -1
- package/dist/materializer/index.d.ts.map +1 -1
- package/dist/materializer/index.js +5 -5
- package/dist/materializer/materializer.d.ts +5 -1
- package/dist/materializer/materializer.d.ts.map +1 -1
- package/dist/materializer/projections/entry-validator.d.ts +2 -1
- package/dist/materializer/projections/entry-validator.d.ts.map +1 -1
- package/dist/materializer/projections/replace.d.ts.map +1 -1
- package/dist/materializer/projections/source-materialization.d.ts +4 -3
- package/dist/materializer/projections/source-materialization.d.ts.map +1 -1
- package/dist/materializer/shared/normalize.d.ts +2 -1
- package/dist/materializer/shared/normalize.d.ts.map +1 -1
- package/dist/models/execution/index.js +5 -5
- package/dist/objects/index.js +9 -9
- package/dist/objects/query/index.js +5 -5
- package/dist/pipelines/index.js +6 -6
- package/dist/pipelines/run-dispatch.d.ts.map +1 -1
- package/dist/projections/internal.js +6 -8
- package/dist/projections/internal.js.map +1 -1
- package/dist/projections/run-dispatch.d.ts.map +1 -1
- package/dist/queues/in-memory.d.ts +7 -3
- package/dist/queues/in-memory.d.ts.map +1 -1
- package/dist/queues/index.js +1 -1
- package/dist/queues/indexed-heap.d.ts +15 -0
- package/dist/queues/indexed-heap.d.ts.map +1 -0
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/internal.js +1 -1
- package/dist/runtime/ontology-mutations.d.ts +3 -1
- package/dist/runtime/ontology-mutations.d.ts.map +1 -1
- package/dist/shares/internal.js +4 -4
- package/dist/storage/connector-connections/in-memory.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider/runs.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider.js +2 -2
- package/dist/storage/connector-connections/provider.js.map +3 -3
- package/dist/storage/connector-connections/types.d.ts +3 -2
- package/dist/storage/connector-connections/types.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +4 -4
- package/dist/storage/internal.js +1 -1
- package/dist/storage/ontology/in-memory/materializations-state.d.ts.map +1 -1
- package/dist/storage/ontology/in-memory/materializations.d.ts +1 -1
- package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
- package/dist/storage/ontology/in-memory/shared-state.d.ts +15 -2
- package/dist/storage/ontology/in-memory/shared-state.d.ts.map +1 -1
- package/dist/storage/ontology/in-memory/source-roots.d.ts +6 -0
- package/dist/storage/ontology/in-memory/source-roots.d.ts.map +1 -0
- package/dist/storage/ontology/in-memory/sources.d.ts.map +1 -1
- package/dist/storage/ontology/index.d.ts +1 -1
- package/dist/storage/ontology/index.d.ts.map +1 -1
- package/dist/storage/ontology/provider.d.ts +16 -0
- package/dist/storage/ontology/provider.d.ts.map +1 -1
- package/dist/storage/ontology/provider.js +4 -2
- package/dist/storage/ontology/provider.js.map +1 -1
- package/dist/storage/ontology/sources.d.ts +13 -4
- package/dist/storage/ontology/sources.d.ts.map +1 -1
- package/dist/storage/projection-runs/lifecycle.d.ts.map +1 -1
- package/dist/storage/projection-runs/provider.js +2 -2
- package/dist/storage/projection-runs/types.d.ts +2 -0
- package/dist/storage/projection-runs/types.d.ts.map +1 -1
- package/dist/syncs/index.js +8 -7
- package/dist/syncs/index.js.map +1 -1
- package/dist/syncs/run-dispatch.d.ts.map +1 -1
- package/dist/testing/broker-contract.d.ts.map +1 -1
- package/dist/testing/connector-connection-storage-contract.d.ts.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1459 -951
- package/dist/testing/index.js.map +9 -8
- package/dist/testing/lake-changes-contract.d.ts +6 -0
- package/dist/testing/lake-changes-contract.d.ts.map +1 -0
- package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
- package/dist/testing/ontology-storage-contract.d.ts.map +1 -1
- package/dist/testing/projection-run-storage-contract.d.ts.map +1 -1
- package/dist/webhooks/connections.d.ts +6 -0
- package/dist/webhooks/connections.d.ts.map +1 -0
- package/dist/webhooks/index.d.ts +1 -1
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/internal.d.ts +2 -0
- package/dist/webhooks/internal.d.ts.map +1 -1
- package/dist/webhooks/internal.js +54 -9
- package/dist/webhooks/internal.js.map +4 -3
- package/dist/webhooks/registry.d.ts +1 -1
- package/dist/webhooks/registry.d.ts.map +1 -1
- package/dist/webhooks/types.d.ts +15 -4
- package/dist/webhooks/types.d.ts.map +1 -1
- package/dist/workflows/index.js +17 -16
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/run-dispatch.d.ts +3 -1
- package/dist/workflows/run-dispatch.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/broker/in-memory.ts +104 -39
- package/src/broker/index.ts +4 -1
- package/src/broker/retention.ts +28 -0
- package/src/broker/subscriber.ts +21 -0
- package/src/broker/types.ts +15 -1
- package/src/connectors/builders.ts +0 -6
- package/src/connectors/connections/authorizations.ts +11 -5
- package/src/connectors/connections/contracts.ts +3 -0
- package/src/connectors/connections/request.ts +56 -22
- package/src/connectors/connections/runs.ts +1 -0
- package/src/connectors/connections/tokens.ts +4 -0
- package/src/connectors/connections/validation.ts +91 -5
- package/src/connectors/types.ts +19 -4
- package/src/events/execution.ts +2 -2
- package/src/events/service.ts +3 -3
- package/src/index.ts +10 -2
- package/src/lake-storage/changes.ts +43 -0
- package/src/lake-storage/in-memory.ts +73 -0
- package/src/lake-storage/index.ts +4 -0
- package/src/lake-storage/types.ts +40 -0
- package/src/logging/service.ts +1 -1
- package/src/materialization/index.ts +2 -0
- package/src/materialization/model.ts +14 -1
- package/src/materializer/index.ts +2 -0
- package/src/materializer/materializer.ts +10 -0
- package/src/materializer/projections/entry-validator.ts +19 -1
- package/src/materializer/projections/replace.ts +36 -1
- package/src/materializer/projections/source-materialization.ts +26 -6
- package/src/materializer/shared/normalize.ts +1 -1
- package/src/pipelines/run-dispatch.ts +21 -1
- package/src/projections/run-dispatch.ts +30 -68
- package/src/queues/in-memory.ts +105 -62
- package/src/queues/indexed-heap.ts +74 -0
- package/src/runtime/host.ts +0 -14
- package/src/runtime/ontology-mutations.ts +4 -0
- package/src/storage/connector-connections/in-memory.ts +3 -1
- package/src/storage/connector-connections/provider/runs.ts +3 -1
- package/src/storage/connector-connections/types.ts +3 -2
- package/src/storage/index.ts +1 -0
- package/src/storage/ontology/in-memory/materializations-state.ts +5 -18
- package/src/storage/ontology/in-memory/materializations.ts +73 -92
- package/src/storage/ontology/in-memory/shared-state.ts +26 -1
- package/src/storage/ontology/in-memory/source-roots.ts +123 -0
- package/src/storage/ontology/in-memory/sources.ts +78 -23
- package/src/storage/ontology/index.ts +1 -0
- package/src/storage/ontology/provider.ts +70 -0
- package/src/storage/ontology/sources.ts +14 -3
- package/src/storage/projection-runs/lifecycle.ts +5 -2
- package/src/storage/projection-runs/types.ts +4 -3
- package/src/syncs/run-dispatch.ts +19 -1
- package/src/testing/broker-contract.ts +95 -0
- package/src/testing/connector-connection-storage-contract.ts +18 -1
- package/src/testing/index.ts +1 -0
- package/src/testing/lake-changes-contract.ts +240 -0
- package/src/testing/materializer-storage-contract.ts +172 -1
- package/src/testing/ontology-storage-contract.ts +7 -0
- package/src/testing/projection-run-storage-contract.ts +34 -0
- package/src/webhooks/connections.ts +56 -0
- package/src/webhooks/index.ts +2 -0
- package/src/webhooks/internal.ts +2 -0
- package/src/webhooks/registry.ts +3 -4
- package/src/webhooks/types.ts +17 -4
- package/src/workflows/run-dispatch.ts +42 -29
- package/dist/chunk-0rdrecsb.js.map +0 -11
- package/dist/chunk-3f7veca0.js.map +0 -10
- package/dist/chunk-3frsnm7x.js.map +0 -11
- package/dist/chunk-52hbv0ak.js.map +0 -36
- package/dist/chunk-emyyk7dx.js.map +0 -14
- package/dist/chunk-jjy0fhrn.js.map +0 -12
- package/dist/chunk-k3cqkkn2.js.map +0 -12
- package/dist/chunk-ppytyr6w.js.map +0 -12
- package/dist/chunk-qhcpj9wc.js.map +0 -14
- package/dist/chunk-r29fxdhn.js.map +0 -11
- package/dist/chunk-zcnq6105.js.map +0 -10
- /package/dist/{chunk-h1gfhhbh.js.map → chunk-28ja2cjs.js.map} +0 -0
- /package/dist/{chunk-1enhpacz.js.map → chunk-976rxdpx.js.map} +0 -0
- /package/dist/{chunk-r4tfx1sf.js.map → chunk-agbbygvh.js.map} +0 -0
- /package/dist/{chunk-0w35wavj.js.map → chunk-g7knyczr.js.map} +0 -0
- /package/dist/{chunk-ypbtykv6.js.map → chunk-nmbxcaqx.js.map} +0 -0
- /package/dist/{chunk-vf691g1r.js.map → chunk-p5vcsw9c.js.map} +0 -0
- /package/dist/{chunk-xyr8kppc.js.map → chunk-rtk71jzp.js.map} +0 -0
- /package/dist/{chunk-m3z3x9v0.js.map → chunk-t1f4f1ve.js.map} +0 -0
package/src/broker/in-memory.ts
CHANGED
|
@@ -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[]) =>
|
|
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
|
|
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[]) =>
|
|
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
|
-
|
|
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
|
|
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
|
|
354
|
-
if (
|
|
355
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/broker/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/broker/types.ts
CHANGED
|
@@ -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[]) =>
|
|
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
|
-
{
|
|
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
|
|
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 =
|
|
185
|
-
|
|
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
|
-
{
|
|
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 =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
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,
|