@shivaedev/jobs 0.31.0 → 0.34.0
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/CHANGELOG.md +40 -0
- package/README.md +75 -30
- package/dist/server/adapters/inspection.d.ts +5 -0
- package/dist/server/adapters/inspection.d.ts.map +1 -0
- package/dist/server/adapters/inspection.js +25 -0
- package/dist/server/adapters/inspection.js.map +1 -0
- package/dist/server/adapters/inspectionChanges.d.ts +7 -0
- package/dist/server/adapters/inspectionChanges.d.ts.map +1 -0
- package/dist/server/adapters/inspectionChanges.js +23 -0
- package/dist/server/adapters/inspectionChanges.js.map +1 -0
- package/dist/server/adapters/inspectionDeferrals.d.ts +10 -0
- package/dist/server/adapters/inspectionDeferrals.d.ts.map +1 -0
- package/dist/server/adapters/inspectionDeferrals.js +13 -0
- package/dist/server/adapters/inspectionDeferrals.js.map +1 -0
- package/dist/server/adapters/inspectionJobs.d.ts +50 -0
- package/dist/server/adapters/inspectionJobs.d.ts.map +1 -0
- package/dist/server/adapters/inspectionJobs.js +21 -0
- package/dist/server/adapters/inspectionJobs.js.map +1 -0
- package/dist/server/adapters/inspectionOperation.d.ts +3 -0
- package/dist/server/adapters/inspectionOperation.d.ts.map +1 -0
- package/dist/server/adapters/inspectionOperation.js +6 -0
- package/dist/server/adapters/inspectionOperation.js.map +1 -0
- package/dist/server/adapters/inspectionPlan.d.ts +3 -0
- package/dist/server/adapters/inspectionPlan.d.ts.map +1 -0
- package/dist/server/adapters/inspectionPlan.js +20 -0
- package/dist/server/adapters/inspectionPlan.js.map +1 -0
- package/dist/server/adapters/inspectionReads.d.ts +59 -0
- package/dist/server/adapters/inspectionReads.d.ts.map +1 -0
- package/dist/server/adapters/inspectionReads.js +44 -0
- package/dist/server/adapters/inspectionReads.js.map +1 -0
- package/dist/server/adapters/inspectionSnapshot.d.ts +6 -0
- package/dist/server/adapters/inspectionSnapshot.d.ts.map +1 -0
- package/dist/server/{internal/testingSnapshot.js → adapters/inspectionSnapshot.js} +9 -7
- package/dist/server/adapters/inspectionSnapshot.js.map +1 -0
- package/dist/server/adapters/jobEngine.d.ts.map +1 -1
- package/dist/server/adapters/jobEngine.js +0 -4
- package/dist/server/adapters/jobEngine.js.map +1 -1
- package/dist/server/adapters/payloadIdentity.d.ts +3 -0
- package/dist/server/adapters/payloadIdentity.d.ts.map +1 -0
- package/dist/server/adapters/payloadIdentity.js +14 -0
- package/dist/server/adapters/payloadIdentity.js.map +1 -0
- package/dist/server/{internal/testingSignal.d.ts → adapters/queueSignal.d.ts} +2 -2
- package/dist/server/adapters/queueSignal.d.ts.map +1 -0
- package/dist/server/{internal/testingSignal.js → adapters/queueSignal.js} +2 -2
- package/dist/server/adapters/queueSignal.js.map +1 -0
- package/dist/server/adapters/settleDeferral.d.ts.map +1 -1
- package/dist/server/adapters/settleDeferral.js +8 -2
- package/dist/server/adapters/settleDeferral.js.map +1 -1
- package/dist/server/adapters/testingClient.d.ts +1 -2
- package/dist/server/adapters/testingClient.d.ts.map +1 -1
- package/dist/server/adapters/testingClient.js +25 -45
- package/dist/server/adapters/testingClient.js.map +1 -1
- package/dist/server/adapters/testingController.d.ts +58 -12
- package/dist/server/adapters/testingController.d.ts.map +1 -1
- package/dist/server/adapters/testingController.js +40 -18
- package/dist/server/adapters/testingController.js.map +1 -1
- package/dist/server/adapters/testingDueNow.js +1 -1
- package/dist/server/adapters/testingDueNow.js.map +1 -1
- package/dist/server/adapters/testingEngine.d.ts.map +1 -1
- package/dist/server/adapters/testingEngine.js +0 -11
- package/dist/server/adapters/testingEngine.js.map +1 -1
- package/dist/server/adapters/testingInspectionClient.d.ts +5 -0
- package/dist/server/adapters/testingInspectionClient.d.ts.map +1 -0
- package/dist/server/adapters/testingInspectionClient.js +52 -0
- package/dist/server/adapters/testingInspectionClient.js.map +1 -0
- package/dist/server/adapters/testingTime.d.ts +10 -0
- package/dist/server/adapters/testingTime.d.ts.map +1 -0
- package/dist/server/adapters/testingTime.js +18 -0
- package/dist/server/adapters/testingTime.js.map +1 -0
- package/dist/server/client.d.ts +3 -0
- package/dist/server/client.d.ts.map +1 -1
- package/dist/server/clientOptions.d.ts +0 -1
- package/dist/server/clientOptions.d.ts.map +1 -1
- package/dist/server/definition.d.ts +12 -11
- package/dist/server/definition.d.ts.map +1 -1
- package/dist/server/definition.js +2 -1
- package/dist/server/definition.js.map +1 -1
- package/dist/server/engineClient.d.ts +0 -5
- package/dist/server/engineClient.d.ts.map +1 -1
- package/dist/server/enqueueOptions.d.ts +6 -0
- package/dist/server/enqueueOptions.d.ts.map +1 -1
- package/dist/server/error.d.ts +1 -1
- package/dist/server/error.d.ts.map +1 -1
- package/dist/server/error.js.map +1 -1
- package/dist/server/inspection.d.ts +102 -0
- package/dist/server/inspection.d.ts.map +1 -0
- package/dist/server/inspection.js +30 -0
- package/dist/server/inspection.js.map +1 -0
- package/dist/server/internal/adapters/deferralChanges.d.ts +3 -0
- package/dist/server/internal/adapters/deferralChanges.d.ts.map +1 -0
- package/dist/server/internal/adapters/deferralChanges.js +2 -0
- package/dist/server/internal/adapters/deferralChanges.js.map +1 -0
- package/dist/server/internal/adapters/testingFetches.d.ts +2 -2
- package/dist/server/internal/adapters/testingFetches.d.ts.map +1 -1
- package/dist/server/internal/adapters/testingFetches.js.map +1 -1
- package/dist/server/internal/deferral.d.ts +0 -2
- package/dist/server/internal/deferral.d.ts.map +1 -1
- package/dist/server/internal/deferral.js +0 -1
- package/dist/server/internal/deferral.js.map +1 -1
- package/dist/server/internal/engine.d.ts +0 -5
- package/dist/server/internal/engine.d.ts.map +1 -1
- package/dist/server/internal/engine.js.map +1 -1
- package/dist/server/internal/enqueueFor.d.ts.map +1 -1
- package/dist/server/internal/enqueueFor.js +11 -1
- package/dist/server/internal/enqueueFor.js.map +1 -1
- package/dist/server/internal/jobStorePlans.d.ts.map +1 -1
- package/dist/server/internal/jobStorePlans.js +2 -1
- package/dist/server/internal/jobStorePlans.js.map +1 -1
- package/dist/server/internal/makeService.d.ts.map +1 -1
- package/dist/server/internal/makeService.js +7 -6
- package/dist/server/internal/makeService.js.map +1 -1
- package/dist/server/internal/register.d.ts.map +1 -1
- package/dist/server/internal/register.js +0 -4
- package/dist/server/internal/register.js.map +1 -1
- package/dist/server/internal/testingIdle.d.ts +4 -3
- package/dist/server/internal/testingIdle.d.ts.map +1 -1
- package/dist/server/internal/testingIdle.js +7 -26
- package/dist/server/internal/testingIdle.js.map +1 -1
- package/dist/server/internal/testingOperation.d.ts +5 -0
- package/dist/server/internal/testingOperation.d.ts.map +1 -0
- package/dist/server/internal/testingOperation.js +12 -0
- package/dist/server/internal/testingOperation.js.map +1 -0
- package/dist/server/internal/testingSchedules.d.ts +8 -5
- package/dist/server/internal/testingSchedules.d.ts.map +1 -1
- package/dist/server/internal/testingSchedules.js +14 -9
- package/dist/server/internal/testingSchedules.js.map +1 -1
- package/dist/server/internal/testingWaits.d.ts +7 -4
- package/dist/server/internal/testingWaits.d.ts.map +1 -1
- package/dist/server/internal/testingWaits.js +7 -10
- package/dist/server/internal/testingWaits.js.map +1 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +5 -0
- package/dist/server/schema.js.map +1 -1
- package/dist/server/service.d.ts +4 -4
- package/dist/server/service.d.ts.map +1 -1
- package/dist/server/service.js +1 -0
- package/dist/server/service.js.map +1 -1
- package/dist/server/testing.d.ts +63 -25
- package/dist/server/testing.d.ts.map +1 -1
- package/dist/server/testing.js.map +1 -1
- package/package.json +7 -7
- package/src/server/adapters/inspection.ts +31 -0
- package/src/server/adapters/inspectionChanges.ts +31 -0
- package/src/server/{internal/testingDeferrals.ts → adapters/inspectionDeferrals.ts} +10 -12
- package/src/server/adapters/inspectionJobs.ts +28 -0
- package/src/server/adapters/inspectionOperation.ts +6 -0
- package/src/server/adapters/inspectionPlan.ts +21 -0
- package/src/server/adapters/inspectionReads.ts +55 -0
- package/src/server/{internal/testingSnapshot.ts → adapters/inspectionSnapshot.ts} +15 -17
- package/src/server/adapters/jobEngine.ts +0 -4
- package/src/server/adapters/payloadIdentity.ts +15 -0
- package/src/server/{internal/testingSignal.ts → adapters/queueSignal.ts} +1 -1
- package/src/server/adapters/settleDeferral.ts +8 -2
- package/src/server/adapters/testingClient.ts +11 -30
- package/src/server/adapters/testingController.ts +49 -21
- package/src/server/adapters/testingDueNow.ts +1 -1
- package/src/server/adapters/testingEngine.ts +0 -13
- package/src/server/adapters/testingInspectionClient.ts +70 -0
- package/src/server/adapters/testingTime.ts +25 -0
- package/src/server/client.ts +3 -0
- package/src/server/clientOptions.ts +0 -1
- package/src/server/definition.ts +18 -12
- package/src/server/engineClient.ts +0 -2
- package/src/server/enqueueOptions.ts +5 -0
- package/src/server/error.ts +1 -0
- package/src/server/inspection.ts +50 -0
- package/src/server/internal/adapters/deferralChanges.ts +3 -0
- package/src/server/internal/adapters/testingFetches.ts +2 -2
- package/src/server/internal/deferral.ts +0 -2
- package/src/server/internal/engine.ts +0 -6
- package/src/server/internal/enqueueFor.ts +14 -1
- package/src/server/internal/jobStorePlans.ts +2 -1
- package/src/server/internal/makeService.ts +7 -6
- package/src/server/internal/register.ts +0 -4
- package/src/server/internal/testingIdle.ts +10 -31
- package/src/server/internal/testingOperation.ts +18 -0
- package/src/server/internal/testingSchedules.ts +28 -16
- package/src/server/internal/testingWaits.ts +11 -16
- package/src/server/schema.ts +5 -0
- package/src/server/service.ts +5 -2
- package/src/server/testing.ts +3 -12
- package/dist/server/adapters/observeTestingSettlements.d.ts +0 -6
- package/dist/server/adapters/observeTestingSettlements.d.ts.map +0 -1
- package/dist/server/adapters/observeTestingSettlements.js +0 -35
- package/dist/server/adapters/observeTestingSettlements.js.map +0 -1
- package/dist/server/internal/testingDeferrals.d.ts +0 -10
- package/dist/server/internal/testingDeferrals.d.ts.map +0 -1
- package/dist/server/internal/testingDeferrals.js +0 -15
- package/dist/server/internal/testingDeferrals.js.map +0 -1
- package/dist/server/internal/testingReadout.d.ts +0 -10
- package/dist/server/internal/testingReadout.d.ts.map +0 -1
- package/dist/server/internal/testingReadout.js +0 -13
- package/dist/server/internal/testingReadout.js.map +0 -1
- package/dist/server/internal/testingRegistrations.d.ts +0 -4
- package/dist/server/internal/testingRegistrations.d.ts.map +0 -1
- package/dist/server/internal/testingRegistrations.js +0 -8
- package/dist/server/internal/testingRegistrations.js.map +0 -1
- package/dist/server/internal/testingSignal.d.ts.map +0 -1
- package/dist/server/internal/testingSignal.js.map +0 -1
- package/dist/server/internal/testingSnapshot.d.ts +0 -6
- package/dist/server/internal/testingSnapshot.d.ts.map +0 -1
- package/dist/server/internal/testingSnapshot.js.map +0 -1
- package/src/server/adapters/observeTestingSettlements.ts +0 -36
- package/src/server/internal/testingReadout.ts +0 -25
- package/src/server/internal/testingRegistrations.ts +0 -10
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { inspectionDeferrals } from "#server/adapters/inspectionDeferrals.ts";
|
|
3
|
+
import { inspectionJobs } from "#server/adapters/inspectionJobs.ts";
|
|
4
|
+
import { inspectionOperation } from "#server/adapters/inspectionOperation.ts";
|
|
5
|
+
import { inspectionSnapshot } from "#server/adapters/inspectionSnapshot.ts";
|
|
6
|
+
import type { PgBossClient } from "#server/client.ts";
|
|
7
|
+
import type { QueueDefinition } from "#server/definition.ts";
|
|
8
|
+
import { quotedSchema } from "#server/internal/storedRows.ts";
|
|
9
|
+
|
|
10
|
+
const Busy = Schema.Struct({ busy: Schema.Boolean });
|
|
11
|
+
export function inspectionReads(client: PgBossClient, schema: string) {
|
|
12
|
+
const quoted = quotedSchema(schema);
|
|
13
|
+
function query(text: string, values?: unknown[]) {
|
|
14
|
+
const db = client.getDb?.();
|
|
15
|
+
if (db === undefined) {
|
|
16
|
+
throw new TypeError("This pg-boss client does not support getDb");
|
|
17
|
+
}
|
|
18
|
+
return db.executeSql(text, values);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...inspectionDeferrals(client),
|
|
22
|
+
busy: (queues: readonly Pick<QueueDefinition, "name">[]) =>
|
|
23
|
+
inspectionOperation(async () => {
|
|
24
|
+
const { rows } = await query(
|
|
25
|
+
`/* jobs inspection busy */ SELECT EXISTS (
|
|
26
|
+
SELECT 1 FROM ${quoted}.job WHERE name = ANY($1::text[]) AND (
|
|
27
|
+
(state < 'active' AND NOT blocked AND start_after <= ${quoted}.job_now()) OR state = 'active'
|
|
28
|
+
)
|
|
29
|
+
) AS busy`,
|
|
30
|
+
[queues.map(({ name }) => name)],
|
|
31
|
+
);
|
|
32
|
+
return Schema.decodeUnknownSync(Busy)(rows[0]).busy;
|
|
33
|
+
}),
|
|
34
|
+
...inspectionJobs(client),
|
|
35
|
+
queue: (queue: Pick<QueueDefinition, "name">) => inspectionOperation(() => client.getQueue(queue.name), queue.name),
|
|
36
|
+
schedules: (name?: string, key?: string) =>
|
|
37
|
+
inspectionOperation(() => {
|
|
38
|
+
if (client.getSchedules === undefined) {
|
|
39
|
+
throw new TypeError("This pg-boss client does not support getSchedules");
|
|
40
|
+
}
|
|
41
|
+
return client.getSchedules(name, key);
|
|
42
|
+
}),
|
|
43
|
+
schemaVersion: () =>
|
|
44
|
+
inspectionOperation(() => {
|
|
45
|
+
if (client.schemaVersion === undefined) {
|
|
46
|
+
throw new TypeError("This pg-boss client does not support schemaVersion");
|
|
47
|
+
}
|
|
48
|
+
return client.schemaVersion();
|
|
49
|
+
}),
|
|
50
|
+
snapshot: inspectionSnapshot(
|
|
51
|
+
() => client,
|
|
52
|
+
() => schema,
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
2
|
+
import { inspectionOperation } from "#server/adapters/inspectionOperation.ts";
|
|
3
|
+
import type { PgBossClient } from "#server/client.ts";
|
|
4
|
+
import type { PgBossError, PgBossPayloadError } from "#server/error.ts";
|
|
5
|
+
import type { SnapshotQueue, UnfinishedJob } from "#server/inspection.ts";
|
|
4
6
|
import { decodeStoredData, quotedSchema } from "#server/internal/storedRows.ts";
|
|
5
|
-
import { testingOperation } from "#server/internal/testingReadout.ts";
|
|
6
|
-
import type { SnapshotQueue, UnfinishedJob } from "#server/testing.ts";
|
|
7
7
|
|
|
8
8
|
const Rows = Schema.Array(
|
|
9
9
|
Schema.Struct({
|
|
@@ -15,29 +15,27 @@ const Rows = Schema.Array(
|
|
|
15
15
|
}),
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
export function
|
|
18
|
+
export function inspectionSnapshot(client: () => PgBossClient, schema: () => string) {
|
|
19
19
|
return <const TQueues extends readonly SnapshotQueue[]>(
|
|
20
20
|
queues: TQueues,
|
|
21
|
-
): Effect.Effect<
|
|
22
|
-
readonly UnfinishedJob<TQueues[number]>[],
|
|
23
|
-
PgBossPayloadError | PgBossTestingError,
|
|
24
|
-
TQueues[number]["schema"]["DecodingServices"]
|
|
25
|
-
> => {
|
|
21
|
+
): Effect.Effect<readonly UnfinishedJob<TQueues[number]>[], PgBossPayloadError | PgBossError, TQueues[number]["schema"]["DecodingServices"]> => {
|
|
26
22
|
const byName = new Map<string, TQueues[number]>(queues.map((queue) => [queue.name, queue]));
|
|
27
|
-
return
|
|
23
|
+
return inspectionOperation(async () => {
|
|
28
24
|
const quoted = quotedSchema(schema());
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
const db = client().getDb?.();
|
|
26
|
+
if (db === undefined) {
|
|
27
|
+
throw new TypeError("This pg-boss client does not support getDb");
|
|
28
|
+
}
|
|
29
|
+
const { rows } = await db.executeSql(
|
|
30
|
+
`SELECT id, name AS queue, data, start_after AS "startAfter",
|
|
33
31
|
CASE WHEN state = 'retry' AND started_on IS NULL AND start_after > ${quoted}.job_now()
|
|
34
32
|
AND output ? 'jobDeferral'
|
|
35
33
|
THEN 'deferred' ELSE state::text END AS state
|
|
36
34
|
FROM ${quoted}.job
|
|
37
35
|
WHERE name = ANY($1::text[]) AND state IN ('created', 'retry', 'active', 'failed')
|
|
38
36
|
ORDER BY created_on, id`,
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
[[...byName.keys()]],
|
|
38
|
+
);
|
|
41
39
|
return Schema.decodeUnknownPromise(Rows)(rows);
|
|
42
40
|
}).pipe(
|
|
43
41
|
Effect.flatMap((rows) =>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type Context, Effect } from "effect";
|
|
2
2
|
import type { PgBossClient } from "#server/client.ts";
|
|
3
3
|
import { toPgBossError } from "#server/error.ts";
|
|
4
|
-
import { deferralObservers } from "#server/internal/deferral.ts";
|
|
5
4
|
import type { JobEngine } from "#server/internal/engine.ts";
|
|
6
5
|
import { listJobs } from "./listJobs.ts";
|
|
7
6
|
import { queueAdministration } from "./queueAdministration.ts";
|
|
@@ -49,9 +48,6 @@ export function jobEngine(client: PgBossClient, schema = "pgboss"): Context.Serv
|
|
|
49
48
|
// Native batch settlement must not match a sibling's new claim at the unchanged retry count.
|
|
50
49
|
job.retryCount = Number.MAX_SAFE_INTEGER;
|
|
51
50
|
}
|
|
52
|
-
if (settled.deferred) {
|
|
53
|
-
deferralObservers.get(client)?.();
|
|
54
|
-
}
|
|
55
51
|
}),
|
|
56
52
|
),
|
|
57
53
|
),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export function payloadKey(parts: readonly string[], payload: object): string {
|
|
4
|
+
return JSON.stringify(parts.map((part) => [part, typeof Reflect.get(payload, part), String(Reflect.get(payload, part))]));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function payloadJobId(queue: string, parts: readonly string[], payload: object): string {
|
|
8
|
+
const bytes = createHash("sha256")
|
|
9
|
+
.update(JSON.stringify([queue, payloadKey(parts, payload)]))
|
|
10
|
+
.digest();
|
|
11
|
+
bytes.writeUInt8((bytes.readUInt8(6) & 0x0f) | 0x80, 6);
|
|
12
|
+
bytes.writeUInt8((bytes.readUInt8(8) & 0x3f) | 0x80, 8);
|
|
13
|
+
const hex = bytes.subarray(0, 16).toString("hex");
|
|
14
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
15
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import type { PgBossClient } from "#server/client.ts";
|
|
3
|
+
import { deferralChanges } from "#server/internal/adapters/deferralChanges.ts";
|
|
3
4
|
import { STATELY_DEFERRAL_POLICY } from "#server/internal/deferral.ts";
|
|
4
5
|
import type { JobAttempt } from "#server/internal/engine.ts";
|
|
5
6
|
import { quotedSchema } from "#server/internal/storedRows.ts";
|
|
@@ -11,8 +12,9 @@ export async function settleDeferral(client: PgBossClient, schema: string, job:
|
|
|
11
12
|
throw new TypeError("This pg-boss client does not support getDb for RetryAt");
|
|
12
13
|
}
|
|
13
14
|
const quoted = quotedSchema(schema);
|
|
15
|
+
const db = client.getDb();
|
|
14
16
|
// Native failure settlement deletes and reinserts jobs, so a cascading job foreign key would erase the counter.
|
|
15
|
-
const { rows } = await
|
|
17
|
+
const { rows } = await db.executeSql(
|
|
16
18
|
`WITH claimed AS MATERIALIZED (
|
|
17
19
|
SELECT name, id, created_on,
|
|
18
20
|
GREATEST(keep_until, $4::timestamptz + (keep_until - start_after)) AS keep_until
|
|
@@ -50,5 +52,9 @@ export async function settleDeferral(client: PgBossClient, schema: string, job:
|
|
|
50
52
|
SELECT EXISTS (SELECT 1 FROM claimed) AS claimed, EXISTS (SELECT 1 FROM deferred) AS deferred`,
|
|
51
53
|
[job.name, job.id, job.retryCount, at, reason, Reflect.get(job, "startedOn"), maxDeferrals],
|
|
52
54
|
);
|
|
53
|
-
|
|
55
|
+
const settled = Schema.decodeUnknownSync(Settlement)(rows[0]);
|
|
56
|
+
if (settled.deferred) {
|
|
57
|
+
deferralChanges.get(db)?.();
|
|
58
|
+
}
|
|
59
|
+
return settled;
|
|
54
60
|
}
|
|
@@ -4,9 +4,8 @@ import { testingExpirations } from "#server/adapters/testingExpirations.ts";
|
|
|
4
4
|
import type { ClientUpdateOptions, ClientUpdateRequest, ClientUpdateResult, PgBossClient } from "#server/client.ts";
|
|
5
5
|
import type { EngineClient } from "#server/engineClient.ts";
|
|
6
6
|
import type { EnqueueOptions } from "#server/enqueueOptions.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { deferralChanges } from "#server/internal/adapters/deferralChanges.ts";
|
|
8
8
|
import type { TestingBatch, TestingWork } from "#server/internal/testingIdle.ts";
|
|
9
|
-
import { testingRegistration } from "#server/internal/testingRegistrations.ts";
|
|
10
9
|
import type { WorkerJob } from "#server/job.ts";
|
|
11
10
|
|
|
12
11
|
export interface TestingControls {
|
|
@@ -18,18 +17,10 @@ export interface TestingControls {
|
|
|
18
17
|
readonly work: TestingWork;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export function testingClient(client: EngineClient, schema: string, controls: TestingControls):
|
|
20
|
+
export function testingClient(client: EngineClient, schema: string, controls: TestingControls): EngineClient {
|
|
22
21
|
const engine = testingEngine(client, schema);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for (const batch of controls.work.batches) {
|
|
26
|
-
if (batch.name === name) {
|
|
27
|
-
batch.settled = true;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
controls.changed();
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
22
|
+
deferralChanges.set(client.getDb(), controls.changed);
|
|
23
|
+
|
|
33
24
|
const expirations = testingExpirations(client);
|
|
34
25
|
function update(request: ClientUpdateRequest): Promise<ClientUpdateResult>;
|
|
35
26
|
function update(name: string, data: object | null | undefined, options?: ClientUpdateOptions): Promise<ClientUpdateResult>;
|
|
@@ -45,6 +36,9 @@ export function testingClient(client: EngineClient, schema: string, controls: Te
|
|
|
45
36
|
return result;
|
|
46
37
|
},
|
|
47
38
|
createQueue: (name, options) => {
|
|
39
|
+
if (options?.deadLetter !== undefined) {
|
|
40
|
+
controls.work.queues.add(name);
|
|
41
|
+
}
|
|
48
42
|
const { retryDelayMax, ...rest } = options ?? {};
|
|
49
43
|
return client.createQueue(name, {
|
|
50
44
|
...rest,
|
|
@@ -84,13 +78,6 @@ export function testingClient(client: EngineClient, schema: string, controls: Te
|
|
|
84
78
|
controls.changed();
|
|
85
79
|
return id;
|
|
86
80
|
},
|
|
87
|
-
stop: async (options) => {
|
|
88
|
-
try {
|
|
89
|
-
await client.stop(options);
|
|
90
|
-
} finally {
|
|
91
|
-
await Effect.runPromise(settlements.close);
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
81
|
update,
|
|
95
82
|
updateQueue: (name, options) =>
|
|
96
83
|
client.updateQueue(name, {
|
|
@@ -98,10 +85,9 @@ export function testingClient(client: EngineClient, schema: string, controls: Te
|
|
|
98
85
|
...controls.retryPolicy,
|
|
99
86
|
...(controls.retryPolicy.retryBackoff === false ? { retryDelayMax: null } : {}),
|
|
100
87
|
}),
|
|
101
|
-
work: async (name, options, handler) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const batch: TestingBatch = { name, settled: false };
|
|
88
|
+
work: async (name, options, handler) =>
|
|
89
|
+
client.work(name, options, async (jobs: readonly WorkerJob<unknown>[]) => {
|
|
90
|
+
const batch: TestingBatch = { jobs: new Map(jobs.map((job) => [job.id, job.retryCount])), name, settled: false };
|
|
105
91
|
controls.work.batches.add(batch);
|
|
106
92
|
controls.changed();
|
|
107
93
|
try {
|
|
@@ -121,14 +107,10 @@ export function testingClient(client: EngineClient, schema: string, controls: Te
|
|
|
121
107
|
controls.work.batches.delete(batch);
|
|
122
108
|
controls.changed();
|
|
123
109
|
}
|
|
124
|
-
})
|
|
125
|
-
},
|
|
110
|
+
}),
|
|
126
111
|
};
|
|
127
112
|
const wrapped = new Proxy(client, {
|
|
128
113
|
get(target, property) {
|
|
129
|
-
if (property === testingRegistration) {
|
|
130
|
-
return (name: string) => controls.work.queues.add(name);
|
|
131
|
-
}
|
|
132
114
|
if (Object.hasOwn(overrides, property)) {
|
|
133
115
|
const override: unknown = Reflect.get(overrides, property);
|
|
134
116
|
return override;
|
|
@@ -137,6 +119,5 @@ export function testingClient(client: EngineClient, schema: string, controls: Te
|
|
|
137
119
|
return typeof value === "function" ? value.bind(target) : value;
|
|
138
120
|
},
|
|
139
121
|
});
|
|
140
|
-
deferralObservers.set(wrapped, controls.changed);
|
|
141
122
|
return wrapped;
|
|
142
123
|
}
|
|
@@ -1,32 +1,36 @@
|
|
|
1
|
-
import { Duration, Effect, Redacted } from "effect";
|
|
1
|
+
import { Duration, Effect, Redacted, type Schema } from "effect";
|
|
2
|
+
import { TestClock as PgBossTestClock } from "pg-boss";
|
|
2
3
|
import { defaultClientFactory } from "#server/adapters/defaultClientFactory.ts";
|
|
3
4
|
import { testingClient } from "#server/adapters/testingClient.ts";
|
|
4
5
|
import { testingDueNow } from "#server/adapters/testingDueNow.ts";
|
|
5
|
-
import
|
|
6
|
-
import type {
|
|
6
|
+
import { testingTime } from "#server/adapters/testingTime.ts";
|
|
7
|
+
import type { JobReadOptions, PgBossClientFactory } from "#server/client.ts";
|
|
8
|
+
import type { JobPayloadSchema, QueueDefinition } from "#server/definition.ts";
|
|
7
9
|
import type { EngineClient } from "#server/engineClient.ts";
|
|
8
10
|
import { PgBossTestingError } from "#server/error.ts";
|
|
11
|
+
import type { PgBossInspectionService, SnapshotQueue } from "#server/inspection.ts";
|
|
9
12
|
import { type TestingFetches, testingFetches } from "#server/internal/adapters/testingFetches.ts";
|
|
10
|
-
import { testingDeferrals } from "#server/internal/testingDeferrals.ts";
|
|
11
13
|
import { type TestingWork, testingIdle } from "#server/internal/testingIdle.ts";
|
|
12
|
-
import {
|
|
14
|
+
import { testingInspection, testingOperation } from "#server/internal/testingOperation.ts";
|
|
13
15
|
import { testingSchedules } from "#server/internal/testingSchedules.ts";
|
|
14
|
-
import { testingSignal } from "#server/internal/testingSignal.ts";
|
|
15
|
-
import { testingSnapshot } from "#server/internal/testingSnapshot.ts";
|
|
16
16
|
import { testingWaits } from "#server/internal/testingWaits.ts";
|
|
17
17
|
import type { PgBossTestingOptions } from "#server/testing.ts";
|
|
18
|
-
|
|
18
|
+
import { queueSignal } from "./queueSignal.ts";
|
|
19
|
+
import { testingInspectionClient } from "./testingInspectionClient.ts";
|
|
20
|
+
export function testingController(settings: PgBossTestingOptions = {}) {
|
|
21
|
+
const clock = settings.clock === undefined ? undefined : new PgBossTestClock(settings.clock.currentTimeMillisUnsafe());
|
|
19
22
|
let current: EngineClient | undefined;
|
|
20
23
|
let fetches: TestingFetches | undefined;
|
|
21
24
|
let schema = "pgboss";
|
|
22
|
-
const signal =
|
|
25
|
+
const signal = queueSignal();
|
|
26
|
+
let observed: PgBossInspectionService | undefined;
|
|
23
27
|
const work: TestingWork = { batches: new Set(), queues: new Set() };
|
|
24
28
|
const controls = {
|
|
25
29
|
changed: signal.changed,
|
|
26
30
|
expirations: new Map<string, number>(),
|
|
27
31
|
expirySeconds: new Map<string, number>(),
|
|
28
32
|
failures: new Set<string>(),
|
|
29
|
-
retryPolicy:
|
|
33
|
+
retryPolicy: settings.retryPolicy ?? {},
|
|
30
34
|
work,
|
|
31
35
|
};
|
|
32
36
|
function client(): EngineClient {
|
|
@@ -35,25 +39,42 @@ export function testingController(options: PgBossTestingOptions = {}) {
|
|
|
35
39
|
}
|
|
36
40
|
return current;
|
|
37
41
|
}
|
|
42
|
+
function inspection(): PgBossInspectionService {
|
|
43
|
+
if (observed === undefined) {
|
|
44
|
+
throw new Error("Provide the testing clientFactory to a jobs Layer first");
|
|
45
|
+
}
|
|
46
|
+
return observed;
|
|
47
|
+
}
|
|
38
48
|
const clientFactory: PgBossClientFactory = (clientOptions) => {
|
|
39
49
|
if (current !== undefined) {
|
|
40
50
|
throw new Error("A testing controller owns one jobs client; create a controller per runtime");
|
|
41
51
|
}
|
|
42
52
|
schema = clientOptions.schema ?? "pgboss";
|
|
43
|
-
current = defaultClientFactory({ ...clientOptions,
|
|
53
|
+
current = defaultClientFactory({ ...clientOptions, ...(clock === undefined ? {} : { clock }) });
|
|
44
54
|
fetches = testingFetches(current.getDb(), signal.changed);
|
|
45
|
-
|
|
55
|
+
current = testingClient(current, schema, controls);
|
|
56
|
+
return testingInspectionClient(current, schema, work, signal.changed, (reads) => {
|
|
57
|
+
observed = reads;
|
|
58
|
+
});
|
|
46
59
|
};
|
|
60
|
+
const wake = Effect.sync(() => {
|
|
61
|
+
if (clock !== undefined) {
|
|
62
|
+
for (const worker of client().getWipData()) {
|
|
63
|
+
client().notifyWorker(worker.id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const idle = testingIdle(inspection, work, signal, wake);
|
|
47
68
|
return {
|
|
48
|
-
|
|
69
|
+
deferred: (queue: Pick<QueueDefinition, "name">) => Effect.suspend(() => testingInspection("deferred", inspection().deferred(queue), queue.name)),
|
|
49
70
|
...testingDueNow(client, () => schema, signal.changed),
|
|
50
|
-
...testingSchedules(client,
|
|
51
|
-
...testingWaits(
|
|
71
|
+
...testingSchedules(client, inspection),
|
|
72
|
+
...testingWaits(inspection, () => fetches, signal),
|
|
52
73
|
checkBatchWindow: (queue: Pick<QueueDefinition, "name" | "workerOptions">, millisecondsPerJob: number) =>
|
|
53
74
|
testingOperation(
|
|
54
75
|
"checkBatchWindow",
|
|
55
76
|
async () => {
|
|
56
|
-
const stored = await
|
|
77
|
+
const stored = await Effect.runPromise(inspection().queue(queue));
|
|
57
78
|
if (stored === null) {
|
|
58
79
|
throw new Error("Queue is not registered");
|
|
59
80
|
}
|
|
@@ -72,7 +93,7 @@ export function testingController(options: PgBossTestingOptions = {}) {
|
|
|
72
93
|
),
|
|
73
94
|
checkSchemaVersion: (expected: number) =>
|
|
74
95
|
testingOperation("checkSchemaVersion", async () => {
|
|
75
|
-
if ((await
|
|
96
|
+
if ((await Effect.runPromise(inspection().schemaVersion())) !== expected) {
|
|
76
97
|
throw new Error(`Expected pg-boss schema version ${expected}`);
|
|
77
98
|
}
|
|
78
99
|
}),
|
|
@@ -99,11 +120,18 @@ export function testingController(options: PgBossTestingOptions = {}) {
|
|
|
99
120
|
},
|
|
100
121
|
}),
|
|
101
122
|
failNextSend: (queue: Pick<QueueDefinition, "name">) => Effect.sync(() => controls.failures.add(queue.name)).pipe(Effect.asVoid),
|
|
102
|
-
jobs:
|
|
123
|
+
jobs: <TName extends string, TPayload extends JobPayloadSchema | typeof Schema.Null>(
|
|
124
|
+
queue: { readonly name: TName; readonly schema: TPayload },
|
|
125
|
+
readOptions?: JobReadOptions,
|
|
126
|
+
) => Effect.suspend(() => testingInspection("jobs", inspection().jobs(queue, readOptions), queue.name)),
|
|
127
|
+
refreshQueueStats: (queue?: Pick<QueueDefinition, "name">) =>
|
|
128
|
+
testingOperation("refreshQueueStats", () => client().supervise(queue?.name), queue?.name),
|
|
103
129
|
revision: signal.revision,
|
|
104
|
-
schemaVersion:
|
|
105
|
-
snapshot:
|
|
130
|
+
schemaVersion: Effect.suspend(() => testingInspection("schemaVersion", inspection().schemaVersion())),
|
|
131
|
+
snapshot: <const TQueues extends readonly SnapshotQueue[]>(queues: TQueues) =>
|
|
132
|
+
Effect.suspend(() => testingInspection("snapshot", inspection().snapshot(queues))),
|
|
106
133
|
waitForChange: signal.waitForChange,
|
|
107
|
-
waitForIdle:
|
|
134
|
+
waitForIdle: idle,
|
|
135
|
+
...testingTime(settings.clock, clock, idle),
|
|
108
136
|
};
|
|
109
137
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { QueueDefinition } from "#server/definition.ts";
|
|
2
2
|
import type { EngineClient } from "#server/engineClient.ts";
|
|
3
3
|
import { quotedSchema } from "#server/internal/storedRows.ts";
|
|
4
|
-
import { testingOperation } from "#server/internal/
|
|
4
|
+
import { testingOperation } from "#server/internal/testingOperation.ts";
|
|
5
5
|
|
|
6
6
|
export function testingDueNow(client: () => EngineClient, schema: () => string, changed: () => void) {
|
|
7
7
|
function makeDue(queue: Pick<QueueDefinition, "name">, deferredOnly: boolean) {
|
|
@@ -3,7 +3,6 @@ import type { EngineClient } from "#server/engineClient.ts";
|
|
|
3
3
|
import { PgBossTestingError } from "#server/error.ts";
|
|
4
4
|
import type { TestingEngine } from "#server/internal/engine.ts";
|
|
5
5
|
import { expireAttempt } from "./expireAttempt.ts";
|
|
6
|
-
import { observeTestingSettlements } from "./observeTestingSettlements.ts";
|
|
7
6
|
|
|
8
7
|
export function testingEngine(client: EngineClient, schema: string): Context.Service.Shape<typeof TestingEngine> {
|
|
9
8
|
return {
|
|
@@ -12,17 +11,5 @@ export function testingEngine(client: EngineClient, schema: string): Context.Ser
|
|
|
12
11
|
catch: (original) => new PgBossTestingError({ operation: "expireAttempt", original: Redacted.make(original), queue: job.name }),
|
|
13
12
|
try: () => expireAttempt(client, schema, job, cause),
|
|
14
13
|
}),
|
|
15
|
-
observeSettlements: (settled) =>
|
|
16
|
-
Effect.sync(() => {
|
|
17
|
-
const observer = observeTestingSettlements(client, settled);
|
|
18
|
-
return {
|
|
19
|
-
close: Effect.sync(() => observer.close()),
|
|
20
|
-
watch: (name: string) =>
|
|
21
|
-
Effect.try({
|
|
22
|
-
catch: (original) => new PgBossTestingError({ operation: "observeSettlements", original: Redacted.make(original), queue: name }),
|
|
23
|
-
try: () => observer.watch(name),
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
}),
|
|
27
14
|
};
|
|
28
15
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Context, Effect, Exit, Layer, Scope } from "effect";
|
|
2
|
+
import type { EngineClient } from "#server/engineClient.ts";
|
|
3
|
+
import { PgBossInspection, type PgBossInspectionService } from "#server/inspection.ts";
|
|
4
|
+
import type { TestingWork } from "#server/internal/testingIdle.ts";
|
|
5
|
+
import { PgBoss } from "#server/PgBoss.ts";
|
|
6
|
+
|
|
7
|
+
const settleBatches = Effect.fnUntraced(function* (inspection: PgBossInspectionService, work: TestingWork) {
|
|
8
|
+
for (const batch of work.batches) {
|
|
9
|
+
if (batch.settled) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const stored = yield* inspection.storedJobs({ name: batch.name });
|
|
13
|
+
batch.settled = !stored.some((job) => job.state === "active" && batch.jobs.get(job.id) === job.retryCount);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export function testingInspectionClient(
|
|
18
|
+
source: EngineClient,
|
|
19
|
+
schema: string,
|
|
20
|
+
work: TestingWork,
|
|
21
|
+
changed: () => void,
|
|
22
|
+
observe: (inspection: PgBossInspectionService) => void,
|
|
23
|
+
): EngineClient {
|
|
24
|
+
const scope = Effect.runSync(Scope.make());
|
|
25
|
+
const start = source.start.bind(source);
|
|
26
|
+
const stop = source.stop.bind(source);
|
|
27
|
+
async function startObserved() {
|
|
28
|
+
const result = await start();
|
|
29
|
+
const context = await Effect.runPromise(
|
|
30
|
+
Layer.buildWithScope(
|
|
31
|
+
PgBossInspection.layer.pipe(Layer.provide(Layer.succeed(PgBoss, { client: source, reused: false, schema, stop: undefined }))),
|
|
32
|
+
scope,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
const inspection = Context.get(context, PgBossInspection);
|
|
36
|
+
observe(inspection);
|
|
37
|
+
await Effect.runPromise(
|
|
38
|
+
Effect.gen(function* () {
|
|
39
|
+
const changes = yield* inspection.changes();
|
|
40
|
+
const from = yield* changes.revision;
|
|
41
|
+
yield* Effect.forkScoped(
|
|
42
|
+
Effect.gen(function* () {
|
|
43
|
+
let revision = from;
|
|
44
|
+
while (true) {
|
|
45
|
+
revision = yield* changes.waitForChange(revision);
|
|
46
|
+
yield* settleBatches(inspection, work);
|
|
47
|
+
changed();
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
}).pipe(Effect.provideService(Scope.Scope, scope)),
|
|
52
|
+
);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
const stopObserved: EngineClient["stop"] = async (options) => {
|
|
56
|
+
await Promise.all([Effect.runPromise(Scope.close(scope, Exit.void)), stop(options)]);
|
|
57
|
+
};
|
|
58
|
+
return new Proxy(source, {
|
|
59
|
+
get(target, property) {
|
|
60
|
+
if (property === "start") {
|
|
61
|
+
return startObserved;
|
|
62
|
+
}
|
|
63
|
+
if (property === "stop") {
|
|
64
|
+
return stopObserved;
|
|
65
|
+
}
|
|
66
|
+
const value: unknown = Reflect.get(target, property);
|
|
67
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Duration, Effect } from "effect";
|
|
2
|
+
import type { TestClock } from "effect/testing";
|
|
3
|
+
import type { PgBossTestingError } from "#server/error.ts";
|
|
4
|
+
import { testingOperation } from "#server/internal/testingOperation.ts";
|
|
5
|
+
|
|
6
|
+
export function testingTime(
|
|
7
|
+
clock: TestClock.TestClock | undefined,
|
|
8
|
+
jobs: { readonly setTime: (time: number) => Promise<void>; readonly tick: (milliseconds: number) => Promise<void> } | undefined,
|
|
9
|
+
idle: Effect.Effect<void, PgBossTestingError>,
|
|
10
|
+
) {
|
|
11
|
+
return {
|
|
12
|
+
timePasses: (duration: Duration.Input) =>
|
|
13
|
+
Effect.gen(function* () {
|
|
14
|
+
if (clock === undefined || jobs === undefined) {
|
|
15
|
+
return yield* Effect.die(new Error("Provide the story's TestClock to makePgBossTesting before passing time"));
|
|
16
|
+
}
|
|
17
|
+
yield* idle;
|
|
18
|
+
const next = clock.currentTimeMillisUnsafe() + Duration.toMillis(Duration.fromInputUnsafe(duration));
|
|
19
|
+
yield* testingOperation("timePasses", () => jobs.setTime(next));
|
|
20
|
+
yield* clock.adjust(duration);
|
|
21
|
+
yield* testingOperation("timePasses", () => jobs.tick(0));
|
|
22
|
+
yield* idle;
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
package/src/server/client.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ClientDatabase } from "#server/ClientDatabase.ts";
|
|
2
2
|
import type { ClientOptions, StopOptions } from "#server/clientOptions.ts";
|
|
3
|
+
import type { EngineSchedule } from "#server/engineClient.ts";
|
|
3
4
|
import type { EnqueueOptions, ScheduleOptions } from "#server/enqueueOptions.ts";
|
|
4
5
|
import type { Job, WorkerJob } from "#server/job.ts";
|
|
5
6
|
import type { QueueOptions } from "#server/queueOptions.ts";
|
|
@@ -62,6 +63,7 @@ export interface PgBossClient {
|
|
|
62
63
|
readonly getDb?: () => ClientDatabase;
|
|
63
64
|
readonly getQueue: (name: string) => Promise<ClientQueue | null>;
|
|
64
65
|
readonly getSchedule?: (name: string, key?: string) => Promise<{ readonly name: string; readonly key: string } | null>;
|
|
66
|
+
readonly getSchedules?: (name?: string, key?: string) => Promise<EngineSchedule[]>;
|
|
65
67
|
readonly getWipData?: (options?: { includeInternal?: boolean }) => { id: string; workId: string; name: string; count: number }[];
|
|
66
68
|
readonly notifyWorker?: (workerId: string) => void;
|
|
67
69
|
readonly off: (event: "error", listener: (error: Error) => void) => unknown;
|
|
@@ -69,6 +71,7 @@ export interface PgBossClient {
|
|
|
69
71
|
readonly on: (event: "error", listener: (error: Error) => void) => unknown;
|
|
70
72
|
readonly retry?: (name: string, id: string | string[], options?: ClientConnectionOptions) => Promise<object>;
|
|
71
73
|
readonly schedule: (name: string, cron: string, data?: object | null, options?: ScheduleOptions) => Promise<void>;
|
|
74
|
+
readonly schemaVersion?: () => Promise<number | null>;
|
|
72
75
|
readonly send: (name: string, data?: object | null, options?: EnqueueOptions) => Promise<string | null>;
|
|
73
76
|
readonly start: () => Promise<unknown>;
|
|
74
77
|
readonly stop: (options?: StopOptions) => Promise<void>;
|
package/src/server/definition.ts
CHANGED
|
@@ -32,7 +32,8 @@ export interface QueueDefinition<TName extends string = string, TPayload extends
|
|
|
32
32
|
readonly handle: <E, R>(
|
|
33
33
|
handler: (payload: Schema.Schema.Type<TPayload>, job: JobContext) => Effect.Effect<unknown, E, R>,
|
|
34
34
|
) => QueueWorker<TPayload, R>;
|
|
35
|
-
readonly
|
|
35
|
+
readonly id?: readonly string[] | undefined;
|
|
36
|
+
readonly key?: readonly string[] | undefined;
|
|
36
37
|
readonly maxDeferrals: number;
|
|
37
38
|
readonly name: TName;
|
|
38
39
|
readonly queueOptions: Omit<QueueOptions, "maxDeferrals">;
|
|
@@ -76,32 +77,37 @@ export type RegistrationRequirements<TRegistrations extends readonly JobRegistra
|
|
|
76
77
|
: never
|
|
77
78
|
: never;
|
|
78
79
|
|
|
80
|
+
export type PayloadParts<TPayload extends JobPayloadSchema> = readonly {
|
|
81
|
+
[TField in Extract<keyof TPayload["Type"], string>]: TPayload["Type"][TField] extends string | number | boolean | null ? TField : never;
|
|
82
|
+
}[Extract<keyof TPayload["Type"], string>][];
|
|
83
|
+
|
|
79
84
|
export type DefineQueueOptions<TName extends string, TPayload extends JobPayloadSchema> = {
|
|
85
|
+
readonly id?: PayloadParts<NoInfer<TPayload>>;
|
|
80
86
|
readonly label?: string;
|
|
81
87
|
readonly name: TName;
|
|
82
88
|
readonly schema: TPayload;
|
|
83
89
|
readonly subject?: readonly SubjectColumn<NoInfer<TPayload>>[];
|
|
84
90
|
readonly worker?: WorkerOptions;
|
|
85
91
|
} & (
|
|
86
|
-
| { readonly key:
|
|
92
|
+
| { readonly key: PayloadParts<NoInfer<TPayload>>; readonly queue: QueueOptions & { readonly policy: "singleton" } }
|
|
87
93
|
| { readonly key?: never; readonly queue?: QueueOptions & { readonly policy?: Exclude<QueuePolicy, "singleton"> } }
|
|
88
94
|
);
|
|
89
95
|
|
|
90
|
-
export function defineQueue<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
): QueueDefinition<TName, TPayload>;
|
|
96
|
+
export function defineQueue<
|
|
97
|
+
const TName extends string,
|
|
98
|
+
const TPayload extends JobPayloadSchema,
|
|
99
|
+
const TId extends readonly string[] | undefined = undefined,
|
|
100
|
+
const TKey extends readonly string[] | undefined = undefined,
|
|
101
|
+
>(
|
|
102
|
+
options: DefineQueueOptions<TName, TPayload> & { readonly id?: TId; readonly key?: TKey },
|
|
103
|
+
): QueueDefinition<TName, TPayload> & { readonly id?: TId; readonly key?: TKey };
|
|
99
104
|
export function defineQueue<const TName extends string, const TPayload extends JobPayloadSchema>(
|
|
100
105
|
options: DefineQueueOptions<TName, TPayload>,
|
|
101
106
|
): QueueDefinition<TName, TPayload> {
|
|
102
107
|
const definition: QueueDefinition<TName, TPayload> = {
|
|
103
108
|
_tag: "QueueDefinition",
|
|
104
|
-
|
|
109
|
+
id: options.id === undefined ? undefined : Object.freeze([...options.id]),
|
|
110
|
+
...(options.key === undefined ? {} : { key: Object.freeze([...options.key]) }),
|
|
105
111
|
...(options.label === undefined ? {} : { label: options.label }),
|
|
106
112
|
...(options.subject === undefined ? {} : { subject: Object.freeze(options.subject.map((column) => Object.freeze({ ...column }))) }),
|
|
107
113
|
handle: (handler) =>
|
|
@@ -16,9 +16,7 @@ export interface EngineClient extends Required<PgBossClient> {
|
|
|
16
16
|
readonly emit: (event: "error", error: Error) => boolean;
|
|
17
17
|
readonly findJobs: <T = unknown>(name: string, options?: JobReadOptions) => Promise<Job<T>[]>;
|
|
18
18
|
readonly getDb: () => ClientDatabase;
|
|
19
|
-
readonly getJobById: <T = unknown>(name: string, id: string) => Promise<Job<T> | null>;
|
|
20
19
|
readonly getSchedules: (name?: string, key?: string) => Promise<EngineSchedule[]>;
|
|
21
|
-
readonly getSpy: (name: string) => { clear: () => void; waitForJob: (selector: () => boolean, state: "completed" | "failed") => Promise<unknown> };
|
|
22
20
|
readonly schemaVersion: () => Promise<number | null>;
|
|
23
21
|
readonly supervise: (name?: string) => Promise<void>;
|
|
24
22
|
}
|