@qlever-llc/trellis 0.10.17 → 0.19.0-rc.1
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/esm/auth/mod.d.ts +1 -1
- package/esm/auth/mod.d.ts.map +1 -1
- package/esm/auth/mod.js +1 -1
- package/esm/auth/protocol.d.ts +416 -398
- package/esm/auth/protocol.d.ts.map +1 -1
- package/esm/auth/protocol.js +35 -33
- package/esm/codec.d.ts +1 -0
- package/esm/codec.d.ts.map +1 -1
- package/esm/codec.js +1 -0
- package/esm/contract_support/canonical.d.ts +3 -0
- package/esm/contract_support/canonical.d.ts.map +1 -1
- package/esm/contract_support/canonical.js +3 -0
- package/esm/contract_support/mod.d.ts +28 -4
- package/esm/contract_support/mod.d.ts.map +1 -1
- package/esm/contract_support/mod.js +85 -4
- package/esm/contract_support/protocol.d.ts +92 -7
- package/esm/contract_support/protocol.d.ts.map +1 -1
- package/esm/contract_support/protocol.js +40 -6
- package/esm/errors/index.d.ts +9 -9
- package/esm/generated-sdk/auth/api.d.ts +4 -1
- package/esm/generated-sdk/auth/api.d.ts.map +1 -1
- package/esm/generated-sdk/auth/api.js +6 -1
- package/esm/generated-sdk/auth/client.d.ts +47 -32
- package/esm/generated-sdk/auth/client.d.ts.map +1 -1
- package/esm/generated-sdk/auth/contract.d.ts +1 -1
- package/esm/generated-sdk/auth/contract.d.ts.map +1 -1
- package/esm/generated-sdk/auth/contract.js +1570 -1202
- package/esm/generated-sdk/auth/schemas.d.ts +1757 -1759
- package/esm/generated-sdk/auth/schemas.d.ts.map +1 -1
- package/esm/generated-sdk/auth/schemas.js +963 -768
- package/esm/generated-sdk/auth/types.d.ts +292 -302
- package/esm/generated-sdk/auth/types.d.ts.map +1 -1
- package/esm/generated-sdk/auth/types.js +1 -1
- package/esm/generated-sdk/health/client.d.ts +4 -4
- package/esm/generated-sdk/health/client.d.ts.map +1 -1
- package/esm/generated-sdk/health/contract.d.ts +1 -1
- package/esm/generated-sdk/health/contract.d.ts.map +1 -1
- package/esm/generated-sdk/health/contract.js +2 -10
- package/esm/generated-sdk/health/schemas.d.ts +1 -14
- package/esm/generated-sdk/health/schemas.d.ts.map +1 -1
- package/esm/generated-sdk/health/schemas.js +1 -9
- package/esm/generated-sdk/health/types.d.ts +3 -5
- package/esm/generated-sdk/health/types.d.ts.map +1 -1
- package/esm/generated-sdk/health/types.js +1 -2
- package/esm/generated-sdk/jobs/api.d.ts +13 -0
- package/esm/generated-sdk/jobs/api.d.ts.map +1 -1
- package/esm/generated-sdk/jobs/client.d.ts +6 -4
- package/esm/generated-sdk/jobs/client.d.ts.map +1 -1
- package/esm/generated-sdk/jobs/contract.d.ts +14 -1
- package/esm/generated-sdk/jobs/contract.d.ts.map +1 -1
- package/esm/generated-sdk/jobs/contract.js +268 -1
- package/esm/generated-sdk/jobs/owned_api.d.ts +13 -0
- package/esm/generated-sdk/jobs/owned_api.d.ts.map +1 -1
- package/esm/generated-sdk/jobs/owned_api.js +20 -1
- package/esm/generated-sdk/jobs/schemas.d.ts +437 -0
- package/esm/generated-sdk/jobs/schemas.d.ts.map +1 -1
- package/esm/generated-sdk/jobs/schemas.js +211 -0
- package/esm/generated-sdk/jobs/types.d.ts +123 -1
- package/esm/generated-sdk/jobs/types.d.ts.map +1 -1
- package/esm/generated-sdk/jobs/types.js +1 -1
- package/esm/generated-sdk/state/client.d.ts +4 -4
- package/esm/generated-sdk/state/client.d.ts.map +1 -1
- package/esm/generated-sdk/trellis-core/api.d.ts +4 -1
- package/esm/generated-sdk/trellis-core/api.d.ts.map +1 -1
- package/esm/generated-sdk/trellis-core/api.js +6 -1
- package/esm/generated-sdk/trellis-core/client.d.ts +28 -3
- package/esm/generated-sdk/trellis-core/client.d.ts.map +1 -1
- package/esm/generated-sdk/trellis-core/contract.d.ts +1 -1
- package/esm/generated-sdk/trellis-core/contract.d.ts.map +1 -1
- package/esm/generated-sdk/trellis-core/contract.js +91 -1
- package/esm/generated-sdk/trellis-core/schemas.d.ts +113 -0
- package/esm/generated-sdk/trellis-core/schemas.d.ts.map +1 -1
- package/esm/generated-sdk/trellis-core/schemas.js +81 -0
- package/esm/generated-sdk/trellis-core/types.d.ts +23 -1
- package/esm/generated-sdk/trellis-core/types.d.ts.map +1 -1
- package/esm/generated-sdk/trellis-core/types.js +1 -1
- package/esm/health.d.ts +1 -1
- package/esm/health.d.ts.map +1 -1
- package/esm/health.js +1 -1
- package/esm/index.d.ts +2 -2
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -1
- package/esm/jobs.d.ts +69 -3
- package/esm/jobs.d.ts.map +1 -1
- package/esm/jobs.js +92 -3
- package/esm/server/health.d.ts +2 -6
- package/esm/server/health.d.ts.map +1 -1
- package/esm/server/health_schemas.d.ts +0 -8
- package/esm/server/health_schemas.d.ts.map +1 -1
- package/esm/server/health_schemas.js +0 -5
- package/esm/server/internal_jobs/bindings.d.ts +11 -0
- package/esm/server/internal_jobs/bindings.d.ts.map +1 -1
- package/esm/server/internal_jobs/job-manager.d.ts +44 -1
- package/esm/server/internal_jobs/job-manager.d.ts.map +1 -1
- package/esm/server/internal_jobs/job-manager.js +361 -51
- package/esm/server/internal_jobs/key-coordinator.d.ts +260 -0
- package/esm/server/internal_jobs/key-coordinator.d.ts.map +1 -0
- package/esm/server/internal_jobs/key-coordinator.js +580 -0
- package/esm/server/internal_jobs/projection.d.ts.map +1 -1
- package/esm/server/internal_jobs/projection.js +2 -0
- package/esm/server/internal_jobs/runtime-worker.d.ts +5 -1
- package/esm/server/internal_jobs/runtime-worker.d.ts.map +1 -1
- package/esm/server/internal_jobs/runtime-worker.js +14 -2
- package/esm/server/internal_jobs/types.d.ts +5 -5
- package/esm/server/internal_jobs/types.d.ts.map +1 -1
- package/esm/server/internal_jobs/types.js +6 -0
- package/esm/server/service.d.ts +8 -3
- package/esm/server/service.d.ts.map +1 -1
- package/esm/server/service.js +127 -6
- package/esm/service/drizzle.d.ts +27 -0
- package/esm/service/drizzle.d.ts.map +1 -0
- package/esm/service/drizzle.js +84 -0
- package/esm/service/mod.d.ts +1 -1
- package/esm/service/mod.d.ts.map +1 -1
- package/esm/service/mod.js +1 -1
- package/esm/service/outbox_inbox.d.ts.map +1 -1
- package/esm/service/outbox_inbox.js +13 -2
- package/esm/trellis.d.ts +24 -8
- package/esm/trellis.d.ts.map +1 -1
- package/esm/trellis.js +44 -22
- package/package.json +14 -2
- package/script/auth/mod.d.ts +1 -1
- package/script/auth/mod.d.ts.map +1 -1
- package/script/auth/mod.js +22 -6
- package/script/auth/protocol.d.ts +416 -398
- package/script/auth/protocol.d.ts.map +1 -1
- package/script/auth/protocol.js +40 -37
- package/script/codec.d.ts +1 -0
- package/script/codec.d.ts.map +1 -1
- package/script/codec.js +1 -0
- package/script/contract_support/canonical.d.ts +3 -0
- package/script/contract_support/canonical.d.ts.map +1 -1
- package/script/contract_support/canonical.js +3 -0
- package/script/contract_support/mod.d.ts +28 -4
- package/script/contract_support/mod.d.ts.map +1 -1
- package/script/contract_support/mod.js +90 -4
- package/script/contract_support/protocol.d.ts +92 -7
- package/script/contract_support/protocol.d.ts.map +1 -1
- package/script/contract_support/protocol.js +41 -7
- package/script/errors/index.d.ts +9 -9
- package/script/generated-sdk/auth/api.d.ts +4 -1
- package/script/generated-sdk/auth/api.d.ts.map +1 -1
- package/script/generated-sdk/auth/api.js +6 -1
- package/script/generated-sdk/auth/client.d.ts +47 -32
- package/script/generated-sdk/auth/client.d.ts.map +1 -1
- package/script/generated-sdk/auth/contract.d.ts +1 -1
- package/script/generated-sdk/auth/contract.d.ts.map +1 -1
- package/script/generated-sdk/auth/contract.js +1570 -1202
- package/script/generated-sdk/auth/schemas.d.ts +1757 -1759
- package/script/generated-sdk/auth/schemas.d.ts.map +1 -1
- package/script/generated-sdk/auth/schemas.js +963 -768
- package/script/generated-sdk/auth/types.d.ts +292 -302
- package/script/generated-sdk/auth/types.d.ts.map +1 -1
- package/script/generated-sdk/auth/types.js +1 -1
- package/script/generated-sdk/health/client.d.ts +4 -4
- package/script/generated-sdk/health/client.d.ts.map +1 -1
- package/script/generated-sdk/health/contract.d.ts +1 -1
- package/script/generated-sdk/health/contract.d.ts.map +1 -1
- package/script/generated-sdk/health/contract.js +2 -10
- package/script/generated-sdk/health/schemas.d.ts +1 -14
- package/script/generated-sdk/health/schemas.d.ts.map +1 -1
- package/script/generated-sdk/health/schemas.js +1 -9
- package/script/generated-sdk/health/types.d.ts +3 -5
- package/script/generated-sdk/health/types.d.ts.map +1 -1
- package/script/generated-sdk/health/types.js +1 -2
- package/script/generated-sdk/jobs/api.d.ts +13 -0
- package/script/generated-sdk/jobs/api.d.ts.map +1 -1
- package/script/generated-sdk/jobs/client.d.ts +6 -4
- package/script/generated-sdk/jobs/client.d.ts.map +1 -1
- package/script/generated-sdk/jobs/contract.d.ts +14 -1
- package/script/generated-sdk/jobs/contract.d.ts.map +1 -1
- package/script/generated-sdk/jobs/contract.js +268 -1
- package/script/generated-sdk/jobs/owned_api.d.ts +13 -0
- package/script/generated-sdk/jobs/owned_api.d.ts.map +1 -1
- package/script/generated-sdk/jobs/owned_api.js +19 -0
- package/script/generated-sdk/jobs/schemas.d.ts +437 -0
- package/script/generated-sdk/jobs/schemas.d.ts.map +1 -1
- package/script/generated-sdk/jobs/schemas.js +212 -1
- package/script/generated-sdk/jobs/types.d.ts +123 -1
- package/script/generated-sdk/jobs/types.d.ts.map +1 -1
- package/script/generated-sdk/jobs/types.js +1 -1
- package/script/generated-sdk/state/client.d.ts +4 -4
- package/script/generated-sdk/state/client.d.ts.map +1 -1
- package/script/generated-sdk/trellis-core/api.d.ts +4 -1
- package/script/generated-sdk/trellis-core/api.d.ts.map +1 -1
- package/script/generated-sdk/trellis-core/api.js +6 -1
- package/script/generated-sdk/trellis-core/client.d.ts +28 -3
- package/script/generated-sdk/trellis-core/client.d.ts.map +1 -1
- package/script/generated-sdk/trellis-core/contract.d.ts +1 -1
- package/script/generated-sdk/trellis-core/contract.d.ts.map +1 -1
- package/script/generated-sdk/trellis-core/contract.js +91 -1
- package/script/generated-sdk/trellis-core/schemas.d.ts +113 -0
- package/script/generated-sdk/trellis-core/schemas.d.ts.map +1 -1
- package/script/generated-sdk/trellis-core/schemas.js +81 -0
- package/script/generated-sdk/trellis-core/types.d.ts +23 -1
- package/script/generated-sdk/trellis-core/types.d.ts.map +1 -1
- package/script/generated-sdk/trellis-core/types.js +1 -1
- package/script/health.d.ts +1 -1
- package/script/health.d.ts.map +1 -1
- package/script/health.js +1 -2
- package/script/index.d.ts +2 -2
- package/script/index.d.ts.map +1 -1
- package/script/index.js +2 -3
- package/script/jobs.d.ts +69 -3
- package/script/jobs.d.ts.map +1 -1
- package/script/jobs.js +93 -3
- package/script/server/health.d.ts +2 -6
- package/script/server/health.d.ts.map +1 -1
- package/script/server/health_schemas.d.ts +0 -8
- package/script/server/health_schemas.d.ts.map +1 -1
- package/script/server/health_schemas.js +1 -6
- package/script/server/internal_jobs/bindings.d.ts +11 -0
- package/script/server/internal_jobs/bindings.d.ts.map +1 -1
- package/script/server/internal_jobs/job-manager.d.ts +44 -1
- package/script/server/internal_jobs/job-manager.d.ts.map +1 -1
- package/script/server/internal_jobs/job-manager.js +361 -51
- package/script/server/internal_jobs/key-coordinator.d.ts +260 -0
- package/script/server/internal_jobs/key-coordinator.d.ts.map +1 -0
- package/script/server/internal_jobs/key-coordinator.js +593 -0
- package/script/server/internal_jobs/projection.d.ts.map +1 -1
- package/script/server/internal_jobs/projection.js +2 -0
- package/script/server/internal_jobs/runtime-worker.d.ts +5 -1
- package/script/server/internal_jobs/runtime-worker.d.ts.map +1 -1
- package/script/server/internal_jobs/runtime-worker.js +14 -2
- package/script/server/internal_jobs/types.d.ts +5 -5
- package/script/server/internal_jobs/types.d.ts.map +1 -1
- package/script/server/internal_jobs/types.js +6 -0
- package/script/server/service.d.ts +8 -3
- package/script/server/service.d.ts.map +1 -1
- package/script/server/service.js +126 -5
- package/script/service/drizzle.d.ts +27 -0
- package/script/service/drizzle.d.ts.map +1 -0
- package/script/service/drizzle.js +88 -0
- package/script/service/mod.d.ts +1 -1
- package/script/service/mod.d.ts.map +1 -1
- package/script/service/mod.js +1 -2
- package/script/service/outbox_inbox.d.ts.map +1 -1
- package/script/service/outbox_inbox.js +13 -2
- package/script/trellis.d.ts +24 -8
- package/script/trellis.d.ts.map +1 -1
- package/script/trellis.js +44 -22
- package/src/auth/mod.ts +28 -2
- package/src/auth/protocol.ts +72 -37
- package/src/codec.ts +1 -0
- package/src/contract_support/canonical.ts +3 -0
- package/src/contract_support/mod.ts +158 -5
- package/src/contract_support/protocol.ts +56 -9
- package/src/health.ts +0 -1
- package/src/index.ts +2 -1
- package/src/jobs.ts +138 -1
- package/src/sdk/_generated/auth/api.ts +9 -2
- package/src/sdk/_generated/auth/client.ts +85 -78
- package/src/sdk/_generated/auth/contract.ts +1803 -1435
- package/src/sdk/_generated/auth/schemas.ts +1166 -971
- package/src/sdk/_generated/auth/types.ts +330 -314
- package/src/sdk/_generated/core/api.ts +9 -2
- package/src/sdk/_generated/core/client.ts +41 -2
- package/src/sdk/_generated/core/contract.ts +91 -1
- package/src/sdk/_generated/core/schemas.ts +81 -0
- package/src/sdk/_generated/core/types.ts +23 -1
- package/src/sdk/_generated/health/client.ts +6 -6
- package/src/sdk/_generated/health/contract.ts +2 -10
- package/src/sdk/_generated/health/schemas.ts +1 -9
- package/src/sdk/_generated/health/types.ts +6 -2
- package/src/sdk/_generated/jobs/client.ts +17 -6
- package/src/sdk/_generated/jobs/contract.ts +269 -1
- package/src/sdk/_generated/jobs/owned_api.ts +21 -0
- package/src/sdk/_generated/jobs/schemas.ts +213 -0
- package/src/sdk/_generated/jobs/types.ts +116 -1
- package/src/sdk/_generated/state/client.ts +6 -6
- package/src/server/health.ts +2 -6
- package/src/server/health_schemas.ts +0 -6
- package/src/server/internal_jobs/bindings.ts +11 -0
- package/src/server/internal_jobs/job-manager.ts +436 -16
- package/src/server/internal_jobs/key-coordinator.ts +955 -0
- package/src/server/internal_jobs/projection.ts +2 -0
- package/src/server/internal_jobs/runtime-worker.ts +17 -1
- package/src/server/internal_jobs/types.ts +6 -0
- package/src/server/service.ts +168 -9
- package/src/service/drizzle.ts +125 -0
- package/src/service/mod.ts +0 -1
- package/src/service/outbox_inbox.ts +16 -6
- package/src/trellis.ts +76 -27
|
@@ -177,6 +177,8 @@ type StartQueueWorkerLoopOptions<TResult> = {
|
|
|
177
177
|
args: ResultValidationArgs<TResult>,
|
|
178
178
|
) => Promise<void> | void;
|
|
179
179
|
handler: (job: ActiveJob<unknown, TResult>) => Promise<TResult>;
|
|
180
|
+
instanceId?: string;
|
|
181
|
+
deferralBackoffMs?: number;
|
|
180
182
|
};
|
|
181
183
|
|
|
182
184
|
type StartNatsQueueWorkerOptions<TResult> =
|
|
@@ -199,6 +201,7 @@ type StartNatsQueueWorkerOptions<TResult> =
|
|
|
199
201
|
args: ResultValidationArgs<TResult>,
|
|
200
202
|
) => Promise<void> | void;
|
|
201
203
|
handler: (job: ActiveJob<unknown, TResult>) => Promise<TResult>;
|
|
204
|
+
instanceId?: string;
|
|
202
205
|
};
|
|
203
206
|
|
|
204
207
|
function toWorkerConsumer(
|
|
@@ -252,7 +255,8 @@ export async function processWorkPayloadWithContextAndHeartbeat<TResult>(
|
|
|
252
255
|
args: ResultValidationArgs<TResult>,
|
|
253
256
|
) => Promise<void> | void;
|
|
254
257
|
},
|
|
255
|
-
runtime?: { redeliveryCount?: number },
|
|
258
|
+
runtime?: { latestState?: Job["state"]; redeliveryCount?: number },
|
|
259
|
+
instanceId?: string,
|
|
256
260
|
): Promise<JobProcessOutcome<TResult> | undefined> {
|
|
257
261
|
const event = parseWorkPayloadEvent(payload);
|
|
258
262
|
if (!event) {
|
|
@@ -280,7 +284,9 @@ export async function processWorkPayloadWithContextAndHeartbeat<TResult>(
|
|
|
280
284
|
return await handler(activeJob);
|
|
281
285
|
},
|
|
282
286
|
{
|
|
287
|
+
latestState: runtime?.latestState,
|
|
283
288
|
redeliveryCount: runtime?.redeliveryCount,
|
|
289
|
+
instanceId,
|
|
284
290
|
},
|
|
285
291
|
{
|
|
286
292
|
validateResult: validation?.validateResult
|
|
@@ -323,6 +329,7 @@ export function ackActionForOutcome(
|
|
|
323
329
|
switch (outcome.outcome) {
|
|
324
330
|
case "retry":
|
|
325
331
|
case "interrupted":
|
|
332
|
+
case "deferred":
|
|
326
333
|
return "nak";
|
|
327
334
|
default:
|
|
328
335
|
return "ack";
|
|
@@ -381,6 +388,7 @@ export async function startQueueWorkerLoop<TResult>(
|
|
|
381
388
|
continue;
|
|
382
389
|
}
|
|
383
390
|
if (lifecycleWorkDecision(latestLifecycle) === "skip-ack") {
|
|
391
|
+
await options.manager.cleanupQueuedKeyedJob(job);
|
|
384
392
|
registry.clearPending(key);
|
|
385
393
|
await msg.ack();
|
|
386
394
|
continue;
|
|
@@ -394,6 +402,7 @@ export async function startQueueWorkerLoop<TResult>(
|
|
|
394
402
|
continue;
|
|
395
403
|
}
|
|
396
404
|
if (projectedWorkDecision(projected, job) === "skip-ack") {
|
|
405
|
+
await options.manager.cleanupQueuedKeyedJob(job);
|
|
397
406
|
registry.clearPending(key);
|
|
398
407
|
await msg.ack();
|
|
399
408
|
continue;
|
|
@@ -422,11 +431,15 @@ export async function startQueueWorkerLoop<TResult>(
|
|
|
422
431
|
validateResult: options.validateResult,
|
|
423
432
|
},
|
|
424
433
|
{
|
|
434
|
+
latestState: latestLifecycle?.state,
|
|
425
435
|
redeliveryCount: msg.info?.redeliveryCount,
|
|
426
436
|
},
|
|
437
|
+
options.instanceId,
|
|
427
438
|
);
|
|
428
439
|
if (ackActionForOutcome(outcome) === "ack") {
|
|
429
440
|
await msg.ack();
|
|
441
|
+
} else if (outcome?.outcome === "deferred") {
|
|
442
|
+
await msg.nak(options.deferralBackoffMs ?? 1_000);
|
|
430
443
|
} else {
|
|
431
444
|
await msg.nak();
|
|
432
445
|
}
|
|
@@ -512,6 +525,8 @@ export async function startNatsQueueWorker<TResult>(
|
|
|
512
525
|
resultSchema: queue.result,
|
|
513
526
|
validateResult: options.validateResult,
|
|
514
527
|
handler: options.handler,
|
|
528
|
+
instanceId: options.instanceId,
|
|
529
|
+
deferralBackoffMs: queue.backoffMs[0] ?? 1_000,
|
|
515
530
|
});
|
|
516
531
|
}
|
|
517
532
|
|
|
@@ -624,6 +639,7 @@ export async function startNatsWorkerHostFromBinding<TResult>(
|
|
|
624
639
|
validatePayload: options.validatePayload,
|
|
625
640
|
validateResult: options.validateResult,
|
|
626
641
|
handler: options.handler,
|
|
642
|
+
instanceId: options.instanceId,
|
|
627
643
|
};
|
|
628
644
|
return await (isCustomNatsRuntimeDeps(options)
|
|
629
645
|
? startNatsQueueWorker({
|
|
@@ -19,6 +19,8 @@ export const JobStateSchema = Type.Union([
|
|
|
19
19
|
Type.Literal("failed"),
|
|
20
20
|
Type.Literal("cancelled"),
|
|
21
21
|
Type.Literal("expired"),
|
|
22
|
+
Type.Literal("skipped"),
|
|
23
|
+
Type.Literal("stale"),
|
|
22
24
|
Type.Literal("dead"),
|
|
23
25
|
Type.Literal("dismissed"),
|
|
24
26
|
]);
|
|
@@ -87,6 +89,10 @@ export const JobEventSchema = Type.Object({
|
|
|
87
89
|
Type.Literal("failed"),
|
|
88
90
|
Type.Literal("cancelled"),
|
|
89
91
|
Type.Literal("expired"),
|
|
92
|
+
Type.Literal("skipped"),
|
|
93
|
+
Type.Literal("stale"),
|
|
94
|
+
Type.Literal("heartbeat"),
|
|
95
|
+
Type.Literal("staleCompletionIgnored"),
|
|
90
96
|
Type.Literal("retried"),
|
|
91
97
|
Type.Literal("dead"),
|
|
92
98
|
Type.Literal("dismissed"),
|
package/src/server/service.ts
CHANGED
|
@@ -105,9 +105,11 @@ import {
|
|
|
105
105
|
ActiveJob as PublicActiveJob,
|
|
106
106
|
type JobIdentity,
|
|
107
107
|
type JobLogEntry,
|
|
108
|
+
JobNotEnqueuedError,
|
|
108
109
|
type JobProgress,
|
|
109
110
|
JobRef,
|
|
110
111
|
type JobSnapshot,
|
|
112
|
+
type JobSubmitOutcome,
|
|
111
113
|
JobWorkerHostAdapter,
|
|
112
114
|
type TerminalJob,
|
|
113
115
|
} from "../jobs.js";
|
|
@@ -116,6 +118,18 @@ import {
|
|
|
116
118
|
JobProcessError as InternalJobProcessError,
|
|
117
119
|
} from "./internal_jobs/job-manager.js";
|
|
118
120
|
import { startNatsWorkerHostFromBinding } from "./internal_jobs/runtime-worker.js";
|
|
121
|
+
import {
|
|
122
|
+
createNatsJobKeyCoordinator,
|
|
123
|
+
normalizeJobKeyPolicy,
|
|
124
|
+
} from "./internal_jobs/key-coordinator.js";
|
|
125
|
+
import type {
|
|
126
|
+
JobKeyConcurrencyBinding,
|
|
127
|
+
JobQueuePolicyBinding,
|
|
128
|
+
} from "./internal_jobs/key-coordinator.js";
|
|
129
|
+
import type {
|
|
130
|
+
JobsBinding,
|
|
131
|
+
JobsQueueBinding,
|
|
132
|
+
} from "./internal_jobs/bindings.js";
|
|
119
133
|
import type { ActiveJob as InternalActiveJob } from "./internal_jobs/active-job.js";
|
|
120
134
|
import {
|
|
121
135
|
type Job as InternalJob,
|
|
@@ -149,6 +163,8 @@ type ResourceBindingJobsQueue = {
|
|
|
149
163
|
logs: boolean;
|
|
150
164
|
dlq: boolean;
|
|
151
165
|
concurrency: number;
|
|
166
|
+
keyConcurrency?: JobKeyConcurrencyBinding;
|
|
167
|
+
queue?: JobQueuePolicyBinding;
|
|
152
168
|
};
|
|
153
169
|
|
|
154
170
|
type ResourceBindingJobs = {
|
|
@@ -157,6 +173,71 @@ type ResourceBindingJobs = {
|
|
|
157
173
|
queues: Record<string, ResourceBindingJobsQueue>;
|
|
158
174
|
};
|
|
159
175
|
|
|
176
|
+
function normalizeResourceJobsBinding(
|
|
177
|
+
binding: ResourceBindingJobs,
|
|
178
|
+
): JobsBinding {
|
|
179
|
+
const queues: Record<string, JobsQueueBinding> = {};
|
|
180
|
+
for (const [name, queue] of Object.entries(binding.queues)) {
|
|
181
|
+
const baseQueue = baseJobsQueueBinding(queue);
|
|
182
|
+
if (!queue.keyConcurrency) {
|
|
183
|
+
queues[name] = {
|
|
184
|
+
...baseQueue,
|
|
185
|
+
...(queue.queue ? { queue: normalizeQueuePolicy(queue.queue) } : {}),
|
|
186
|
+
};
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const policy = normalizeJobKeyPolicy({
|
|
191
|
+
keyConcurrency: queue.keyConcurrency,
|
|
192
|
+
queue: queue.queue,
|
|
193
|
+
});
|
|
194
|
+
queues[name] = {
|
|
195
|
+
...baseQueue,
|
|
196
|
+
keyConcurrency: {
|
|
197
|
+
key: policy.key,
|
|
198
|
+
maxActive: policy.maxActive,
|
|
199
|
+
heartbeatIntervalMs: policy.heartbeatIntervalMs,
|
|
200
|
+
heartbeatTtlMs: policy.heartbeatTtlMs,
|
|
201
|
+
stalePolicy: policy.stalePolicy,
|
|
202
|
+
},
|
|
203
|
+
queue: policy.queue,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return { namespace: binding.namespace, queues };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function baseJobsQueueBinding(
|
|
210
|
+
queue: ResourceBindingJobsQueue,
|
|
211
|
+
): Omit<JobsQueueBinding, "keyConcurrency" | "queue"> {
|
|
212
|
+
return {
|
|
213
|
+
queueType: queue.queueType,
|
|
214
|
+
publishPrefix: queue.publishPrefix,
|
|
215
|
+
workSubject: queue.workSubject,
|
|
216
|
+
consumerName: queue.consumerName,
|
|
217
|
+
payload: queue.payload,
|
|
218
|
+
...(queue.result ? { result: queue.result } : {}),
|
|
219
|
+
maxDeliver: queue.maxDeliver,
|
|
220
|
+
backoffMs: queue.backoffMs,
|
|
221
|
+
ackWaitMs: queue.ackWaitMs,
|
|
222
|
+
...(queue.defaultDeadlineMs !== undefined
|
|
223
|
+
? { defaultDeadlineMs: queue.defaultDeadlineMs }
|
|
224
|
+
: {}),
|
|
225
|
+
progress: queue.progress,
|
|
226
|
+
logs: queue.logs,
|
|
227
|
+
dlq: queue.dlq,
|
|
228
|
+
concurrency: queue.concurrency,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeQueuePolicy(
|
|
233
|
+
queue: JobQueuePolicyBinding,
|
|
234
|
+
): JobsQueueBinding["queue"] {
|
|
235
|
+
return {
|
|
236
|
+
maxQueuedPerKey: queue.maxQueuedPerKey ?? 0,
|
|
237
|
+
whenFull: queue.whenFull ?? "reject",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
160
241
|
type ResourceBindingEventConsumer = {
|
|
161
242
|
stream: string;
|
|
162
243
|
consumerName: string;
|
|
@@ -1208,6 +1289,9 @@ export type JobQueue<
|
|
|
1208
1289
|
TJobs extends ContractJobsMetadata = {},
|
|
1209
1290
|
> = {
|
|
1210
1291
|
create(payload: TPayload): AsyncResult<JobRef<TPayload, TResult>, BaseError>;
|
|
1292
|
+
submit(
|
|
1293
|
+
payload: TPayload,
|
|
1294
|
+
): AsyncResult<JobSubmitOutcome<TPayload, TResult>, BaseError>;
|
|
1211
1295
|
handle(
|
|
1212
1296
|
handler: (args: {
|
|
1213
1297
|
job: PublicActiveJob<TPayload, TResult>;
|
|
@@ -1239,7 +1323,7 @@ type ServiceEventOf<
|
|
|
1239
1323
|
type ServiceEventPayloadOf<
|
|
1240
1324
|
TA extends TrellisAPI,
|
|
1241
1325
|
E extends ServiceEventName<TA>,
|
|
1242
|
-
> =
|
|
1326
|
+
> = ServiceEventOf<TA, E> & Record<string, unknown>;
|
|
1243
1327
|
|
|
1244
1328
|
type BoundEventHandleFn<
|
|
1245
1329
|
TEventApi extends TrellisAPI,
|
|
@@ -1249,7 +1333,7 @@ type BoundEventHandleFn<
|
|
|
1249
1333
|
TJobs extends ContractJobsMetadata,
|
|
1250
1334
|
TDeps,
|
|
1251
1335
|
> = (args: {
|
|
1252
|
-
event:
|
|
1336
|
+
event: ServiceEventPayloadOf<TEventApi, E>;
|
|
1253
1337
|
context: EventListenerContext;
|
|
1254
1338
|
client: Trellis<TTrellisApi, TKv, TJobs>;
|
|
1255
1339
|
deps: TDeps;
|
|
@@ -1419,6 +1503,9 @@ type BoundJobQueue<
|
|
|
1419
1503
|
TDeps,
|
|
1420
1504
|
> = {
|
|
1421
1505
|
create(payload: TPayload): AsyncResult<JobRef<TPayload, TResult>, BaseError>;
|
|
1506
|
+
submit(
|
|
1507
|
+
payload: TPayload,
|
|
1508
|
+
): AsyncResult<JobSubmitOutcome<TPayload, TResult>, BaseError>;
|
|
1422
1509
|
handle(
|
|
1423
1510
|
handler: (args: {
|
|
1424
1511
|
job: PublicActiveJob<TPayload, TResult>;
|
|
@@ -1922,6 +2009,7 @@ export async function createConnectedService<
|
|
|
1922
2009
|
prepare: (event, data) => outbound.prepare(event, data),
|
|
1923
2010
|
publish: (event, data) => outbound.publish(event, data),
|
|
1924
2011
|
publishPrepared: (event) => outbound.publishPrepared(event),
|
|
2012
|
+
stopEventListeners: () => outbound.stopEventListeners(),
|
|
1925
2013
|
get kv() {
|
|
1926
2014
|
return getHandlerResources().kv;
|
|
1927
2015
|
},
|
|
@@ -2081,7 +2169,8 @@ function isTerminalJobState(
|
|
|
2081
2169
|
state: string,
|
|
2082
2170
|
): state is TerminalJob<unknown, unknown>["state"] {
|
|
2083
2171
|
return state === "completed" || state === "failed" || state === "cancelled" ||
|
|
2084
|
-
state === "expired" || state === "
|
|
2172
|
+
state === "expired" || state === "skipped" || state === "stale" ||
|
|
2173
|
+
state === "dead" || state === "dismissed";
|
|
2085
2174
|
}
|
|
2086
2175
|
|
|
2087
2176
|
function isTerminalJobSnapshot<TPayload, TResult>(
|
|
@@ -2202,6 +2291,9 @@ function snapshotFromLifecycleEvent<TPayload, TResult>(
|
|
|
2202
2291
|
case "failed":
|
|
2203
2292
|
case "cancelled":
|
|
2204
2293
|
case "expired":
|
|
2294
|
+
case "skipped":
|
|
2295
|
+
case "stale":
|
|
2296
|
+
case "staleCompletionIgnored":
|
|
2205
2297
|
case "dead":
|
|
2206
2298
|
case "dismissed":
|
|
2207
2299
|
return event.error === undefined ? base : {
|
|
@@ -2359,8 +2451,8 @@ function createJobLifecycleTracker(nc: NatsConnection): JobLifecycleTracker {
|
|
|
2359
2451
|
function createJobRef<TPayload, TResult>(args: {
|
|
2360
2452
|
nc: NatsConnection;
|
|
2361
2453
|
queueType: string;
|
|
2362
|
-
jobsBinding:
|
|
2363
|
-
queueBinding:
|
|
2454
|
+
jobsBinding: JobsBinding;
|
|
2455
|
+
queueBinding: JobsQueueBinding;
|
|
2364
2456
|
seed: JobSnapshot<TPayload, TResult>;
|
|
2365
2457
|
lifecycle: JobLifecycleTracker;
|
|
2366
2458
|
}): JobRef<TPayload, TResult> {
|
|
@@ -2467,6 +2559,10 @@ function createJobsFacade<
|
|
|
2467
2559
|
const handlers = new Map<string, RegisteredJobHandler<unknown, unknown>>();
|
|
2468
2560
|
const jobsFacade: Record<string, unknown> = {};
|
|
2469
2561
|
const lifecycle = createJobLifecycleTracker(args.nc);
|
|
2562
|
+
const keyCoordinator = createNatsJobKeyCoordinator(args.nc);
|
|
2563
|
+
const jobsBinding = args.jobsBinding
|
|
2564
|
+
? normalizeResourceJobsBinding(args.jobsBinding)
|
|
2565
|
+
: undefined;
|
|
2470
2566
|
let activeHost: JobWorkerHostAdapter | undefined;
|
|
2471
2567
|
let startupPromise:
|
|
2472
2568
|
| Promise<Result<JobWorkerHostAdapter, BaseError>>
|
|
@@ -2474,14 +2570,13 @@ function createJobsFacade<
|
|
|
2474
2570
|
let stopPromise: Promise<Result<void, BaseError>> | undefined;
|
|
2475
2571
|
|
|
2476
2572
|
for (const queueType of Object.keys(args.contractJobs ?? {})) {
|
|
2477
|
-
const queueBinding =
|
|
2573
|
+
const queueBinding = jobsBinding?.queues[queueType];
|
|
2478
2574
|
if (queueBinding) lifecycle.watch(queueBinding);
|
|
2479
2575
|
|
|
2480
2576
|
jobsFacade[queueType] = {
|
|
2481
2577
|
create: (payload) =>
|
|
2482
2578
|
AsyncResult.from((async () => {
|
|
2483
2579
|
try {
|
|
2484
|
-
const jobsBinding = args.jobsBinding;
|
|
2485
2580
|
if (!jobsBinding) {
|
|
2486
2581
|
return Result.err(
|
|
2487
2582
|
toUnexpectedError(new Error("Jobs bindings are unavailable")),
|
|
@@ -2499,6 +2594,7 @@ function createJobsFacade<
|
|
|
2499
2594
|
const manager = new InternalJobManager<unknown, unknown>({
|
|
2500
2595
|
nc: args.nc,
|
|
2501
2596
|
jobs: jobsBinding,
|
|
2597
|
+
keyCoordinator,
|
|
2502
2598
|
});
|
|
2503
2599
|
await args.nc.flush();
|
|
2504
2600
|
const created = await manager.create(queueType, payload);
|
|
@@ -2510,6 +2606,67 @@ function createJobsFacade<
|
|
|
2510
2606
|
seed: created as JobSnapshot<unknown, unknown>,
|
|
2511
2607
|
lifecycle,
|
|
2512
2608
|
}));
|
|
2609
|
+
} catch (cause) {
|
|
2610
|
+
if (cause instanceof JobNotEnqueuedError) {
|
|
2611
|
+
return Result.err(cause);
|
|
2612
|
+
}
|
|
2613
|
+
return Result.err(toUnexpectedError(cause));
|
|
2614
|
+
}
|
|
2615
|
+
})()),
|
|
2616
|
+
submit: (payload) =>
|
|
2617
|
+
AsyncResult.from((async () => {
|
|
2618
|
+
try {
|
|
2619
|
+
if (!jobsBinding) {
|
|
2620
|
+
return Result.err(
|
|
2621
|
+
toUnexpectedError(new Error("Jobs bindings are unavailable")),
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
const queueBinding = jobsBinding.queues[queueType];
|
|
2625
|
+
if (!queueBinding) {
|
|
2626
|
+
return Result.err(toUnexpectedError(
|
|
2627
|
+
new Error(
|
|
2628
|
+
`Jobs binding for queue '${queueType}' is unavailable`,
|
|
2629
|
+
),
|
|
2630
|
+
));
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
const manager = new InternalJobManager<unknown, unknown>({
|
|
2634
|
+
nc: args.nc,
|
|
2635
|
+
jobs: jobsBinding,
|
|
2636
|
+
keyCoordinator,
|
|
2637
|
+
});
|
|
2638
|
+
await args.nc.flush();
|
|
2639
|
+
const outcome = await manager.submit(queueType, payload);
|
|
2640
|
+
if (outcome.kind === "accepted") {
|
|
2641
|
+
return Result.ok({
|
|
2642
|
+
kind: "accepted",
|
|
2643
|
+
key: outcome.key,
|
|
2644
|
+
ref: createJobRef({
|
|
2645
|
+
nc: args.nc,
|
|
2646
|
+
queueType,
|
|
2647
|
+
jobsBinding,
|
|
2648
|
+
queueBinding,
|
|
2649
|
+
seed: outcome.job as JobSnapshot<unknown, unknown>,
|
|
2650
|
+
lifecycle,
|
|
2651
|
+
}),
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
if (outcome.kind === "replaced") {
|
|
2655
|
+
return Result.ok({
|
|
2656
|
+
kind: "replaced",
|
|
2657
|
+
key: outcome.key,
|
|
2658
|
+
replaced: outcome.replaced,
|
|
2659
|
+
ref: createJobRef({
|
|
2660
|
+
nc: args.nc,
|
|
2661
|
+
queueType,
|
|
2662
|
+
jobsBinding,
|
|
2663
|
+
queueBinding,
|
|
2664
|
+
seed: outcome.job as JobSnapshot<unknown, unknown>,
|
|
2665
|
+
lifecycle,
|
|
2666
|
+
}),
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
return Result.ok(outcome);
|
|
2513
2670
|
} catch (cause) {
|
|
2514
2671
|
return Result.err(toUnexpectedError(cause));
|
|
2515
2672
|
}
|
|
@@ -2554,7 +2711,7 @@ function createJobsFacade<
|
|
|
2554
2711
|
return Result.ok(host);
|
|
2555
2712
|
}
|
|
2556
2713
|
|
|
2557
|
-
if (!
|
|
2714
|
+
if (!jobsBinding || !args.workStream) {
|
|
2558
2715
|
return Result.err(toUnexpectedError(
|
|
2559
2716
|
new Error(
|
|
2560
2717
|
"Jobs infrastructure bindings are unavailable for this service",
|
|
@@ -2562,7 +2719,6 @@ function createJobsFacade<
|
|
|
2562
2719
|
));
|
|
2563
2720
|
}
|
|
2564
2721
|
|
|
2565
|
-
const jobsBinding = args.jobsBinding;
|
|
2566
2722
|
const workStream = args.workStream;
|
|
2567
2723
|
|
|
2568
2724
|
const hosts = [] as Array<{ stop(): Promise<void> }>;
|
|
@@ -2582,6 +2738,7 @@ function createJobsFacade<
|
|
|
2582
2738
|
const manager = new InternalJobManager<unknown, unknown>({
|
|
2583
2739
|
nc: args.nc,
|
|
2584
2740
|
jobs: jobsBinding,
|
|
2741
|
+
keyCoordinator,
|
|
2585
2742
|
});
|
|
2586
2743
|
const host = await startNatsWorkerHostFromBinding<unknown>({
|
|
2587
2744
|
jobs: jobsBinding,
|
|
@@ -2750,6 +2907,7 @@ function createBoundJobsFacade<
|
|
|
2750
2907
|
if (!queue) continue;
|
|
2751
2908
|
boundJobs[queueType] = {
|
|
2752
2909
|
create: (payload) => queue.create(payload),
|
|
2910
|
+
submit: (payload) => queue.submit(payload),
|
|
2753
2911
|
handle: (handler) =>
|
|
2754
2912
|
queue.handle(({ job, client }) =>
|
|
2755
2913
|
handler({
|
|
@@ -3339,6 +3497,7 @@ export class TrellisService<
|
|
|
3339
3497
|
async stop(): Promise<void> {
|
|
3340
3498
|
this.#stopPromise ??= (async () => {
|
|
3341
3499
|
this.connection.stopObserving();
|
|
3500
|
+
this.#handlerTrellis.stopEventListeners();
|
|
3342
3501
|
|
|
3343
3502
|
try {
|
|
3344
3503
|
await this.#stopHealthPublishing();
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import "../_dnt.polyfills.js";
|
|
2
|
+
import { type SQL, sql } from "drizzle-orm";
|
|
3
|
+
import type { SqlExecutor, SqlRow } from "./outbox_inbox.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Structural Drizzle SQLite database or transaction shape accepted by Trellis
|
|
7
|
+
* SQL outbox helpers.
|
|
8
|
+
*/
|
|
9
|
+
export type DrizzleSqlDatabase = {
|
|
10
|
+
/** Runs a SQL statement that returns rows. */
|
|
11
|
+
all(query: SQL): Promise<readonly SqlRow[]>;
|
|
12
|
+
/** Runs a SQL statement that does not return rows. */
|
|
13
|
+
run(query: SQL): Promise<unknown>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adapts a caller-owned Drizzle SQLite database or transaction to Trellis'
|
|
18
|
+
* generic `SqlExecutor` interface.
|
|
19
|
+
*/
|
|
20
|
+
export function createDrizzleSqlExecutor(
|
|
21
|
+
database: DrizzleSqlDatabase,
|
|
22
|
+
): SqlExecutor {
|
|
23
|
+
return {
|
|
24
|
+
query(statement, params) {
|
|
25
|
+
return database.all(bindDrizzleSqlStatement(statement, params));
|
|
26
|
+
},
|
|
27
|
+
async execute(statement, params) {
|
|
28
|
+
await database.run(bindDrizzleSqlStatement(statement, params));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Converts a Trellis SQL statement with positional placeholders into a Drizzle
|
|
35
|
+
* `SQL` object with parameters bound in order.
|
|
36
|
+
*
|
|
37
|
+
* Supports SQLite-style `?` placeholders and PostgreSQL-style `$1`, `$2`, ...
|
|
38
|
+
* placeholders. A single statement must not mix both styles.
|
|
39
|
+
*/
|
|
40
|
+
export function bindDrizzleSqlStatement(
|
|
41
|
+
statement: string,
|
|
42
|
+
params: readonly unknown[],
|
|
43
|
+
): SQL {
|
|
44
|
+
const matches = [...statement.matchAll(/\?|\$(\d+)/g)];
|
|
45
|
+
const hasQuestionPlaceholders = matches.some((match) => match[0] === "?");
|
|
46
|
+
const hasNumberedPlaceholders = matches.some((match) =>
|
|
47
|
+
match[1] !== undefined
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (hasQuestionPlaceholders && hasNumberedPlaceholders) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"SQL statement cannot mix ? and $n placeholders for Drizzle binding",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (hasQuestionPlaceholders) {
|
|
57
|
+
validateQuestionPlaceholders(matches.length, params.length);
|
|
58
|
+
} else if (hasNumberedPlaceholders) {
|
|
59
|
+
validateNumberedPlaceholders(matches, params.length);
|
|
60
|
+
} else if (params.length !== 0) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`SQL statement has no placeholders but received ${params.length} parameters`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const chunks: SQL[] = [];
|
|
67
|
+
let cursor = 0;
|
|
68
|
+
let nextQuestionParam = 0;
|
|
69
|
+
for (const match of matches) {
|
|
70
|
+
const matchIndex = match.index;
|
|
71
|
+
chunks.push(sql.raw(statement.slice(cursor, matchIndex)));
|
|
72
|
+
if (match[0] === "?") {
|
|
73
|
+
chunks.push(sql`${params[nextQuestionParam]}`);
|
|
74
|
+
nextQuestionParam += 1;
|
|
75
|
+
} else {
|
|
76
|
+
const index = Number(match[1]) - 1;
|
|
77
|
+
chunks.push(sql`${params[index]}`);
|
|
78
|
+
}
|
|
79
|
+
cursor = matchIndex + match[0].length;
|
|
80
|
+
}
|
|
81
|
+
chunks.push(sql.raw(statement.slice(cursor)));
|
|
82
|
+
|
|
83
|
+
return sql.join(chunks);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateQuestionPlaceholders(
|
|
87
|
+
placeholders: number,
|
|
88
|
+
paramCount: number,
|
|
89
|
+
): void {
|
|
90
|
+
if (placeholders !== paramCount) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`SQL statement expected ${placeholders} parameters for ? placeholders, received ${paramCount}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function validateNumberedPlaceholders(
|
|
98
|
+
matches: RegExpMatchArray[],
|
|
99
|
+
paramCount: number,
|
|
100
|
+
): void {
|
|
101
|
+
const referenced = new Set<number>();
|
|
102
|
+
for (const match of matches) {
|
|
103
|
+
if (match[1] === undefined) continue;
|
|
104
|
+
const index = Number(match[1]);
|
|
105
|
+
if (index < 1) {
|
|
106
|
+
throw new Error("PostgreSQL SQL placeholder indexes must start at $1");
|
|
107
|
+
}
|
|
108
|
+
referenced.add(index);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const maxIndex = Math.max(...referenced);
|
|
112
|
+
if (maxIndex !== paramCount) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`SQL statement expected ${maxIndex} parameters for $n placeholders, received ${paramCount}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (let index = 1; index <= maxIndex; index += 1) {
|
|
119
|
+
if (!referenced.has(index)) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`SQL statement is missing PostgreSQL placeholder $${index}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/service/mod.ts
CHANGED
|
@@ -698,14 +698,13 @@ export async function dispatchOutbox(
|
|
|
698
698
|
export function outboxMessageToPrepared(
|
|
699
699
|
message: OutboxMessage,
|
|
700
700
|
): PreparedTrellisEvent {
|
|
701
|
+
const payload = JSON.parse(message.payload) as Record<string, unknown>;
|
|
702
|
+
const header = eventHeaderFromMessage(message.headers);
|
|
701
703
|
return Object.freeze({
|
|
702
704
|
event: message.event,
|
|
703
705
|
subject: message.subject,
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
header: { id: string; time: string };
|
|
707
|
-
},
|
|
708
|
-
),
|
|
706
|
+
header: Object.freeze(header),
|
|
707
|
+
payload: Object.freeze(payload),
|
|
709
708
|
encodedPayload: message.payload,
|
|
710
709
|
headers: Object.freeze({ ...message.headers }),
|
|
711
710
|
});
|
|
@@ -713,7 +712,18 @@ export function outboxMessageToPrepared(
|
|
|
713
712
|
|
|
714
713
|
function messageId(event: PreparedTrellisEvent): string {
|
|
715
714
|
return event.headers["Nats-Msg-Id"] ?? event.headers["nats-msg-id"] ??
|
|
716
|
-
event.
|
|
715
|
+
event.header.id;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function eventHeaderFromMessage(
|
|
719
|
+
headers: Record<string, string>,
|
|
720
|
+
): { id: string; time: string } {
|
|
721
|
+
const id = headers["Nats-Msg-Id"] ?? headers["nats-msg-id"];
|
|
722
|
+
const time = headers["Trellis-Event-Time"] ?? headers["trellis-event-time"];
|
|
723
|
+
return {
|
|
724
|
+
id: typeof id === "string" ? id : "",
|
|
725
|
+
time: typeof time === "string" ? time : new Date(0).toISOString(),
|
|
726
|
+
};
|
|
717
727
|
}
|
|
718
728
|
|
|
719
729
|
function rowToOutboxMessage(row: SqlRow): OutboxMessage {
|