@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.
- package/dist/actions/index.js +9 -9
- package/dist/agents/index.js +17 -16
- package/dist/agents/index.js.map +1 -1
- package/dist/blob-storage/types.d.ts +4 -1
- package/dist/blob-storage/types.d.ts.map +1 -1
- package/dist/bootstrap/index.js +21 -20
- 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-ypbtykv6.js → chunk-01kj2pbh.js} +1 -1
- package/dist/{chunk-k3cqkkn2.js → chunk-1824gssz.js} +30 -65
- package/dist/chunk-1824gssz.js.map +12 -0
- package/dist/{chunk-xyr8kppc.js → chunk-1yefycb1.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-m3z3x9v0.js → chunk-3ckk0xfj.js} +2 -2
- 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-52hbv0ak.js → chunk-4zhmdsx6.js} +141 -55
- package/dist/chunk-4zhmdsx6.js.map +36 -0
- package/dist/{chunk-zcnq6105.js → chunk-85cd7e8c.js} +2 -1
- package/dist/chunk-85cd7e8c.js.map +10 -0
- package/dist/{chunk-3f7veca0.js → chunk-9x8dybsb.js} +3 -3
- package/dist/chunk-9x8dybsb.js.map +10 -0
- package/dist/{chunk-0w35wavj.js → chunk-a0e6x5hd.js} +1 -1
- package/dist/{chunk-r29fxdhn.js → chunk-aa6z4a19.js} +160 -44
- package/dist/chunk-aa6z4a19.js.map +12 -0
- package/dist/{chunk-emyyk7dx.js → chunk-cfvakeax.js} +22 -145
- package/dist/chunk-cfvakeax.js.map +12 -0
- package/dist/chunk-d62kjtbg.js.map +1 -1
- package/dist/{chunk-h1gfhhbh.js → chunk-d63nnr5b.js} +2 -2
- package/dist/{chunk-r4tfx1sf.js → chunk-fh8kgex1.js} +5 -5
- 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-jjy0fhrn.js → chunk-g91qxjj0.js} +20 -3
- package/dist/chunk-g91qxjj0.js.map +12 -0
- package/dist/{chunk-hpr1r6av.js → chunk-gq8vhakp.js} +51 -40
- package/dist/{chunk-hpr1r6av.js.map → chunk-gq8vhakp.js.map} +6 -6
- 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-mjbk2djy.js → chunk-gqm76kse.js} +231 -125
- package/dist/{chunk-mjbk2djy.js.map → chunk-gqm76kse.js.map} +9 -8
- package/dist/{chunk-e0d1rzef.js → chunk-jsy70480.js} +16 -3
- package/dist/chunk-jsy70480.js.map +15 -0
- package/dist/{chunk-0rdrecsb.js → chunk-kfee1h52.js} +116 -32
- package/dist/chunk-kfee1h52.js.map +13 -0
- package/dist/{chunk-3frsnm7x.js → chunk-qma80ych.js} +4 -6
- package/dist/chunk-qma80ych.js.map +11 -0
- package/dist/{chunk-vf691g1r.js → chunk-sepyxgpc.js} +3 -3
- package/dist/chunk-v2vcg0w6.js +151 -0
- package/dist/chunk-v2vcg0w6.js.map +11 -0
- package/dist/{chunk-3tgwrcyh.js → chunk-wf88w4kp.js} +17 -23
- package/dist/chunk-wf88w4kp.js.map +14 -0
- package/dist/{chunk-xfbydgzy.js → chunk-x9m9x2eq.js} +7 -8
- package/dist/{chunk-xfbydgzy.js.map → chunk-x9m9x2eq.js.map} +3 -3
- package/dist/{chunk-qhcpj9wc.js → chunk-z0271qkp.js} +98 -2
- package/dist/chunk-z0271qkp.js.map +15 -0
- package/dist/{chunk-1enhpacz.js → chunk-z0s4axeh.js} +6 -6
- package/dist/connectors/connections/authorizations.d.ts.map +1 -1
- package/dist/connectors/connections/command-context.d.ts +2 -0
- package/dist/connectors/connections/command-context.d.ts.map +1 -1
- package/dist/connectors/connections/contracts.d.ts +4 -1
- package/dist/connectors/connections/contracts.d.ts.map +1 -1
- package/dist/connectors/connections/execution.d.ts +1 -0
- package/dist/connectors/connections/execution.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 +1 -0
- package/dist/connectors/connections/runs.d.ts.map +1 -1
- package/dist/connectors/connections/service.d.ts +2 -0
- package/dist/connectors/connections/service.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/outbox-dispatcher.d.ts +2 -2
- package/dist/events/outbox-dispatcher.d.ts.map +1 -1
- package/dist/events/service.d.ts +2 -2
- package/dist/events/service.d.ts.map +1 -1
- package/dist/execution/agent.js +17 -16
- package/dist/execution/agent.js.map +1 -1
- package/dist/execution/primitive.js +17 -16
- package/dist/execution/primitive.js.map +2 -2
- package/dist/execution/request.js +17 -16
- 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 +31 -29
- 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 +38 -0
- package/dist/lake-storage/types.d.ts.map +1 -1
- package/dist/logging/internal.js +1 -1
- package/dist/maintenance/ontology-maintenance.d.ts.map +1 -1
- package/dist/maintenance/types.d.ts +1 -1
- package/dist/maintenance/types.d.ts.map +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 +6 -6
- 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/definitions.d.ts +7 -0
- package/dist/models/definitions.d.ts.map +1 -1
- package/dist/models/execution/index.js +6 -6
- package/dist/models/index.js +1 -1
- package/dist/objects/index.js +10 -10
- package/dist/objects/query/index.js +6 -6
- package/dist/pipelines/index.js +7 -7
- package/dist/pipelines/run-dispatch.d.ts.map +1 -1
- package/dist/projections/internal.js +7 -9
- 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 +5 -5
- package/dist/storage/agents/provider.js +1 -1
- package/dist/storage/connector-connections/in-memory.d.ts +1 -0
- package/dist/storage/connector-connections/in-memory.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider/connections.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider/runs.d.ts +1 -0
- package/dist/storage/connector-connections/provider/runs.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider/shared.d.ts +1 -0
- package/dist/storage/connector-connections/provider/shared.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider.d.ts +1 -0
- package/dist/storage/connector-connections/provider.d.ts.map +1 -1
- package/dist/storage/connector-connections/provider.js +11 -17
- package/dist/storage/connector-connections/provider.js.map +6 -6
- package/dist/storage/connector-connections/types.d.ts +6 -3
- 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 +5 -5
- 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/outbox.d.ts +1 -0
- package/dist/storage/ontology/outbox.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 +9 -8
- 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 +1499 -963
- package/dist/testing/index.js.map +10 -9
- package/dist/testing/lake-changes-contract.d.ts +6 -0
- package/dist/testing/lake-changes-contract.d.ts.map +1 -0
- package/dist/testing/lake-storage-contract.d.ts.map +1 -1
- 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 +55 -10
- 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 +18 -17
- 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/agents/model-loop.ts +7 -0
- package/src/blob-storage/types.ts +4 -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/command-context.ts +21 -4
- package/src/connectors/connections/contracts.ts +7 -1
- package/src/connectors/connections/execution.ts +3 -0
- package/src/connectors/connections/request.ts +56 -22
- package/src/connectors/connections/runs.ts +39 -1
- package/src/connectors/connections/service.ts +6 -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/outbox-dispatcher.ts +20 -26
- 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 +75 -0
- package/src/lake-storage/index.ts +4 -0
- package/src/lake-storage/types.ts +42 -0
- package/src/logging/service.ts +1 -1
- package/src/maintenance/ontology-maintenance.ts +6 -2
- package/src/maintenance/types.ts +1 -1
- package/src/materialization/index.ts +2 -0
- package/src/materialization/model.ts +14 -1
- package/src/materializer/README.md +1 -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/models/definitions.ts +19 -0
- package/src/models/pricing.ts +6 -2
- 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 +19 -19
- package/src/storage/connector-connections/provider/connections.ts +1 -15
- package/src/storage/connector-connections/provider/runs.ts +9 -1
- package/src/storage/connector-connections/provider/shared.ts +2 -3
- package/src/storage/connector-connections/provider.ts +6 -0
- package/src/storage/connector-connections/types.ts +8 -3
- 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 +36 -4
- 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/outbox.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 +29 -12
- package/src/testing/index.ts +1 -0
- package/src/testing/lake-changes-contract.ts +240 -0
- package/src/testing/lake-storage-contract.ts +29 -0
- package/src/testing/materializer-storage-contract.ts +172 -1
- package/src/testing/ontology-storage-contract.ts +15 -3
- 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-3tgwrcyh.js.map +0 -14
- package/dist/chunk-52hbv0ak.js.map +0 -36
- package/dist/chunk-e0d1rzef.js.map +0 -15
- 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-ypbtykv6.js.map → chunk-01kj2pbh.js.map} +0 -0
- /package/dist/{chunk-xyr8kppc.js.map → chunk-1yefycb1.js.map} +0 -0
- /package/dist/{chunk-m3z3x9v0.js.map → chunk-3ckk0xfj.js.map} +0 -0
- /package/dist/{chunk-0w35wavj.js.map → chunk-a0e6x5hd.js.map} +0 -0
- /package/dist/{chunk-h1gfhhbh.js.map → chunk-d63nnr5b.js.map} +0 -0
- /package/dist/{chunk-r4tfx1sf.js.map → chunk-fh8kgex1.js.map} +0 -0
- /package/dist/{chunk-vf691g1r.js.map → chunk-sepyxgpc.js.map} +0 -0
- /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
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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,
|
package/src/connectors/types.ts
CHANGED
|
@@ -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
|
-
|
|
79
|
-
readonly
|
|
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
|
-
|
|
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?:
|
|
149
|
+
readonly webhooks?: readonly WebhookDefinition<unknown, TClient>[]
|
|
135
150
|
discoverAccounts(
|
|
136
151
|
context: ConnectorContext,
|
|
137
152
|
credentials: ConnectorOAuthCredentials
|
package/src/events/execution.ts
CHANGED
|
@@ -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[]) =>
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.draining
|
|
208
|
-
this.pendingDrain
|
|
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
|
-
|
|
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 {
|
package/src/events/service.ts
CHANGED
|
@@ -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[]) =>
|
|
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[]) =>
|
|
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 {
|
|
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[]
|