@shivaedev/effect-pg-boss 0.10.1 → 0.11.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 +9 -0
- package/README.md +24 -4
- package/dist/server/definition.d.ts +15 -3
- package/dist/server/definition.d.ts.map +1 -1
- package/dist/server/definition.js +21 -3
- package/dist/server/definition.js.map +1 -1
- package/dist/server/internal/batchHandler.d.ts.map +1 -1
- package/dist/server/internal/batchHandler.js +13 -7
- package/dist/server/internal/batchHandler.js.map +1 -1
- package/dist/server/internal/deferrableAttempt.d.ts +7 -0
- package/dist/server/internal/deferrableAttempt.d.ts.map +1 -0
- package/dist/server/internal/deferrableAttempt.js +28 -0
- package/dist/server/internal/deferrableAttempt.js.map +1 -0
- package/dist/server/internal/deferral.d.ts +15 -0
- package/dist/server/internal/deferral.d.ts.map +1 -0
- package/dist/server/internal/deferral.js +14 -0
- package/dist/server/internal/deferral.js.map +1 -0
- package/dist/server/internal/deferralStorePlan.d.ts +2 -0
- package/dist/server/internal/deferralStorePlan.d.ts.map +1 -0
- package/dist/server/internal/deferralStorePlan.js +34 -0
- package/dist/server/internal/deferralStorePlan.js.map +1 -0
- package/dist/server/internal/expireAttempt.d.ts +1 -1
- package/dist/server/internal/expireAttempt.d.ts.map +1 -1
- package/dist/server/internal/expireAttempt.js +4 -2
- package/dist/server/internal/expireAttempt.js.map +1 -1
- package/dist/server/internal/lifecycle.d.ts.map +1 -1
- package/dist/server/internal/lifecycle.js +4 -0
- package/dist/server/internal/lifecycle.js.map +1 -1
- package/dist/server/internal/register.d.ts +1 -1
- package/dist/server/internal/register.d.ts.map +1 -1
- package/dist/server/internal/register.js +10 -10
- package/dist/server/internal/register.js.map +1 -1
- package/dist/server/internal/registerWorker.d.ts +2 -2
- package/dist/server/internal/registerWorker.d.ts.map +1 -1
- package/dist/server/internal/registerWorker.js +7 -2
- package/dist/server/internal/registerWorker.js.map +1 -1
- package/dist/server/internal/settleDeferral.d.ts +7 -0
- package/dist/server/internal/settleDeferral.d.ts.map +1 -0
- package/dist/server/internal/settleDeferral.js +47 -0
- package/dist/server/internal/settleDeferral.js.map +1 -0
- package/dist/server/internal/testingClient.d.ts.map +1 -1
- package/dist/server/internal/testingClient.js +4 -1
- package/dist/server/internal/testingClient.js.map +1 -1
- package/dist/server/internal/testingDeferrals.d.ts +11 -0
- package/dist/server/internal/testingDeferrals.d.ts.map +1 -0
- package/dist/server/internal/testingDeferrals.js +28 -0
- package/dist/server/internal/testingDeferrals.js.map +1 -0
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +8 -1
- package/dist/server/schema.js.map +1 -1
- package/dist/server/service.js +1 -1
- package/dist/server/service.js.map +1 -1
- package/dist/server/testing.d.ts +5 -0
- package/dist/server/testing.d.ts.map +1 -1
- package/dist/server/testing.js +4 -0
- package/dist/server/testing.js.map +1 -1
- package/package.json +3 -3
- package/src/server/definition.ts +29 -5
- package/src/server/internal/batchHandler.ts +21 -6
- package/src/server/internal/deferrableAttempt.ts +41 -0
- package/src/server/internal/deferral.ts +19 -0
- package/src/server/internal/deferralStorePlan.ts +34 -0
- package/src/server/internal/expireAttempt.ts +4 -2
- package/src/server/internal/lifecycle.ts +4 -0
- package/src/server/internal/register.ts +15 -15
- package/src/server/internal/registerWorker.ts +39 -26
- package/src/server/internal/settleDeferral.ts +53 -0
- package/src/server/internal/testingClient.ts +4 -1
- package/src/server/internal/testingDeferrals.ts +43 -0
- package/src/server/schema.ts +8 -1
- package/src/server/service.ts +1 -1
- package/src/server/testing.ts +4 -0
|
@@ -2,6 +2,7 @@ import { Effect } from "effect";
|
|
|
2
2
|
import type { ConstructorOptions, StopOptions } from "pg-boss";
|
|
3
3
|
import { defaultClientFactory, type PgBossClient, type PgBossClientFactory } from "#client.ts";
|
|
4
4
|
import { toPgBossError } from "#error.ts";
|
|
5
|
+
import { deferralStorePlan } from "#internal/deferralStorePlan.ts";
|
|
5
6
|
import { acquireCachedClient, releaseCachedClient } from "./clientCache.ts";
|
|
6
7
|
import { logClientError, observeClientErrors } from "./clientErrors.ts";
|
|
7
8
|
|
|
@@ -22,6 +23,9 @@ async function startClient(options: AcquireClientOptions, report: (error: Error)
|
|
|
22
23
|
observeClientErrors(client, report);
|
|
23
24
|
try {
|
|
24
25
|
await client.start();
|
|
26
|
+
if (options.constructor.migrate !== false && client.getDb !== undefined) {
|
|
27
|
+
await client.getDb().executeSql(deferralStorePlan(options.constructor.schema ?? "pgboss"));
|
|
28
|
+
}
|
|
25
29
|
return client;
|
|
26
30
|
} catch (error) {
|
|
27
31
|
try {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { PgBossError, PgBossPayloadError, PgBossQueueOptionsError, toPgBossError } from "#error.ts";
|
|
13
13
|
import { ensureQueue } from "#internal/ensureQueue.ts";
|
|
14
14
|
import { registerWorker } from "#internal/registerWorker.ts";
|
|
15
|
-
import { payloadOf
|
|
15
|
+
import { payloadOf } from "#internal/tracing.ts";
|
|
16
16
|
|
|
17
17
|
function isQueueWorker(registration: JobRegistration): registration is QueueWorker {
|
|
18
18
|
return registration._tag === "QueueWorker";
|
|
@@ -65,7 +65,7 @@ function workerContext(context: unknown): unknown {
|
|
|
65
65
|
return context;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function runQueueWorker(worker: QueueWorker, job: Job<unknown
|
|
68
|
+
function runQueueWorker(worker: QueueWorker, job: Job<unknown>) {
|
|
69
69
|
return Schema.decodeUnknownEffect(worker.queue.schema)(payloadOf(job.data)).pipe(
|
|
70
70
|
Effect.mapError(
|
|
71
71
|
(error) =>
|
|
@@ -76,8 +76,6 @@ function runQueueWorker(worker: QueueWorker, job: Job<unknown>, context: Context
|
|
|
76
76
|
}),
|
|
77
77
|
),
|
|
78
78
|
Effect.flatMap((payload) => worker.handler(payload, jobContext(job))),
|
|
79
|
-
(effect) => traceJob(effect, job, worker.queue.name),
|
|
80
|
-
Effect.provide(context),
|
|
81
79
|
);
|
|
82
80
|
}
|
|
83
81
|
|
|
@@ -86,24 +84,25 @@ async function registerQueue(
|
|
|
86
84
|
registration: QueueWorker | QueueDefinition,
|
|
87
85
|
context: Context.Context<unknown>,
|
|
88
86
|
scope: Scope.Scope,
|
|
87
|
+
schema: string,
|
|
89
88
|
): Promise<void> {
|
|
90
89
|
const queue = registration._tag === "QueueWorker" ? registration.queue : registration;
|
|
91
90
|
const name = queue.name;
|
|
92
91
|
if (registration._tag === "QueueDefinition") {
|
|
93
92
|
return;
|
|
94
93
|
}
|
|
95
|
-
await registerWorker(client, name, queue.workerOptions, (job) => runQueueWorker(registration, job,
|
|
94
|
+
await registerWorker(client, name, queue.workerOptions, (job) => runQueueWorker(registration, job), scope, schema, queue.maxDeferrals, context);
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
async function registerSchedule(
|
|
97
|
+
async function registerSchedule(
|
|
98
|
+
client: PgBossClient,
|
|
99
|
+
worker: ScheduledWorker,
|
|
100
|
+
context: Context.Context<unknown>,
|
|
101
|
+
scope: Scope.Scope,
|
|
102
|
+
schema: string,
|
|
103
|
+
): Promise<void> {
|
|
99
104
|
const { schedule } = worker;
|
|
100
|
-
await registerWorker(
|
|
101
|
-
client,
|
|
102
|
-
schedule.name,
|
|
103
|
-
schedule.workerOptions,
|
|
104
|
-
(job) => Effect.provide(traceJob(worker.effect, job, schedule.name), context),
|
|
105
|
-
scope,
|
|
106
|
-
);
|
|
105
|
+
await registerWorker(client, schedule.name, schedule.workerOptions, () => worker.effect, scope, schema, schedule.maxDeferrals, context);
|
|
107
106
|
await client.schedule(schedule.name, schedule.cron, null, schedule.scheduleOptions);
|
|
108
107
|
}
|
|
109
108
|
|
|
@@ -120,6 +119,7 @@ export const registerJobs = <R>(
|
|
|
120
119
|
registrations: readonly JobRegistration[],
|
|
121
120
|
context: Context.Context<R>,
|
|
122
121
|
replaceWorkers: boolean,
|
|
122
|
+
schema = "pgboss",
|
|
123
123
|
): Effect.Effect<void, PgBossError | PgBossQueueOptionsError, Scope.Scope> =>
|
|
124
124
|
Effect.flatMap(Effect.scope, (scope) =>
|
|
125
125
|
Effect.tryPromise({
|
|
@@ -130,9 +130,9 @@ export const registerJobs = <R>(
|
|
|
130
130
|
const provided = workerContext(context);
|
|
131
131
|
for (const registration of registrations) {
|
|
132
132
|
if (isQueueWorker(registration) || isQueueDefinition(registration)) {
|
|
133
|
-
await registerQueue(client, registration, provided, scope);
|
|
133
|
+
await registerQueue(client, registration, provided, scope, schema);
|
|
134
134
|
} else if (isScheduledWorker(registration)) {
|
|
135
|
-
await registerSchedule(client, registration, provided, scope);
|
|
135
|
+
await registerSchedule(client, registration, provided, scope, schema);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
},
|
|
@@ -1,42 +1,55 @@
|
|
|
1
|
-
import { Clock, Effect, type Scope } from "effect";
|
|
1
|
+
import { Clock, type Context, Effect, type Scope } from "effect";
|
|
2
2
|
import type { Job, WorkOptions } from "pg-boss";
|
|
3
3
|
import type { PgBossClient } from "#client.ts";
|
|
4
4
|
import { batchHandler } from "#internal/batchHandler.ts";
|
|
5
|
+
import { deferrableAttempt } from "#internal/deferrableAttempt.ts";
|
|
6
|
+
import { traceJob } from "#internal/tracing.ts";
|
|
5
7
|
|
|
6
8
|
export function registerWorker(
|
|
7
9
|
client: PgBossClient,
|
|
8
10
|
name: string,
|
|
9
11
|
options: WorkOptions,
|
|
10
|
-
run: (job: Job<unknown>) => Effect.Effect<unknown, unknown>,
|
|
12
|
+
run: (job: Job<unknown>) => Effect.Effect<unknown, unknown, unknown>,
|
|
11
13
|
scope: Scope.Scope,
|
|
14
|
+
schema: string,
|
|
15
|
+
maxDeferrals: number,
|
|
16
|
+
context: Context.Context<unknown>,
|
|
12
17
|
): Promise<string> {
|
|
13
18
|
const registration = client.work(
|
|
14
19
|
name,
|
|
15
20
|
{ ...options, includeMetadata: true },
|
|
16
|
-
batchHandler(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
batchHandler(
|
|
22
|
+
client,
|
|
23
|
+
name,
|
|
24
|
+
(job) => {
|
|
25
|
+
const attempt = traceJob(deferrableAttempt(run(job), client, schema, job, maxDeferrals), job, name);
|
|
26
|
+
return Effect.provide(attempt, context);
|
|
27
|
+
},
|
|
28
|
+
async (attempt) => {
|
|
29
|
+
if (client.findJobs === undefined || client.notifyWorker === undefined) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const job = (await client.findJobs(name, { id: attempt.id }))[0];
|
|
33
|
+
if (job?.state !== "retry") {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const workId = await registration;
|
|
37
|
+
await Effect.runPromise(
|
|
38
|
+
Effect.gen(function* () {
|
|
39
|
+
const now = yield* Clock.currentTimeMillis;
|
|
40
|
+
// PostgreSQL timestamps retain sub-millisecond precision that Date drops.
|
|
41
|
+
yield* Effect.sleep(Math.max(0, job.startAfter.getTime() + 1 - now));
|
|
42
|
+
const workerIds = client
|
|
43
|
+
.getWipData?.()
|
|
44
|
+
.filter((worker) => worker.workId === workId)
|
|
45
|
+
.map((worker) => worker.id) ?? [workId];
|
|
46
|
+
for (const workerId of workerIds) {
|
|
47
|
+
client.notifyWorker?.(workerId);
|
|
48
|
+
}
|
|
49
|
+
}).pipe(Effect.forkIn(scope)),
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
),
|
|
40
53
|
);
|
|
41
54
|
return registration;
|
|
42
55
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import type { Job } from "pg-boss";
|
|
3
|
+
import type { PgBossClient } from "#client.ts";
|
|
4
|
+
import { STATELY_DEFERRAL_POLICY } from "./deferral.ts";
|
|
5
|
+
|
|
6
|
+
const Settlement = Schema.Struct({ claimed: Schema.Boolean, deferred: Schema.Boolean });
|
|
7
|
+
|
|
8
|
+
export async function settleDeferral(client: PgBossClient, schema: string, job: Job<unknown>, at: string, reason: string, maxDeferrals: number) {
|
|
9
|
+
if (client.getDb === undefined) {
|
|
10
|
+
throw new TypeError("This pg-boss client does not support getDb for RetryAt");
|
|
11
|
+
}
|
|
12
|
+
const quoted = `"${schema.replaceAll('"', '""')}"`;
|
|
13
|
+
// Native failure settlement deletes and reinserts jobs, so a cascading job foreign key would erase the counter.
|
|
14
|
+
const { rows } = await client.getDb().executeSql(
|
|
15
|
+
`WITH claimed AS MATERIALIZED (
|
|
16
|
+
SELECT name, id, created_on,
|
|
17
|
+
GREATEST(keep_until, $4::timestamptz + (keep_until - start_after)) AS keep_until
|
|
18
|
+
FROM ${quoted}.job
|
|
19
|
+
WHERE name = $1 AND id = $2 AND state = 'active' AND retry_count = $3
|
|
20
|
+
AND date_trunc('milliseconds', started_on) = $6::timestamptz
|
|
21
|
+
FOR UPDATE
|
|
22
|
+
), counted AS (
|
|
23
|
+
INSERT INTO ${quoted}.effect_job_deferral AS previous (name, id, created_on, count, keep_until)
|
|
24
|
+
SELECT name, id, created_on, 1, keep_until FROM claimed
|
|
25
|
+
ON CONFLICT (name, id) DO UPDATE SET
|
|
26
|
+
count = CASE WHEN previous.created_on = EXCLUDED.created_on THEN previous.count + 1 ELSE 1 END,
|
|
27
|
+
created_on = EXCLUDED.created_on, keep_until = EXCLUDED.keep_until
|
|
28
|
+
WHERE previous.created_on <> EXCLUDED.created_on OR previous.count < $7
|
|
29
|
+
RETURNING name, id, keep_until
|
|
30
|
+
), deferred AS (
|
|
31
|
+
UPDATE ${quoted}.job AS job SET state = 'retry', start_after = $4::timestamptz,
|
|
32
|
+
policy = CASE WHEN job.policy = 'stately' THEN '${STATELY_DEFERRAL_POLICY}' ELSE job.policy END,
|
|
33
|
+
keep_until = counted.keep_until, started_on = NULL, heartbeat_on = NULL,
|
|
34
|
+
output = jsonb_build_object('jobDeferral', jsonb_build_object('at', $4::text, 'reason', $5::text))
|
|
35
|
+
FROM counted WHERE job.name = counted.name AND job.id = counted.id
|
|
36
|
+
RETURNING job.id
|
|
37
|
+
), cleaned AS (
|
|
38
|
+
DELETE FROM ${quoted}.effect_job_deferral WHERE (name, id) IN (
|
|
39
|
+
SELECT stale.name, stale.id FROM ${quoted}.effect_job_deferral AS stale
|
|
40
|
+
WHERE EXISTS (SELECT 1 FROM counted) AND stale.keep_until < ${quoted}.job_now()
|
|
41
|
+
AND (stale.name, stale.id) <> ($1, $2)
|
|
42
|
+
AND NOT EXISTS (
|
|
43
|
+
SELECT 1 FROM ${quoted}.job AS live
|
|
44
|
+
WHERE live.name = stale.name AND live.id = stale.id AND live.created_on = stale.created_on
|
|
45
|
+
)
|
|
46
|
+
ORDER BY stale.name, stale.id LIMIT 100 FOR UPDATE OF stale SKIP LOCKED
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
SELECT EXISTS (SELECT 1 FROM claimed) AS claimed, EXISTS (SELECT 1 FROM deferred) AS deferred`,
|
|
50
|
+
[job.name, job.id, job.retryCount, at, reason, Reflect.get(job, "startedOn"), maxDeferrals],
|
|
51
|
+
);
|
|
52
|
+
return Schema.decodeUnknownSync(Settlement)(rows[0]);
|
|
53
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Job, PgBoss, SendOptions, UpdateOptions, UpdateRequest, UpdateResponse } from "pg-boss";
|
|
2
2
|
import type { PgBossClient } from "#client.ts";
|
|
3
|
+
import { deferralObservers } from "#internal/deferral.ts";
|
|
3
4
|
import { expireAttempt } from "#internal/expireAttempt.ts";
|
|
4
5
|
import { observeTestingSettlements } from "#internal/observeTestingSettlements.ts";
|
|
5
6
|
import { testingExpirations } from "#internal/testingExpirations.ts";
|
|
@@ -102,7 +103,7 @@ export function testingClient(client: PgBoss, schema: string, controls: TestingC
|
|
|
102
103
|
});
|
|
103
104
|
},
|
|
104
105
|
};
|
|
105
|
-
|
|
106
|
+
const wrapped = new Proxy(client, {
|
|
106
107
|
get(target, property) {
|
|
107
108
|
if (Object.hasOwn(overrides, property)) {
|
|
108
109
|
const override: unknown = Reflect.get(overrides, property);
|
|
@@ -112,4 +113,6 @@ export function testingClient(client: PgBoss, schema: string, controls: TestingC
|
|
|
112
113
|
return typeof value === "function" ? value.bind(target) : value;
|
|
113
114
|
},
|
|
114
115
|
});
|
|
116
|
+
deferralObservers.set(wrapped, controls.changed);
|
|
117
|
+
return wrapped;
|
|
115
118
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DateTime } from "effect";
|
|
2
|
+
import type { PgBoss } from "pg-boss";
|
|
3
|
+
import type { QueueDefinition } from "#definition.ts";
|
|
4
|
+
import { deferralOf } from "#internal/deferral.ts";
|
|
5
|
+
import { testingOperation } from "#internal/testingReadout.ts";
|
|
6
|
+
|
|
7
|
+
export function testingDeferrals(client: () => PgBoss, schema: () => string, changed: () => void) {
|
|
8
|
+
return {
|
|
9
|
+
deferred: (queue: QueueDefinition) =>
|
|
10
|
+
testingOperation(
|
|
11
|
+
"deferred",
|
|
12
|
+
async () => {
|
|
13
|
+
const jobs = await client().findJobs(queue.name);
|
|
14
|
+
return jobs.flatMap((job) => {
|
|
15
|
+
const deferral = job.state === "retry" && job.startedOn === null ? deferralOf(job) : undefined;
|
|
16
|
+
return deferral === undefined ? [] : [{ at: DateTime.makeUnsafe(job.startAfter), reason: deferral.reason }];
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
queue.name,
|
|
20
|
+
),
|
|
21
|
+
runDeferredNow: (queue: QueueDefinition) =>
|
|
22
|
+
testingOperation(
|
|
23
|
+
"runDeferredNow",
|
|
24
|
+
async () => {
|
|
25
|
+
const quotedSchema = `"${schema().replaceAll('"', '""')}"`;
|
|
26
|
+
await client()
|
|
27
|
+
.getDb()
|
|
28
|
+
.executeSql(
|
|
29
|
+
`UPDATE ${quotedSchema}.job SET start_after = ${quotedSchema}.job_now()
|
|
30
|
+
WHERE name = $1 AND state = 'retry' AND output ? 'jobDeferral' AND started_on IS NULL`,
|
|
31
|
+
[queue.name],
|
|
32
|
+
);
|
|
33
|
+
changed();
|
|
34
|
+
for (const worker of client()
|
|
35
|
+
.getWipData()
|
|
36
|
+
.filter((candidate) => candidate.name === queue.name)) {
|
|
37
|
+
client().notifyWorker(worker.id);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
queue.name,
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
}
|
package/src/server/schema.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Connection } from "effect/sql/SqlConnection";
|
|
|
4
4
|
import { getConstructionPlans, getMigrationPlans, type MigrationPartition, PgBoss } from "pg-boss";
|
|
5
5
|
import pgBossPackage from "pg-boss/package.json" with { type: "json" };
|
|
6
6
|
import { type PgBossError, toPgBossError } from "#error.ts";
|
|
7
|
+
import { deferralStorePlan } from "#internal/deferralStorePlan.ts";
|
|
7
8
|
import { runSchemaPlan } from "#internal/runSchemaPlan.ts";
|
|
8
9
|
import { resumeSchemaIndexes } from "#internal/schemaIndexBuilds.ts";
|
|
9
10
|
|
|
@@ -51,7 +52,13 @@ export function installPgBossSchema(options: PgBossSchemaOptions = {}): Effect.E
|
|
|
51
52
|
const sql = yield* SqlClient.SqlClient;
|
|
52
53
|
const connection = yield* sql.reserve;
|
|
53
54
|
const run = Effect.runPromiseWith(yield* Effect.context<never>());
|
|
54
|
-
yield* Effect.tryPromise({
|
|
55
|
+
yield* Effect.tryPromise({
|
|
56
|
+
catch: (error) => error,
|
|
57
|
+
try: async () => {
|
|
58
|
+
await installSchema(connection, options, run);
|
|
59
|
+
await run(connection.executeRaw(deferralStorePlan(options.schema ?? "pgboss"), []));
|
|
60
|
+
},
|
|
61
|
+
});
|
|
55
62
|
}).pipe(Effect.mapError((error) => toPgBossError("install", error))),
|
|
56
63
|
).pipe(Effect.uninterruptible);
|
|
57
64
|
}
|
package/src/server/service.ts
CHANGED
|
@@ -72,7 +72,7 @@ export const makePgBoss = <const TIdentifier extends string>(identifier: TIdenti
|
|
|
72
72
|
void Effect.runPromise(Effect.exit(Effect.provide(reportError(error), context)));
|
|
73
73
|
};
|
|
74
74
|
const unsubscribe = subscribeClientErrors(acquired.client, errorListener);
|
|
75
|
-
const registration = registerJobs(acquired.client, jobs, context, acquired.reused);
|
|
75
|
+
const registration = registerJobs(acquired.client, jobs, context, acquired.reused, constructorOptions.schema);
|
|
76
76
|
return yield* registration.pipe(
|
|
77
77
|
Effect.as({ acquired, service, unsubscribe }),
|
|
78
78
|
Effect.onError(() => releaseClient(acquired, stop).pipe(Effect.ensuring(Effect.sync(unsubscribe)))),
|
package/src/server/testing.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { PgBossClientFactory } from "#client.ts";
|
|
|
4
4
|
import type { QueueDefinition } from "#definition.ts";
|
|
5
5
|
import { PgBossTestingError } from "#error.ts";
|
|
6
6
|
import { testingClient } from "#internal/testingClient.ts";
|
|
7
|
+
import { testingDeferrals } from "#internal/testingDeferrals.ts";
|
|
7
8
|
import { testingOperation, testingReadout } from "#internal/testingReadout.ts";
|
|
8
9
|
import { testingSignal } from "#internal/testingSignal.ts";
|
|
9
10
|
|
|
@@ -15,6 +16,7 @@ export interface PgBossTestingOptions {
|
|
|
15
16
|
|
|
16
17
|
export function makePgBossTesting(options: PgBossTestingOptions = {}) {
|
|
17
18
|
let current: PgBoss | undefined;
|
|
19
|
+
let schema = "pgboss";
|
|
18
20
|
const signal = testingSignal();
|
|
19
21
|
const controls = {
|
|
20
22
|
changed: signal.changed,
|
|
@@ -32,10 +34,12 @@ export function makePgBossTesting(options: PgBossTestingOptions = {}) {
|
|
|
32
34
|
if (current !== undefined) {
|
|
33
35
|
throw new Error("A testing controller owns one jobs client; create a controller per runtime");
|
|
34
36
|
}
|
|
37
|
+
schema = clientOptions.schema ?? "pgboss";
|
|
35
38
|
current = new PgBoss({ ...clientOptions, "__test__enableSpies": true });
|
|
36
39
|
return testingClient(current, clientOptions.schema ?? "pgboss", controls);
|
|
37
40
|
};
|
|
38
41
|
return {
|
|
42
|
+
...testingDeferrals(client, () => schema, signal.changed),
|
|
39
43
|
checkBatchWindow: (queue: QueueDefinition, millisecondsPerJob: number) =>
|
|
40
44
|
testingOperation(
|
|
41
45
|
"checkBatchWindow",
|