@shivaedev/jobs 0.31.0 → 0.32.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 +24 -0
- package/README.md +57 -26
- 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/testingClient.d.ts +1 -2
- package/dist/server/adapters/testingClient.d.ts.map +1 -1
- package/dist/server/adapters/testingClient.js +23 -45
- package/dist/server/adapters/testingClient.js.map +1 -1
- package/dist/server/adapters/testingController.d.ts +57 -12
- package/dist/server/adapters/testingController.d.ts.map +1 -1
- package/dist/server/adapters/testingController.js +28 -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/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/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 +3 -2
- package/dist/server/internal/testingIdle.d.ts.map +1 -1
- package/dist/server/internal/testingIdle.js +5 -25
- 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 +5 -6
- 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 +60 -25
- package/dist/server/testing.d.ts.map +1 -1
- package/dist/server/testing.js.map +1 -1
- package/package.json +6 -6
- 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/testingClient.ts +9 -30
- package/src/server/adapters/testingController.ts +37 -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/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/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 +7 -30
- package/src/server/internal/testingOperation.ts +18 -0
- package/src/server/internal/testingSchedules.ts +28 -16
- package/src/server/internal/testingWaits.ts +10 -11
- package/src/server/schema.ts +5 -0
- package/src/server/service.ts +5 -2
- package/src/server/testing.ts +1 -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
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
import { Duration, Effect, Redacted } from "effect";
|
|
1
|
+
import { Duration, Effect, Redacted, type Schema } from "effect";
|
|
2
2
|
import { defaultClientFactory } from "#server/adapters/defaultClientFactory.ts";
|
|
3
3
|
import { testingClient } from "#server/adapters/testingClient.ts";
|
|
4
4
|
import { testingDueNow } from "#server/adapters/testingDueNow.ts";
|
|
5
|
-
import type { PgBossClientFactory } from "#server/client.ts";
|
|
6
|
-
import type { QueueDefinition } from "#server/definition.ts";
|
|
5
|
+
import type { JobReadOptions, PgBossClientFactory } from "#server/client.ts";
|
|
6
|
+
import type { JobPayloadSchema, QueueDefinition } from "#server/definition.ts";
|
|
7
7
|
import type { EngineClient } from "#server/engineClient.ts";
|
|
8
8
|
import { PgBossTestingError } from "#server/error.ts";
|
|
9
|
+
import type { PgBossInspectionService, SnapshotQueue } from "#server/inspection.ts";
|
|
9
10
|
import { type TestingFetches, testingFetches } from "#server/internal/adapters/testingFetches.ts";
|
|
10
|
-
import { testingDeferrals } from "#server/internal/testingDeferrals.ts";
|
|
11
11
|
import { type TestingWork, testingIdle } from "#server/internal/testingIdle.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { testingInspection, testingOperation } from "#server/internal/testingOperation.ts";
|
|
13
13
|
import { testingSchedules } from "#server/internal/testingSchedules.ts";
|
|
14
|
-
import { testingSignal } from "#server/internal/testingSignal.ts";
|
|
15
|
-
import { testingSnapshot } from "#server/internal/testingSnapshot.ts";
|
|
16
14
|
import { testingWaits } from "#server/internal/testingWaits.ts";
|
|
17
15
|
import type { PgBossTestingOptions } from "#server/testing.ts";
|
|
18
|
-
|
|
16
|
+
import { queueSignal } from "./queueSignal.ts";
|
|
17
|
+
import { testingInspectionClient } from "./testingInspectionClient.ts";
|
|
18
|
+
export function testingController(settings: PgBossTestingOptions = {}) {
|
|
19
19
|
let current: EngineClient | undefined;
|
|
20
20
|
let fetches: TestingFetches | undefined;
|
|
21
21
|
let schema = "pgboss";
|
|
22
|
-
const signal =
|
|
22
|
+
const signal = queueSignal();
|
|
23
|
+
let observed: PgBossInspectionService | undefined;
|
|
23
24
|
const work: TestingWork = { batches: new Set(), queues: new Set() };
|
|
24
25
|
const controls = {
|
|
25
26
|
changed: signal.changed,
|
|
26
27
|
expirations: new Map<string, number>(),
|
|
27
28
|
expirySeconds: new Map<string, number>(),
|
|
28
29
|
failures: new Set<string>(),
|
|
29
|
-
retryPolicy:
|
|
30
|
+
retryPolicy: settings.retryPolicy ?? {},
|
|
30
31
|
work,
|
|
31
32
|
};
|
|
32
33
|
function client(): EngineClient {
|
|
@@ -35,25 +36,34 @@ export function testingController(options: PgBossTestingOptions = {}) {
|
|
|
35
36
|
}
|
|
36
37
|
return current;
|
|
37
38
|
}
|
|
39
|
+
function inspection(): PgBossInspectionService {
|
|
40
|
+
if (observed === undefined) {
|
|
41
|
+
throw new Error("Provide the testing clientFactory to a jobs Layer first");
|
|
42
|
+
}
|
|
43
|
+
return observed;
|
|
44
|
+
}
|
|
38
45
|
const clientFactory: PgBossClientFactory = (clientOptions) => {
|
|
39
46
|
if (current !== undefined) {
|
|
40
47
|
throw new Error("A testing controller owns one jobs client; create a controller per runtime");
|
|
41
48
|
}
|
|
42
49
|
schema = clientOptions.schema ?? "pgboss";
|
|
43
|
-
current = defaultClientFactory(
|
|
50
|
+
current = defaultClientFactory(clientOptions);
|
|
44
51
|
fetches = testingFetches(current.getDb(), signal.changed);
|
|
45
|
-
|
|
52
|
+
current = testingClient(current, schema, controls);
|
|
53
|
+
return testingInspectionClient(current, schema, work, signal.changed, (reads) => {
|
|
54
|
+
observed = reads;
|
|
55
|
+
});
|
|
46
56
|
};
|
|
47
57
|
return {
|
|
48
|
-
|
|
58
|
+
deferred: (queue: Pick<QueueDefinition, "name">) => Effect.suspend(() => testingInspection("deferred", inspection().deferred(queue), queue.name)),
|
|
49
59
|
...testingDueNow(client, () => schema, signal.changed),
|
|
50
|
-
...testingSchedules(client,
|
|
51
|
-
...testingWaits(
|
|
60
|
+
...testingSchedules(client, inspection),
|
|
61
|
+
...testingWaits(inspection, () => fetches, signal),
|
|
52
62
|
checkBatchWindow: (queue: Pick<QueueDefinition, "name" | "workerOptions">, millisecondsPerJob: number) =>
|
|
53
63
|
testingOperation(
|
|
54
64
|
"checkBatchWindow",
|
|
55
65
|
async () => {
|
|
56
|
-
const stored = await
|
|
66
|
+
const stored = await Effect.runPromise(inspection().queue(queue));
|
|
57
67
|
if (stored === null) {
|
|
58
68
|
throw new Error("Queue is not registered");
|
|
59
69
|
}
|
|
@@ -72,7 +82,7 @@ export function testingController(options: PgBossTestingOptions = {}) {
|
|
|
72
82
|
),
|
|
73
83
|
checkSchemaVersion: (expected: number) =>
|
|
74
84
|
testingOperation("checkSchemaVersion", async () => {
|
|
75
|
-
if ((await
|
|
85
|
+
if ((await Effect.runPromise(inspection().schemaVersion())) !== expected) {
|
|
76
86
|
throw new Error(`Expected pg-boss schema version ${expected}`);
|
|
77
87
|
}
|
|
78
88
|
}),
|
|
@@ -99,11 +109,17 @@ export function testingController(options: PgBossTestingOptions = {}) {
|
|
|
99
109
|
},
|
|
100
110
|
}),
|
|
101
111
|
failNextSend: (queue: Pick<QueueDefinition, "name">) => Effect.sync(() => controls.failures.add(queue.name)).pipe(Effect.asVoid),
|
|
102
|
-
jobs:
|
|
112
|
+
jobs: <TName extends string, TPayload extends JobPayloadSchema | typeof Schema.Null>(
|
|
113
|
+
queue: { readonly name: TName; readonly schema: TPayload },
|
|
114
|
+
readOptions?: JobReadOptions,
|
|
115
|
+
) => Effect.suspend(() => testingInspection("jobs", inspection().jobs(queue, readOptions), queue.name)),
|
|
116
|
+
refreshQueueStats: (queue?: Pick<QueueDefinition, "name">) =>
|
|
117
|
+
testingOperation("refreshQueueStats", () => client().supervise(queue?.name), queue?.name),
|
|
103
118
|
revision: signal.revision,
|
|
104
|
-
schemaVersion:
|
|
105
|
-
snapshot:
|
|
119
|
+
schemaVersion: Effect.suspend(() => testingInspection("schemaVersion", inspection().schemaVersion())),
|
|
120
|
+
snapshot: <const TQueues extends readonly SnapshotQueue[]>(queues: TQueues) =>
|
|
121
|
+
Effect.suspend(() => testingInspection("snapshot", inspection().snapshot(queues))),
|
|
106
122
|
waitForChange: signal.waitForChange,
|
|
107
|
-
waitForIdle: testingIdle(
|
|
123
|
+
waitForIdle: testingIdle(inspection, work, signal),
|
|
108
124
|
};
|
|
109
125
|
}
|
|
@@ -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
|
+
}
|
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
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientDatabase } from "#server/ClientDatabase.ts";
|
|
2
|
+
import type { QueueDefinition } from "#server/definition.ts";
|
|
2
3
|
import type { RetryOptions } from "#server/queueOptions.ts";
|
|
3
4
|
|
|
4
5
|
export interface EnqueueOptions extends RetryOptions {
|
|
@@ -13,6 +14,10 @@ export interface EnqueueOptions extends RetryOptions {
|
|
|
13
14
|
readonly startAfter?: number | string | Date;
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
export type QueueEnqueueOptions<TQueue extends QueueDefinition> = Omit<EnqueueOptions, "id" | "singletonKey"> &
|
|
18
|
+
(TQueue["id"] extends undefined ? Pick<EnqueueOptions, "id"> : { readonly id?: never }) &
|
|
19
|
+
(TQueue["key"] extends undefined ? Pick<EnqueueOptions, "singletonKey"> : { readonly singletonKey?: never });
|
|
20
|
+
|
|
16
21
|
export interface ScheduleOptions extends EnqueueOptions {
|
|
17
22
|
readonly key?: string;
|
|
18
23
|
readonly missed?: "skip" | "once";
|
package/src/server/error.ts
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type DateTime, Effect } from "effect";
|
|
2
|
+
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
3
|
+
import { genericMethod } from "@shivaedev/service/genericMethod.ts";
|
|
4
|
+
import { inspection } from "#server/adapters/inspection.ts";
|
|
5
|
+
import type { inspectionChanges } from "#server/adapters/inspectionChanges.ts";
|
|
6
|
+
import type { inspectionReads } from "#server/adapters/inspectionReads.ts";
|
|
7
|
+
import type { JobPayloadSchema, QueueDefinition } from "#server/definition.ts";
|
|
8
|
+
import { PgBoss } from "#server/PgBoss.ts";
|
|
9
|
+
import type { PgBossService } from "#server/service.ts";
|
|
10
|
+
|
|
11
|
+
export type SnapshotQueue = Pick<QueueDefinition<string, JobPayloadSchema>, "name" | "schema">;
|
|
12
|
+
|
|
13
|
+
export interface UnfinishedJob<TQueue extends SnapshotQueue> {
|
|
14
|
+
readonly data: TQueue["schema"]["Type"];
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly queue: TQueue["name"];
|
|
17
|
+
readonly startAfter: DateTime.Utc;
|
|
18
|
+
readonly state: "active" | "created" | "deferred" | "failed" | "retry";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const PgBossInspection = defineService({
|
|
22
|
+
id: "@shivaedev/jobs/PgBossInspection",
|
|
23
|
+
initialize: Effect.gen(function* () {
|
|
24
|
+
const acquired = yield* PgBoss;
|
|
25
|
+
return yield* inspection(acquired.client, acquired.schema);
|
|
26
|
+
}),
|
|
27
|
+
methods: (service) => ({
|
|
28
|
+
busy: service.busy,
|
|
29
|
+
changes: service.changes,
|
|
30
|
+
counts: service.counts,
|
|
31
|
+
deadLetteredJobs: genericMethod(service.deadLetteredJobs),
|
|
32
|
+
deferred: service.deferred,
|
|
33
|
+
failedJobs: genericMethod(service.failedJobs),
|
|
34
|
+
getJob: genericMethod(service.getJob),
|
|
35
|
+
health: service.health,
|
|
36
|
+
jobs: genericMethod(service.jobs),
|
|
37
|
+
queue: service.queue,
|
|
38
|
+
schedules: service.schedules,
|
|
39
|
+
schemaVersion: service.schemaVersion,
|
|
40
|
+
snapshot: genericMethod(service.snapshot),
|
|
41
|
+
storedJobs: service.storedJobs,
|
|
42
|
+
}),
|
|
43
|
+
requires: [PgBoss] as const,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export type PgBossInspectionService = ReturnType<typeof inspectionReads> &
|
|
47
|
+
Pick<PgBossService, "counts" | "deadLetteredJobs" | "failedJobs" | "getJob"> & {
|
|
48
|
+
readonly health: (queues: readonly { readonly name: string }[]) => ReturnType<PgBossService["health"]>;
|
|
49
|
+
readonly changes: (queue?: { readonly name: string }) => ReturnType<typeof inspectionChanges>;
|
|
50
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientDatabase } from "#server/ClientDatabase.ts";
|
|
2
2
|
|
|
3
3
|
// pg-boss offers no hook for a worker fetch that finds nothing, so the controller recognizes its claim statement on the client's database.
|
|
4
4
|
const claim = /UPDATE\s+\S+\s+j\s+SET\s+state\s*=\s*'active'/u;
|
|
@@ -9,7 +9,7 @@ export interface TestingFetches {
|
|
|
9
9
|
readonly passedSince: (queue: string, begun: number) => number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function testingFetches(db:
|
|
12
|
+
export function testingFetches(db: ClientDatabase, changed: () => void): TestingFetches {
|
|
13
13
|
const begun = new Map<string, number>();
|
|
14
14
|
const passed = new Map<string, number[]>();
|
|
15
15
|
const executeSql = db.executeSql.bind(db);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
-
import type { PgBossClient } from "#server/client.ts";
|
|
3
2
|
import type { WorkerJob } from "#server/job.ts";
|
|
4
3
|
|
|
5
4
|
export const deferredAttempt = Symbol("deferredAttempt");
|
|
6
|
-
export const deferralObservers = new WeakMap<PgBossClient, () => void>();
|
|
7
5
|
export const STATELY_DEFERRAL_POLICY = "effect-stately-deferred";
|
|
8
6
|
|
|
9
7
|
export const Deferral = Schema.Struct({
|
|
@@ -27,15 +27,9 @@ export class JobEngine extends Context.Service<
|
|
|
27
27
|
}
|
|
28
28
|
>()("@shivaedev/jobs/JobEngine") {}
|
|
29
29
|
|
|
30
|
-
export interface TestingSettlements {
|
|
31
|
-
readonly close: Effect.Effect<void>;
|
|
32
|
-
readonly watch: (name: string) => Effect.Effect<void, PgBossTestingError>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
30
|
export class TestingEngine extends Context.Service<
|
|
36
31
|
TestingEngine,
|
|
37
32
|
{
|
|
38
33
|
readonly expireAttempt: (job: JobAttempt, cause?: "expiry" | "heartbeat") => Effect.Effect<void, PgBossTestingError>;
|
|
39
|
-
readonly observeSettlements: (settled: (name: string) => void) => Effect.Effect<TestingSettlements>;
|
|
40
34
|
}
|
|
41
35
|
>()("@shivaedev/jobs/TestingEngine") {}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
|
+
import { payloadJobId, payloadKey } from "#server/adapters/payloadIdentity.ts";
|
|
2
3
|
import { transactionDb } from "#server/adapters/transactionDb.ts";
|
|
3
4
|
import type { PgBossClient } from "#server/client.ts";
|
|
5
|
+
import type { QueueDefinition } from "#server/definition.ts";
|
|
6
|
+
import type { EnqueueOptions } from "#server/enqueueOptions.ts";
|
|
4
7
|
import { PgBossPayloadError, toPgBossError } from "#server/error.ts";
|
|
5
8
|
import { capturePayload } from "#server/internal/tracing.ts";
|
|
6
9
|
import type { PgBossService } from "#server/service.ts";
|
|
7
10
|
|
|
11
|
+
function identityOptions(queue: Pick<QueueDefinition, "id" | "key" | "name">, payload: object, options?: EnqueueOptions) {
|
|
12
|
+
return queue.id === undefined && queue.key === undefined
|
|
13
|
+
? options
|
|
14
|
+
: {
|
|
15
|
+
...options,
|
|
16
|
+
...(queue.id === undefined ? {} : { id: payloadJobId(queue.name, queue.id, payload) }),
|
|
17
|
+
...(queue.key === undefined ? {} : { singletonKey: payloadKey(queue.key, payload) }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export function enqueueFor(client: PgBossClient): PgBossService["enqueue"] {
|
|
9
22
|
return (queue, payload, options) =>
|
|
10
23
|
Effect.gen(function* () {
|
|
@@ -20,7 +33,7 @@ export function enqueueFor(client: PgBossClient): PgBossService["enqueue"] {
|
|
|
20
33
|
}
|
|
21
34
|
const data = yield* capturePayload(encoded);
|
|
22
35
|
const db = yield* transactionDb;
|
|
23
|
-
const sendOptions = queue
|
|
36
|
+
const sendOptions = identityOptions(queue, payload, options);
|
|
24
37
|
const send = Effect.tryPromise({
|
|
25
38
|
catch: (error) => toPgBossError("enqueue", error, queue.name),
|
|
26
39
|
try: () => client.send(queue.name, data, db === undefined ? sendOptions : { ...sendOptions, db }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { inspectionPlan } from "#server/adapters/inspectionPlan.ts";
|
|
1
2
|
import { deadLetterResolutionPlan } from "#server/internal/deadLetterResolutionPlan.ts";
|
|
2
3
|
import { deferralStorePlan } from "#server/internal/deferralStorePlan.ts";
|
|
3
4
|
|
|
4
5
|
export function jobStorePlans(schema: string): readonly string[] {
|
|
5
|
-
return [deferralStorePlan(schema), deadLetterResolutionPlan(schema)];
|
|
6
|
+
return [deferralStorePlan(schema), deadLetterResolutionPlan(schema), inspectionPlan(schema)];
|
|
6
7
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
+
import { inspection } from "#server/adapters/inspection.ts";
|
|
2
3
|
import type { PgBossClient } from "#server/client.ts";
|
|
3
|
-
import { countsFor } from "#server/internal/countsFor.ts";
|
|
4
4
|
import { JobEngine } from "#server/internal/engine.ts";
|
|
5
5
|
import { enqueueFor } from "#server/internal/enqueueFor.ts";
|
|
6
|
-
import { healthFor } from "#server/internal/healthFor.ts";
|
|
7
6
|
import { jobControl } from "#server/internal/jobControl.ts";
|
|
8
|
-
import { jobListings } from "#server/internal/jobListings.ts";
|
|
9
7
|
import type { PgBossService } from "#server/service.ts";
|
|
10
8
|
|
|
11
9
|
export const makeService = Effect.fnUntraced(function* (
|
|
@@ -14,13 +12,16 @@ export const makeService = Effect.fnUntraced(function* (
|
|
|
14
12
|
schema = "pgboss",
|
|
15
13
|
): Effect.fn.Return<PgBossService, never, JobEngine> {
|
|
16
14
|
const engine = yield* JobEngine;
|
|
15
|
+
const reads = yield* inspection(client, schema);
|
|
17
16
|
return {
|
|
18
|
-
counts: (queue) =>
|
|
17
|
+
counts: (queue) => reads.counts(queue),
|
|
18
|
+
deadLetteredJobs: reads.deadLetteredJobs,
|
|
19
19
|
deleteQueue: engine.deleteQueue,
|
|
20
|
-
...(yield* jobListings),
|
|
21
20
|
enqueue: enqueueFor(client),
|
|
21
|
+
failedJobs: reads.failedJobs,
|
|
22
|
+
inspection: () => Effect.succeed(reads),
|
|
22
23
|
...(yield* jobControl),
|
|
23
|
-
health: (queues) =>
|
|
24
|
+
health: (queues) => reads.health(queues ?? names.map((name) => ({ name }))),
|
|
24
25
|
refreshQueueStats: engine.refreshQueueStats,
|
|
25
26
|
unschedule: engine.unschedule,
|
|
26
27
|
};
|
|
@@ -7,7 +7,6 @@ import type { JobPayloadSchema, JobRegistration, QueueDefinition, QueueWorker, S
|
|
|
7
7
|
import { PgBossQueueOptionsError, toPgBossError } from "#server/error.ts";
|
|
8
8
|
import { ensureQueue } from "#server/internal/ensureQueue.ts";
|
|
9
9
|
import { runQueueWorker } from "#server/internal/runQueueWorker.ts";
|
|
10
|
-
import { registerTestingQueue } from "#server/internal/testingRegistrations.ts";
|
|
11
10
|
|
|
12
11
|
function registrationDefinition(registration: JobRegistration): QueueDefinition | ScheduleDefinition {
|
|
13
12
|
if (registration._tag === "QueueDefinition") {
|
|
@@ -107,9 +106,6 @@ export const registerJobs = Effect.fnUntraced(function* <R>(
|
|
|
107
106
|
const scope = yield* Scope.fork(yield* Effect.scope, "parallel");
|
|
108
107
|
yield* Effect.try({ catch: (error) => toPgBossError("register", error), try: () => registrationNames(registrations) });
|
|
109
108
|
yield* prepareQueues(client, registrations, replaceWorkers);
|
|
110
|
-
for (const registration of registrations) {
|
|
111
|
-
registerTestingQueue(client, registrationName(registration));
|
|
112
|
-
}
|
|
113
109
|
if (!startWorkers) {
|
|
114
110
|
return;
|
|
115
111
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Clock, Effect
|
|
2
|
-
import type { EngineClient } from "#server/engineClient.ts";
|
|
1
|
+
import { Clock, Effect } from "effect";
|
|
3
2
|
import type { PgBossTestingError } from "#server/error.ts";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import type { PgBossInspectionService } from "#server/inspection.ts";
|
|
4
|
+
import { testingInspection } from "#server/internal/testingOperation.ts";
|
|
6
5
|
|
|
7
6
|
export interface TestingBatch {
|
|
7
|
+
readonly jobs: ReadonlyMap<string, number>;
|
|
8
8
|
readonly name: string;
|
|
9
9
|
settled: boolean;
|
|
10
10
|
}
|
|
@@ -14,30 +14,12 @@ export interface TestingWork {
|
|
|
14
14
|
readonly queues: Set<string>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const Busy = Schema.Struct({ busy: Schema.Boolean });
|
|
18
|
-
|
|
19
17
|
export function testingIdle(
|
|
20
|
-
|
|
21
|
-
schema: () => string,
|
|
18
|
+
inspection: () => PgBossInspectionService,
|
|
22
19
|
work: TestingWork,
|
|
23
20
|
signal: { readonly revision: Effect.Effect<number>; readonly waitForChange: (after: number) => Effect.Effect<number> },
|
|
24
21
|
): Effect.Effect<void, PgBossTestingError> {
|
|
25
|
-
const busy =
|
|
26
|
-
const quoted = quotedSchema(schema());
|
|
27
|
-
const { rows } = await client()
|
|
28
|
-
.getDb()
|
|
29
|
-
.executeSql(
|
|
30
|
-
`/* jobs testing idle */ SELECT EXISTS (
|
|
31
|
-
SELECT 1 FROM ${quoted}.job
|
|
32
|
-
WHERE name = ANY($1::text[]) AND (
|
|
33
|
-
(state < 'active' AND NOT blocked AND start_after <= ${quoted}.job_now())
|
|
34
|
-
OR state = 'active'
|
|
35
|
-
)
|
|
36
|
-
) AS busy`,
|
|
37
|
-
[[...work.queues]],
|
|
38
|
-
);
|
|
39
|
-
return (await Schema.decodeUnknownPromise(Busy)(rows[0])).busy;
|
|
40
|
-
});
|
|
22
|
+
const busy = Effect.suspend(() => testingInspection("waitForIdle", inspection().busy([...work.queues].map((name) => ({ name })))));
|
|
41
23
|
return Effect.gen(function* () {
|
|
42
24
|
while (true) {
|
|
43
25
|
const revision = yield* signal.revision;
|
|
@@ -46,12 +28,7 @@ export function testingIdle(
|
|
|
46
28
|
if (work.queues.size === 0 || !(unsettled || (yield* busy))) {
|
|
47
29
|
return;
|
|
48
30
|
}
|
|
49
|
-
|
|
50
|
-
yield* signal.waitForChange(revision);
|
|
51
|
-
} else {
|
|
52
|
-
// Other clients have no local callback; PostgreSQL remains the source of idle state.
|
|
53
|
-
yield* Effect.raceFirst(signal.waitForChange(revision), Effect.sleep("50 millis"));
|
|
54
|
-
}
|
|
31
|
+
yield* signal.waitForChange(revision);
|
|
55
32
|
}
|
|
56
33
|
}).pipe(Effect.provideService(Clock.Clock, Clock.Clock.defaultValue()));
|
|
57
34
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Effect, Redacted } from "effect";
|
|
2
|
+
import { PgBossError, PgBossTestingError } from "#server/error.ts";
|
|
3
|
+
|
|
4
|
+
export function testingOperation<A>(operation: string, run: () => Promise<A>, queue?: string) {
|
|
5
|
+
return Effect.tryPromise({
|
|
6
|
+
catch: (original) => new PgBossTestingError({ operation, original: Redacted.make(original), queue }),
|
|
7
|
+
try: run,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function testingInspection<A, E, R>(operation: string, effect: Effect.Effect<A, E | PgBossError, R>, queue?: string) {
|
|
12
|
+
return effect.pipe(
|
|
13
|
+
Effect.catchIf(
|
|
14
|
+
(error): error is PgBossError => error instanceof PgBossError,
|
|
15
|
+
(error) => Effect.fail(new PgBossTestingError({ operation, original: error.original, queue: queue ?? error.queue })),
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
}
|