@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
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
ProjectionCommitResult,
|
|
11
11
|
ProjectionExecution,
|
|
12
12
|
ProjectionMaterializationIdentity,
|
|
13
|
+
ProjectionSourceBase,
|
|
14
|
+
ProjectionSourceDeletion,
|
|
13
15
|
ProjectionSourceEntry,
|
|
14
16
|
ProjectionSourceRef,
|
|
15
17
|
ProjectionSourceReplacement,
|
|
@@ -19,6 +21,7 @@ import type {
|
|
|
19
21
|
OntologyCommitRecord,
|
|
20
22
|
OntologyCommitWrite,
|
|
21
23
|
OntologyMaterializationStorage,
|
|
24
|
+
OntologySourceRecord,
|
|
22
25
|
OntologyStorage,
|
|
23
26
|
} from "../../storage/ontology"
|
|
24
27
|
import type { MaterializerContext, MaterializerStorage } from "../context"
|
|
@@ -60,7 +63,8 @@ interface PreparedProjectionReplacement {
|
|
|
60
63
|
readonly source: ProjectionSourceRef
|
|
61
64
|
readonly datasetVersion: PinnedDatasetVersion
|
|
62
65
|
readonly execution: ProjectionExecution
|
|
63
|
-
readonly entries: AsyncIterable<ProjectionSourceEntry>
|
|
66
|
+
readonly entries: AsyncIterable<ProjectionSourceEntry | ProjectionSourceDeletion>
|
|
67
|
+
readonly base?: ProjectionSourceBase
|
|
64
68
|
readonly signal?: AbortSignal
|
|
65
69
|
readonly resolved: ResolvedSourceProjection
|
|
66
70
|
readonly projectionKind: "object" | "link"
|
|
@@ -128,6 +132,7 @@ function prepareProjectionReplacement(
|
|
|
128
132
|
datasetVersion,
|
|
129
133
|
execution,
|
|
130
134
|
entries: raw.input.entries,
|
|
135
|
+
...(raw.input.base ? { base: Object.freeze({ ...raw.input.base }) } : {}),
|
|
131
136
|
resolved,
|
|
132
137
|
projectionKind,
|
|
133
138
|
runIdentity,
|
|
@@ -237,6 +242,7 @@ async function prepareProjectionCandidate(
|
|
|
237
242
|
source: command.source,
|
|
238
243
|
})
|
|
239
244
|
validateProjectionWatermark(active, command.datasetVersion)
|
|
245
|
+
assertDeltaBase(active, command)
|
|
240
246
|
return {
|
|
241
247
|
materializationId: context.materializationId(),
|
|
242
248
|
createdAt: context.clock().toISOString(),
|
|
@@ -263,6 +269,7 @@ async function stageProjectionCandidate(
|
|
|
263
269
|
ownershipHash: command.resolved.ownershipHash,
|
|
264
270
|
createdAt: candidate.createdAt,
|
|
265
271
|
entries: command.entries,
|
|
272
|
+
...(command.base ? { base: command.base } : {}),
|
|
266
273
|
validateEntry: createProjectionEntryValidator(context.ontology, command.resolved),
|
|
267
274
|
}
|
|
268
275
|
const staged = await stageCandidateEntries(context, input, command.signal)
|
|
@@ -312,6 +319,14 @@ async function executeProjectionTransaction(
|
|
|
312
319
|
)
|
|
313
320
|
if (replay) return projectionReplayResult(replay, command)
|
|
314
321
|
|
|
322
|
+
if (command.base) {
|
|
323
|
+
const active = await storage.ontology.sources.getActive({
|
|
324
|
+
projectId: context.projectId,
|
|
325
|
+
source: command.source,
|
|
326
|
+
})
|
|
327
|
+
validateProjectionWatermark(active, command.datasetVersion)
|
|
328
|
+
assertDeltaBase(active, command)
|
|
329
|
+
}
|
|
315
330
|
const origin = projectionOrigin(command)
|
|
316
331
|
throwIfAborted(command.signal)
|
|
317
332
|
const session = await storage.ontology.materializations.begin({
|
|
@@ -478,3 +493,23 @@ function validateProjectionWatermark(
|
|
|
478
493
|
if (!active) return
|
|
479
494
|
assertPinnedDatasetWatermark(active.datasetVersion, next, "Projection replacement")
|
|
480
495
|
}
|
|
496
|
+
|
|
497
|
+
function assertDeltaBase(
|
|
498
|
+
active: OntologySourceRecord | null,
|
|
499
|
+
command: PreparedProjectionReplacement
|
|
500
|
+
): void {
|
|
501
|
+
if (
|
|
502
|
+
command.base &&
|
|
503
|
+
(!active ||
|
|
504
|
+
active.materializationId !== command.base.materializationId ||
|
|
505
|
+
active.lastCommitId !== command.base.lastCommitId ||
|
|
506
|
+
active.projectionRevision !== command.runIdentity.projectionRevision ||
|
|
507
|
+
active.ontologyRevision !== command.runIdentity.ontologyRevision ||
|
|
508
|
+
active.ownershipHash !== command.runIdentity.ownershipHash)
|
|
509
|
+
) {
|
|
510
|
+
throw new MaterializationConflictError(
|
|
511
|
+
"source-materialization",
|
|
512
|
+
"Projection delta no longer matches the active source or its definition."
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { MaterializationValidationError } from "../../materialization/errors"
|
|
1
2
|
import type {
|
|
2
3
|
PinnedDatasetVersion,
|
|
3
4
|
ProjectionExecution,
|
|
5
|
+
ProjectionSourceBase,
|
|
6
|
+
ProjectionSourceDeletion,
|
|
4
7
|
ProjectionSourceEntry,
|
|
5
8
|
ProjectionSourceRef,
|
|
6
9
|
} from "../../materialization/model"
|
|
7
10
|
import { utf8JsonByteLength } from "../../materialization/refs"
|
|
8
|
-
import type { StageSourceAssertion } from "../../storage/ontology"
|
|
11
|
+
import type { StageSourceAssertion, StageSourceRoot } from "../../storage/ontology"
|
|
9
12
|
import type { MaterializerContext } from "../context"
|
|
10
13
|
import { throwIfAborted } from "../shared/abort"
|
|
11
14
|
import { chunkBySize } from "../shared/chunking"
|
|
12
|
-
import { normalizeProjectionSourceEntry } from "../shared/normalize"
|
|
13
|
-
import type
|
|
15
|
+
import { normalizeProjectionEntity, normalizeProjectionSourceEntry } from "../shared/normalize"
|
|
16
|
+
import { type ProjectionEntryValidator, validateProjectionRootRef } from "./entry-validator"
|
|
14
17
|
|
|
15
18
|
export interface StagedProjectionMaterialization {
|
|
16
19
|
readonly rootCount: number
|
|
@@ -37,7 +40,8 @@ export async function stageProjectionMaterialization(
|
|
|
37
40
|
readonly projectionRevision: string
|
|
38
41
|
readonly ownershipHash: string
|
|
39
42
|
readonly createdAt: string
|
|
40
|
-
readonly entries: AsyncIterable<ProjectionSourceEntry>
|
|
43
|
+
readonly entries: AsyncIterable<ProjectionSourceEntry | ProjectionSourceDeletion>
|
|
44
|
+
readonly base?: ProjectionSourceBase
|
|
41
45
|
readonly validateEntry: ProjectionEntryValidator
|
|
42
46
|
readonly signal?: AbortSignal
|
|
43
47
|
}
|
|
@@ -54,13 +58,28 @@ export async function stageProjectionMaterialization(
|
|
|
54
58
|
ownershipHash: input.ownershipHash,
|
|
55
59
|
ontologyRevision: context.projectionRegistry.ontologyRevision,
|
|
56
60
|
createdAt: input.createdAt,
|
|
61
|
+
...(input.base ? { base: input.base } : {}),
|
|
57
62
|
})
|
|
58
63
|
|
|
59
64
|
let rootCount = 0
|
|
60
65
|
let assertionCount = 0
|
|
61
|
-
async function* assertions(): AsyncIterable<StageSourceAssertion> {
|
|
66
|
+
async function* assertions(): AsyncIterable<StageSourceAssertion | StageSourceRoot> {
|
|
62
67
|
for await (const rawEntry of input.entries) {
|
|
63
68
|
throwIfAborted(input.signal)
|
|
69
|
+
if ("deleted" in rawEntry) {
|
|
70
|
+
if (!input.base || rawEntry.deleted !== true) {
|
|
71
|
+
throw new MaterializationValidationError(
|
|
72
|
+
"Explicit root deletions require a pinned source base."
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
const root = normalizeProjectionEntity(rawEntry.root)
|
|
76
|
+
validateProjectionRootRef(
|
|
77
|
+
context.projectionRegistry.resolveSource(input.source.projectionId),
|
|
78
|
+
root
|
|
79
|
+
)
|
|
80
|
+
yield { root, stagingOrdinal: rootCount++ }
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
64
83
|
const entry = input.validateEntry(normalizeProjectionSourceEntry(rawEntry))
|
|
65
84
|
const stagingOrdinal = rootCount
|
|
66
85
|
for (const assertion of entry.assertions) {
|
|
@@ -82,7 +101,8 @@ export async function stageProjectionMaterialization(
|
|
|
82
101
|
source: input.source,
|
|
83
102
|
materializationId: input.materializationId,
|
|
84
103
|
execution: input.execution,
|
|
85
|
-
rows,
|
|
104
|
+
rows: rows.filter((row): row is StageSourceAssertion => "assertion" in row),
|
|
105
|
+
deletions: rows.filter((row) => !("assertion" in row)),
|
|
86
106
|
})
|
|
87
107
|
}
|
|
88
108
|
|
|
@@ -344,7 +344,7 @@ export function normalizeProjectionExecution(input: ProjectionExecution): Projec
|
|
|
344
344
|
})
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
function normalizeProjectionEntity(entity: ProjectionEntityRef): ProjectionEntityRef {
|
|
347
|
+
export function normalizeProjectionEntity(entity: ProjectionEntityRef): ProjectionEntityRef {
|
|
348
348
|
return entity.kind === "object"
|
|
349
349
|
? Object.freeze({ kind: "object", ref: normalizeObjectRef(entity.ref) })
|
|
350
350
|
: Object.freeze({ kind: "link", ref: normalizeLinkRef(entity.ref) })
|
|
@@ -101,7 +101,10 @@ export async function dispatchPipelineRun(
|
|
|
101
101
|
input: DispatchPipelineRunInput
|
|
102
102
|
): Promise<PipelineRunRequestResult> {
|
|
103
103
|
const runId = createPipelineRunId(input.runId)
|
|
104
|
-
|
|
104
|
+
let persisted = await persistPipelineRun(input, runId)
|
|
105
|
+
if (!persisted.publish && input.validateExistingRun && persisted.run.status === "queued") {
|
|
106
|
+
persisted = await reusePipelineRun(input, persisted.run)
|
|
107
|
+
}
|
|
105
108
|
if (!persisted.publish) return existingPipelineRunResult(persisted.run)
|
|
106
109
|
return publishPipelineRun(input, persisted)
|
|
107
110
|
}
|
|
@@ -155,6 +158,15 @@ async function reusePipelineRun(
|
|
|
155
158
|
): Promise<PersistedPipelineRun> {
|
|
156
159
|
assertExistingRunMatchesRequest(existing, input)
|
|
157
160
|
await input.validateExistingRun?.(existing, input.storage)
|
|
161
|
+
if (input.validateExistingRun && existing.status === "queued") {
|
|
162
|
+
const execution = await input.storage.executions.getById({
|
|
163
|
+
projectId: input.projectId,
|
|
164
|
+
id: existing.executionId,
|
|
165
|
+
})
|
|
166
|
+
if (!execution)
|
|
167
|
+
throw new PipelineRunError(`[Sixb] Execution '${existing.executionId}' was not found.`)
|
|
168
|
+
return { publish: true, run: existing, execution, queuedAt: existing.queuedAt, created: false }
|
|
169
|
+
}
|
|
158
170
|
if (
|
|
159
171
|
existing.status !== "failed" ||
|
|
160
172
|
existing.error?.code !== "queue.enqueue_failed" ||
|
|
@@ -200,6 +212,11 @@ async function publishPipelineRun(
|
|
|
200
212
|
},
|
|
201
213
|
],
|
|
202
214
|
})
|
|
215
|
+
if (!job || job.id !== persisted.run.id) {
|
|
216
|
+
throw new PipelineRunError(
|
|
217
|
+
`[Sixb] Queue did not acknowledge Pipeline run '${persisted.run.id}'.`
|
|
218
|
+
)
|
|
219
|
+
}
|
|
203
220
|
return {
|
|
204
221
|
pipelineId: persisted.run.pipelineId,
|
|
205
222
|
runId: persisted.run.id,
|
|
@@ -208,6 +225,9 @@ async function publishPipelineRun(
|
|
|
208
225
|
created: persisted.created,
|
|
209
226
|
}
|
|
210
227
|
} catch (error) {
|
|
228
|
+
// Automatic consumers retain the batch and retry. Keep uncertain delivery queued:
|
|
229
|
+
// a lost reply does not prove that the queue rejected the job.
|
|
230
|
+
if (input.validateExistingRun) throw error
|
|
211
231
|
const failedAt = new Date()
|
|
212
232
|
const failure = toEnqueueFailure(error, persisted.run, failedAt)
|
|
213
233
|
const failed = await requirePipelineRunStorage(input.storage).finish({
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto"
|
|
2
|
-
import { reportRunFailure } from "../error-reporting/capability"
|
|
3
|
-
import { createSixbError, toSixbFailure } from "../errors/internal"
|
|
4
|
-
import type { SixbFailure } from "../errors/types"
|
|
5
2
|
import type { RunDispatcher } from "../execution/dispatch"
|
|
6
3
|
import { createPrimitiveExecutionRecord } from "../execution/durable"
|
|
7
4
|
import type { DatasetProducer, LakeStorage } from "../lake-storage"
|
|
@@ -17,11 +14,7 @@ import {
|
|
|
17
14
|
import { getProjectionRegistry } from "./capability"
|
|
18
15
|
import { ProjectionValidationError } from "./errors"
|
|
19
16
|
import { createProjectionRunId } from "./run-id"
|
|
20
|
-
import {
|
|
21
|
-
PROJECTION_RUN_FAILURE_CODES,
|
|
22
|
-
type ProjectionDefinition,
|
|
23
|
-
type ProjectionRunFailureCode,
|
|
24
|
-
} from "./types"
|
|
17
|
+
import type { ProjectionDefinition } from "./types"
|
|
25
18
|
|
|
26
19
|
/** Target maximum mapped points per telemetry commit; a single-property projection reads 500 rows. */
|
|
27
20
|
export const PROJECTION_TELEMETRY_BATCH_SIZE = 500
|
|
@@ -106,7 +99,7 @@ export class ProjectionRunDispatcher implements ProjectionRunDispatchPort {
|
|
|
106
99
|
const runId = createProjectionRunId(this.dependencies.id, identity)
|
|
107
100
|
const projectionRuns = requireProjectionRunStorage(this.dependencies.storage)
|
|
108
101
|
const existing = await projectionRuns.getById({ projectId: this.dependencies.id, id: runId })
|
|
109
|
-
|
|
102
|
+
let persisted = existing
|
|
110
103
|
? await reuseProjectionRun(this.dependencies, existing, identity)
|
|
111
104
|
: await persistProjectionRun({
|
|
112
105
|
dependencies: this.dependencies,
|
|
@@ -120,6 +113,9 @@ export class ProjectionRunDispatcher implements ProjectionRunDispatchPort {
|
|
|
120
113
|
),
|
|
121
114
|
})
|
|
122
115
|
|
|
116
|
+
if (!persisted.publish && persisted.run.status === "queued") {
|
|
117
|
+
persisted = await reuseProjectionRun(this.dependencies, persisted.run, identity)
|
|
118
|
+
}
|
|
123
119
|
if (!persisted.publish) return existingResult(persisted.run)
|
|
124
120
|
return publishProjectionRun(this.dependencies, input.metadata, persisted)
|
|
125
121
|
}
|
|
@@ -195,6 +191,9 @@ async function reuseProjectionRun(
|
|
|
195
191
|
identity: ProjectionRunRecord["identity"]
|
|
196
192
|
): Promise<PersistedProjectionRun> {
|
|
197
193
|
await assertExistingRun(dependencies.storage, existing, identity)
|
|
194
|
+
if (existing.status === "queued") {
|
|
195
|
+
return { publish: true, run: existing, queuedAt: existing.queuedAt, created: false }
|
|
196
|
+
}
|
|
198
197
|
if (
|
|
199
198
|
existing.status !== "failed" ||
|
|
200
199
|
existing.error?.code !== "queue.enqueue_failed" ||
|
|
@@ -228,45 +227,29 @@ async function publishProjectionRun(
|
|
|
228
227
|
metadata: Readonly<Record<string, string>> | undefined,
|
|
229
228
|
persisted: Extract<PersistedProjectionRun, { readonly publish: true }>
|
|
230
229
|
): Promise<ProjectionRunDispatchResult> {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
})
|
|
243
|
-
return {
|
|
244
|
-
projectionId: persisted.run.identity.projectionId,
|
|
245
|
-
runId: persisted.run.id,
|
|
246
|
-
queuedAt: persisted.queuedAt.toISOString(),
|
|
247
|
-
...(job?.id ? { jobId: job.id } : {}),
|
|
248
|
-
created: persisted.created,
|
|
249
|
-
}
|
|
250
|
-
} catch (error) {
|
|
251
|
-
const failedAt = new Date()
|
|
252
|
-
const failure = toEnqueueFailure(error, persisted.run, failedAt)
|
|
253
|
-
const failed = await requireProjectionRunStorage(dependencies.storage).failEnqueue({
|
|
254
|
-
projectId: dependencies.id,
|
|
255
|
-
id: persisted.run.id,
|
|
256
|
-
finishedAt: failedAt,
|
|
257
|
-
error: failure,
|
|
258
|
-
})
|
|
259
|
-
reportRunFailure(dependencies, error, {
|
|
260
|
-
projectId: dependencies.id,
|
|
261
|
-
runKind: "projection",
|
|
262
|
-
run: {
|
|
263
|
-
runId: failed.id,
|
|
264
|
-
projectionId: failed.identity.projectionId,
|
|
265
|
-
projectionKind: failed.identity.projectionKind,
|
|
230
|
+
// Keep the run queued on uncertain publication; the caller retries the same identity.
|
|
231
|
+
const [job] = await dependencies.queues.projections.enqueue({
|
|
232
|
+
projectId: dependencies.id,
|
|
233
|
+
jobs: [
|
|
234
|
+
{
|
|
235
|
+
id: persisted.run.id,
|
|
236
|
+
type: "projection.run.requested",
|
|
237
|
+
payload: { runId: persisted.run.id },
|
|
238
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
266
239
|
},
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
240
|
+
],
|
|
241
|
+
})
|
|
242
|
+
if (!job || job.id !== persisted.run.id) {
|
|
243
|
+
throw new ProjectionRunError(
|
|
244
|
+
`[Sixb] Queue did not acknowledge Projection run '${persisted.run.id}'.`
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
projectionId: persisted.run.identity.projectionId,
|
|
249
|
+
runId: persisted.run.id,
|
|
250
|
+
queuedAt: persisted.queuedAt.toISOString(),
|
|
251
|
+
...(job?.id ? { jobId: job.id } : {}),
|
|
252
|
+
created: persisted.created,
|
|
270
253
|
}
|
|
271
254
|
}
|
|
272
255
|
|
|
@@ -478,24 +461,3 @@ function assertDispatchInput(input: ProjectionRunDispatchInput): void {
|
|
|
478
461
|
throw new ProjectionValidationError("[Sixb] Projection dataset version timestamp is invalid.")
|
|
479
462
|
}
|
|
480
463
|
}
|
|
481
|
-
|
|
482
|
-
function toEnqueueFailure(
|
|
483
|
-
error: unknown,
|
|
484
|
-
run: ProjectionRunRecord,
|
|
485
|
-
at: Date
|
|
486
|
-
): SixbFailure<ProjectionRunFailureCode> {
|
|
487
|
-
const enqueueError = createSixbError(
|
|
488
|
-
"queue.enqueue_failed",
|
|
489
|
-
`[Sixb] Could not enqueue Projection run '${run.id}'.`,
|
|
490
|
-
{
|
|
491
|
-
cause: error,
|
|
492
|
-
details: {
|
|
493
|
-
projectionId: run.identity.projectionId,
|
|
494
|
-
projectionKind: run.identity.projectionKind,
|
|
495
|
-
runId: run.id,
|
|
496
|
-
phase: "enqueue",
|
|
497
|
-
},
|
|
498
|
-
}
|
|
499
|
-
)
|
|
500
|
-
return toSixbFailure(enqueueError, { allowedCodes: PROJECTION_RUN_FAILURE_CODES, at })
|
|
501
|
-
}
|
package/src/queues/in-memory.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto"
|
|
2
2
|
import type { SixbErrorCode } from "../errors/types"
|
|
3
3
|
import { QueueError } from "./errors"
|
|
4
|
+
import { IndexedHeap } from "./indexed-heap"
|
|
4
5
|
import type {
|
|
5
6
|
ActionQueueJobFailureCode,
|
|
6
7
|
ActionRunRequestedQueueJob,
|
|
@@ -29,6 +30,9 @@ interface QueueRecord<TQueueJob extends QueueJob = QueueJob> {
|
|
|
29
30
|
readonly sequence: number
|
|
30
31
|
readonly queueId: string
|
|
31
32
|
job: TQueueJob
|
|
33
|
+
availableAtMs: number
|
|
34
|
+
readonly createdAtMs: number
|
|
35
|
+
leaseExpiryMs: number | null
|
|
32
36
|
// A leased job stays "queued" until a worker completes or fails it.
|
|
33
37
|
state: QueueRecordState
|
|
34
38
|
failure: QueueJobFailure | null
|
|
@@ -63,25 +67,15 @@ function clearLease(record: QueueRecord): void {
|
|
|
63
67
|
record.leaseId = null
|
|
64
68
|
record.claimedAt = null
|
|
65
69
|
record.leaseExpiresAt = null
|
|
70
|
+
record.leaseExpiryMs = null
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
// Best-effort ordering is part of the contract; this keeps the in-memory provider deterministic.
|
|
69
74
|
function compareQueueRecords(left: QueueRecord, right: QueueRecord): number {
|
|
70
|
-
const availableDifference =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (availableDifference !== 0) {
|
|
75
|
-
return availableDifference
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const createdDifference =
|
|
79
|
-
parseTimestamp(left.job.createdAt, "job.createdAt") -
|
|
80
|
-
parseTimestamp(right.job.createdAt, "job.createdAt")
|
|
81
|
-
|
|
82
|
-
if (createdDifference !== 0) {
|
|
83
|
-
return createdDifference
|
|
84
|
-
}
|
|
75
|
+
const availableDifference = left.availableAtMs - right.availableAtMs
|
|
76
|
+
if (availableDifference !== 0) return availableDifference
|
|
77
|
+
const createdDifference = left.createdAtMs - right.createdAtMs
|
|
78
|
+
if (createdDifference !== 0) return createdDifference
|
|
85
79
|
|
|
86
80
|
return left.sequence - right.sequence
|
|
87
81
|
}
|
|
@@ -107,11 +101,18 @@ function createQueueJob<TQueueJob extends QueueJob>(
|
|
|
107
101
|
}
|
|
108
102
|
|
|
109
103
|
function leaseExpiresAtMs(record: QueueRecord): number | null {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
104
|
+
return record.leaseExpiryMs
|
|
105
|
+
}
|
|
113
106
|
|
|
114
|
-
|
|
107
|
+
function eligibleAt(record: QueueRecord): number {
|
|
108
|
+
return Math.max(record.availableAtMs, record.leaseExpiryMs ?? -Infinity)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface QueueLane {
|
|
112
|
+
// Terminal records remain here for caller-id deduplication, never in the delivery indexes.
|
|
113
|
+
readonly records: Map<string, QueueRecord>
|
|
114
|
+
readonly ready: IndexedHeap<QueueRecord>
|
|
115
|
+
readonly waiting: IndexedHeap<QueueRecord>
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
function toClaimedQueueJob<TQueueJob extends QueueJob>(
|
|
@@ -130,27 +131,26 @@ function toClaimedQueueJob<TQueueJob extends QueueJob>(
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
class InMemoryQueueStore {
|
|
133
|
-
private readonly
|
|
134
|
+
private readonly projects = new Map<string, Map<string, QueueLane>>()
|
|
134
135
|
private nextSequence = 1
|
|
135
136
|
|
|
136
137
|
add<TQueueJob extends QueueJob>(projectId: string, queueId: string, job: TQueueJob): void {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
sequence: this.nextSequence
|
|
138
|
+
const lane = this.lane(projectId, queueId)
|
|
139
|
+
const record: QueueRecord = {
|
|
140
|
+
sequence: this.nextSequence++,
|
|
140
141
|
queueId,
|
|
141
142
|
job,
|
|
143
|
+
availableAtMs: parseTimestamp(job.availableAt, "job.availableAt"),
|
|
144
|
+
createdAtMs: parseTimestamp(job.createdAt, "job.createdAt"),
|
|
145
|
+
leaseExpiryMs: null,
|
|
142
146
|
state: "queued",
|
|
143
147
|
failure: null,
|
|
144
148
|
leaseId: null,
|
|
145
149
|
claimedAt: null,
|
|
146
150
|
leaseExpiresAt: null,
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
list<TQueueJob extends QueueJob>(projectId: string, queueId: string): QueueRecord<TQueueJob>[] {
|
|
152
|
-
const records = this.recordsByProject.get(projectId) ?? []
|
|
153
|
-
return records.filter((record) => record.queueId === queueId) as QueueRecord<TQueueJob>[]
|
|
151
|
+
}
|
|
152
|
+
lane.records.set(job.id, record)
|
|
153
|
+
this.schedule(projectId, record, Date.now())
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
find<TQueueJob extends QueueJob>(
|
|
@@ -158,20 +158,67 @@ class InMemoryQueueStore {
|
|
|
158
158
|
queueId: string,
|
|
159
159
|
jobId: string
|
|
160
160
|
): QueueRecord<TQueueJob> | null {
|
|
161
|
-
return (
|
|
162
|
-
|
|
163
|
-
)
|
|
161
|
+
return (this.projects.get(projectId)?.get(queueId)?.records.get(jobId) ??
|
|
162
|
+
null) as QueueRecord<TQueueJob> | null
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
|
|
165
|
+
schedule(projectId: string, record: QueueRecord, now: number): void {
|
|
166
|
+
const lane = this.lane(projectId, record.queueId)
|
|
167
|
+
if (record.state !== "queued") {
|
|
168
|
+
lane.ready.delete(record)
|
|
169
|
+
lane.waiting.delete(record)
|
|
170
|
+
} else if (eligibleAt(record) <= now) {
|
|
171
|
+
lane.waiting.delete(record)
|
|
172
|
+
lane.ready.set(record)
|
|
173
|
+
} else {
|
|
174
|
+
lane.ready.delete(record)
|
|
175
|
+
lane.waiting.set(record)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
takeReady<TQueueJob extends QueueJob>(
|
|
180
|
+
projectId: string,
|
|
181
|
+
queueId: string,
|
|
182
|
+
now: number,
|
|
183
|
+
limit: number
|
|
184
|
+
): QueueRecord<TQueueJob>[] {
|
|
185
|
+
const lane = this.projects.get(projectId)?.get(queueId)
|
|
186
|
+
if (!lane) return []
|
|
187
|
+
// Wake delayed jobs and expired leases, then order all ready jobs by the original contract.
|
|
188
|
+
// A lease expiry controls eligibility, not the priority of a redelivered job.
|
|
189
|
+
while (lane.waiting.peek() && eligibleAt(lane.waiting.peek()!) <= now) {
|
|
190
|
+
lane.ready.set(lane.waiting.pop()!)
|
|
170
191
|
}
|
|
192
|
+
const selected: QueueRecord[] = []
|
|
193
|
+
while (selected.length < limit) {
|
|
194
|
+
const record = lane.ready.peek()
|
|
195
|
+
if (!record || record.availableAtMs > now) break
|
|
196
|
+
lane.ready.pop()
|
|
197
|
+
// A backwards wall-clock adjustment can make a previously expired lease live again.
|
|
198
|
+
if (eligibleAt(record) > now) lane.waiting.set(record)
|
|
199
|
+
else selected.push(record)
|
|
200
|
+
}
|
|
201
|
+
return selected as QueueRecord<TQueueJob>[]
|
|
202
|
+
}
|
|
171
203
|
|
|
172
|
-
|
|
173
|
-
this.
|
|
174
|
-
|
|
204
|
+
private lane(projectId: string, queueId: string): QueueLane {
|
|
205
|
+
let queues = this.projects.get(projectId)
|
|
206
|
+
if (!queues) {
|
|
207
|
+
queues = new Map()
|
|
208
|
+
this.projects.set(projectId, queues)
|
|
209
|
+
}
|
|
210
|
+
let lane = queues.get(queueId)
|
|
211
|
+
if (!lane) {
|
|
212
|
+
lane = {
|
|
213
|
+
records: new Map(),
|
|
214
|
+
ready: new IndexedHeap(compareQueueRecords),
|
|
215
|
+
waiting: new IndexedHeap(
|
|
216
|
+
(left, right) => eligibleAt(left) - eligibleAt(right) || left.sequence - right.sequence
|
|
217
|
+
),
|
|
218
|
+
}
|
|
219
|
+
queues.set(queueId, lane)
|
|
220
|
+
}
|
|
221
|
+
return lane
|
|
175
222
|
}
|
|
176
223
|
}
|
|
177
224
|
|
|
@@ -219,8 +266,8 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
219
266
|
assertNonEmpty(params.projectId, "projectId")
|
|
220
267
|
assertNonEmpty(params.workerId, "workerId")
|
|
221
268
|
|
|
222
|
-
const limit = params.limit ?? 1
|
|
223
|
-
if (limit
|
|
269
|
+
const limit = Math.trunc(params.limit ?? 1)
|
|
270
|
+
if (!(limit > 0)) {
|
|
224
271
|
return []
|
|
225
272
|
}
|
|
226
273
|
|
|
@@ -228,24 +275,11 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
228
275
|
assertPositiveNumber(leaseMs, "leaseMs")
|
|
229
276
|
|
|
230
277
|
const now = Date.now()
|
|
231
|
-
const claimable = this.store
|
|
232
|
-
.list<TQueueJob>(params.projectId, this.queueId)
|
|
233
|
-
.filter((record) => {
|
|
234
|
-
if (record.state !== "queued") {
|
|
235
|
-
return false
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (parseTimestamp(record.job.availableAt, "job.availableAt") > now) {
|
|
239
|
-
return false
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const expiresAt = leaseExpiresAtMs(record)
|
|
243
|
-
return expiresAt === null || expiresAt <= now
|
|
244
|
-
})
|
|
245
|
-
.sort(compareQueueRecords)
|
|
246
|
-
.slice(0, limit)
|
|
247
|
-
|
|
248
278
|
const claimedAt = new Date(now).toISOString()
|
|
279
|
+
// Validate before removing candidates, and cache the same millisecond precision we return.
|
|
280
|
+
const leaseExpiry = new Date(now + leaseMs)
|
|
281
|
+
const leaseExpiresAt = leaseExpiry.toISOString()
|
|
282
|
+
const claimable = this.store.takeReady<TQueueJob>(params.projectId, this.queueId, now, limit)
|
|
249
283
|
|
|
250
284
|
return claimable.map((record) => {
|
|
251
285
|
// Attempts count claims so redelivery after lease expiry or retry is visible to workers.
|
|
@@ -255,7 +289,9 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
255
289
|
}
|
|
256
290
|
record.leaseId = randomUUID()
|
|
257
291
|
record.claimedAt = claimedAt
|
|
258
|
-
record.leaseExpiresAt =
|
|
292
|
+
record.leaseExpiresAt = leaseExpiresAt
|
|
293
|
+
record.leaseExpiryMs = leaseExpiry.getTime()
|
|
294
|
+
this.store.schedule(params.projectId, record, now)
|
|
259
295
|
|
|
260
296
|
return toClaimedQueueJob(record)
|
|
261
297
|
})
|
|
@@ -265,6 +301,7 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
265
301
|
const record = this.requireActiveLease(params)
|
|
266
302
|
record.state = "completed"
|
|
267
303
|
clearLease(record)
|
|
304
|
+
this.store.schedule(params.projectId, record, Date.now())
|
|
268
305
|
}
|
|
269
306
|
|
|
270
307
|
async retry(params: {
|
|
@@ -275,13 +312,15 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
275
312
|
}): Promise<void> {
|
|
276
313
|
const record = this.requireActiveLease(params)
|
|
277
314
|
const availableAt = params.availableAt ?? new Date().toISOString()
|
|
278
|
-
parseTimestamp(availableAt, "availableAt")
|
|
315
|
+
const availableAtMs = parseTimestamp(availableAt, "availableAt")
|
|
279
316
|
|
|
317
|
+
record.availableAtMs = availableAtMs
|
|
280
318
|
record.job = {
|
|
281
319
|
...record.job,
|
|
282
320
|
availableAt,
|
|
283
321
|
}
|
|
284
322
|
clearLease(record)
|
|
323
|
+
this.store.schedule(params.projectId, record, Date.now())
|
|
285
324
|
}
|
|
286
325
|
|
|
287
326
|
async fail(params: {
|
|
@@ -294,6 +333,7 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
294
333
|
record.state = "failed"
|
|
295
334
|
record.failure = structuredClone(params.failure)
|
|
296
335
|
clearLease(record)
|
|
336
|
+
this.store.schedule(params.projectId, record, Date.now())
|
|
297
337
|
}
|
|
298
338
|
|
|
299
339
|
async renewLease(params: {
|
|
@@ -319,7 +359,10 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
|
|
|
319
359
|
return null
|
|
320
360
|
}
|
|
321
361
|
|
|
322
|
-
|
|
362
|
+
const leaseExpiry = new Date(now + params.leaseMs)
|
|
363
|
+
record.leaseExpiresAt = leaseExpiry.toISOString()
|
|
364
|
+
record.leaseExpiryMs = leaseExpiry.getTime()
|
|
365
|
+
this.store.schedule(params.projectId, record, now)
|
|
323
366
|
return toClaimedQueueJob(record)
|
|
324
367
|
}
|
|
325
368
|
|