@shivaedev/jobs 0.24.0 → 0.26.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 +49 -0
- package/README.md +49 -24
- package/dist/server/adapters/batchClockOffset.d.ts +4 -0
- package/dist/server/adapters/batchClockOffset.d.ts.map +1 -0
- package/dist/server/adapters/batchClockOffset.js +16 -0
- package/dist/server/adapters/batchClockOffset.js.map +1 -0
- package/dist/server/adapters/clientErrorReporter.d.ts +3 -0
- package/dist/server/adapters/clientErrorReporter.d.ts.map +1 -0
- package/dist/server/adapters/clientErrorReporter.js +8 -0
- package/dist/server/adapters/clientErrorReporter.js.map +1 -0
- package/dist/server/adapters/installSchema.d.ts +5 -0
- package/dist/server/adapters/installSchema.d.ts.map +1 -0
- package/dist/server/adapters/installSchema.js +57 -0
- package/dist/server/adapters/installSchema.js.map +1 -0
- package/dist/server/adapters/registerWorker.d.ts +5 -0
- package/dist/server/adapters/registerWorker.d.ts.map +1 -0
- package/dist/server/{internal → adapters}/registerWorker.js +5 -4
- package/dist/server/adapters/registerWorker.js.map +1 -0
- package/dist/server/{internal → adapters}/runSchemaPlan.d.ts.map +1 -1
- package/dist/server/{internal → adapters}/runSchemaPlan.js.map +1 -1
- package/dist/server/{internal → adapters}/schemaIndexBuilds.d.ts.map +1 -1
- package/dist/server/{internal → adapters}/schemaIndexBuilds.js.map +1 -1
- package/dist/server/{internal → adapters}/testingExpirations.d.ts.map +1 -1
- package/dist/server/{internal → adapters}/testingExpirations.js.map +1 -1
- package/dist/server/{internal → adapters}/transactionDb.d.ts.map +1 -1
- package/dist/server/{internal → adapters}/transactionDb.js.map +1 -1
- package/dist/server/adapters/workerDiagnostics.d.ts +3 -0
- package/dist/server/adapters/workerDiagnostics.d.ts.map +1 -0
- package/dist/server/adapters/workerDiagnostics.js +24 -0
- package/dist/server/adapters/workerDiagnostics.js.map +1 -0
- package/dist/server/client.d.ts +0 -1
- package/dist/server/client.d.ts.map +1 -1
- package/dist/server/client.js +1 -2
- package/dist/server/client.js.map +1 -1
- package/dist/server/defaultClientFactory.d.ts +3 -0
- package/dist/server/defaultClientFactory.d.ts.map +1 -0
- package/dist/server/{internal/nativeClient.js → defaultClientFactory.js} +2 -2
- package/dist/server/defaultClientFactory.js.map +1 -0
- package/dist/server/defineSchedule.d.ts +3 -0
- package/dist/server/defineSchedule.d.ts.map +1 -0
- package/dist/server/{internal/scheduleDefinition.js → defineSchedule.js} +2 -2
- package/dist/server/defineSchedule.js.map +1 -0
- package/dist/server/definition.d.ts +1 -2
- package/dist/server/definition.d.ts.map +1 -1
- package/dist/server/definition.js +2 -7
- package/dist/server/definition.js.map +1 -1
- package/dist/server/error.d.ts +17 -7
- package/dist/server/error.d.ts.map +1 -1
- package/dist/server/error.js +11 -3
- package/dist/server/error.js.map +1 -1
- package/dist/server/health.d.ts +1 -0
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/internal/adapters/testingFetches.d.ts +7 -0
- package/dist/server/internal/adapters/testingFetches.d.ts.map +1 -0
- package/dist/server/internal/adapters/testingFetches.js +30 -0
- package/dist/server/internal/adapters/testingFetches.js.map +1 -0
- package/dist/server/internal/batchHandler.d.ts +1 -2
- package/dist/server/internal/batchHandler.d.ts.map +1 -1
- package/dist/server/internal/batchHandler.js +18 -6
- package/dist/server/internal/batchHandler.js.map +1 -1
- package/dist/server/internal/deadLetterResolutionPlan.d.ts +2 -0
- package/dist/server/internal/deadLetterResolutionPlan.d.ts.map +1 -0
- package/dist/server/internal/deadLetterResolutionPlan.js +28 -0
- package/dist/server/internal/deadLetterResolutionPlan.js.map +1 -0
- package/dist/server/internal/enqueueFor.d.ts.map +1 -1
- package/dist/server/internal/enqueueFor.js +2 -2
- package/dist/server/internal/enqueueFor.js.map +1 -1
- package/dist/server/internal/jobControl.d.ts.map +1 -1
- package/dist/server/internal/jobControl.js +2 -1
- package/dist/server/internal/jobControl.js.map +1 -1
- package/dist/server/internal/jobStorePlans.d.ts +2 -0
- package/dist/server/internal/jobStorePlans.d.ts.map +1 -0
- package/dist/server/internal/jobStorePlans.js +6 -0
- package/dist/server/internal/jobStorePlans.js.map +1 -0
- package/dist/server/internal/lifecycle.d.ts +1 -1
- package/dist/server/internal/lifecycle.d.ts.map +1 -1
- package/dist/server/internal/lifecycle.js +5 -3
- package/dist/server/internal/lifecycle.js.map +1 -1
- package/dist/server/internal/listJobs.d.ts.map +1 -1
- package/dist/server/internal/listJobs.js +3 -2
- package/dist/server/internal/listJobs.js.map +1 -1
- package/dist/server/internal/register.d.ts +1 -2
- package/dist/server/internal/register.d.ts.map +1 -1
- package/dist/server/internal/register.js +8 -8
- package/dist/server/internal/register.js.map +1 -1
- package/dist/server/internal/registerReleasedJobs.d.ts +1 -2
- package/dist/server/internal/registerReleasedJobs.d.ts.map +1 -1
- package/dist/server/internal/registerReleasedJobs.js +3 -3
- package/dist/server/internal/registerReleasedJobs.js.map +1 -1
- package/dist/server/internal/runQueueWorker.d.ts +1 -1
- package/dist/server/internal/runQueueWorker.d.ts.map +1 -1
- package/dist/server/internal/runQueueWorker.js +4 -3
- package/dist/server/internal/runQueueWorker.js.map +1 -1
- package/dist/server/internal/storedRows.js +1 -1
- package/dist/server/internal/storedRows.js.map +1 -1
- package/dist/server/internal/testingClient.d.ts.map +1 -1
- package/dist/server/internal/testingClient.js +1 -1
- package/dist/server/internal/testingClient.js.map +1 -1
- package/dist/server/internal/testingWaits.d.ts +16 -0
- package/dist/server/internal/testingWaits.d.ts.map +1 -0
- package/dist/server/internal/testingWaits.js +56 -0
- package/dist/server/internal/testingWaits.js.map +1 -0
- package/dist/server/internal/waitForBatchExpiry.d.ts +1 -1
- package/dist/server/internal/waitForBatchExpiry.d.ts.map +1 -1
- package/dist/server/internal/waitForBatchExpiry.js +2 -4
- package/dist/server/internal/waitForBatchExpiry.js.map +1 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +11 -53
- package/dist/server/schema.js.map +1 -1
- package/dist/server/service.d.ts +3 -3
- package/dist/server/service.d.ts.map +1 -1
- package/dist/server/service.js +6 -10
- 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 +7 -2
- package/dist/server/testing.js.map +1 -1
- package/package.json +4 -4
- package/src/server/adapters/batchClockOffset.ts +17 -0
- package/src/server/adapters/clientErrorReporter.ts +8 -0
- package/src/server/adapters/installSchema.ts +68 -0
- package/src/server/{internal → adapters}/registerWorker.ts +8 -9
- package/src/server/adapters/workerDiagnostics.ts +28 -0
- package/src/server/client.ts +0 -3
- package/src/server/{internal/nativeClient.ts → defaultClientFactory.ts} +1 -1
- package/src/server/{internal/scheduleDefinition.ts → defineSchedule.ts} +1 -1
- package/src/server/definition.ts +2 -8
- package/src/server/error.ts +16 -3
- package/src/server/health.ts +1 -0
- package/src/server/internal/adapters/testingFetches.ts +36 -0
- package/src/server/internal/batchHandler.ts +20 -12
- package/src/server/internal/deadLetterResolutionPlan.ts +28 -0
- package/src/server/internal/enqueueFor.ts +2 -2
- package/src/server/internal/jobControl.ts +2 -1
- package/src/server/internal/jobStorePlans.ts +6 -0
- package/src/server/internal/lifecycle.ts +6 -3
- package/src/server/internal/listJobs.ts +11 -3
- package/src/server/internal/register.ts +7 -22
- package/src/server/internal/registerReleasedJobs.ts +2 -6
- package/src/server/internal/runQueueWorker.ts +5 -3
- package/src/server/internal/storedRows.ts +1 -1
- package/src/server/internal/testingClient.ts +1 -1
- package/src/server/internal/testingWaits.ts +98 -0
- package/src/server/internal/waitForBatchExpiry.ts +7 -4
- package/src/server/schema.ts +14 -65
- package/src/server/service.ts +12 -21
- package/src/server/testing.ts +11 -2
- package/dist/server/internal/nativeClient.d.ts +0 -3
- package/dist/server/internal/nativeClient.d.ts.map +0 -1
- package/dist/server/internal/nativeClient.js.map +0 -1
- package/dist/server/internal/registerWorker.d.ts +0 -6
- package/dist/server/internal/registerWorker.d.ts.map +0 -1
- package/dist/server/internal/registerWorker.js.map +0 -1
- package/dist/server/internal/scheduleDefinition.d.ts +0 -3
- package/dist/server/internal/scheduleDefinition.d.ts.map +0 -1
- package/dist/server/internal/scheduleDefinition.js.map +0 -1
- /package/dist/server/{internal → adapters}/runSchemaPlan.d.ts +0 -0
- /package/dist/server/{internal → adapters}/runSchemaPlan.js +0 -0
- /package/dist/server/{internal → adapters}/schemaIndexBuilds.d.ts +0 -0
- /package/dist/server/{internal → adapters}/schemaIndexBuilds.js +0 -0
- /package/dist/server/{internal → adapters}/testingExpirations.d.ts +0 -0
- /package/dist/server/{internal → adapters}/testingExpirations.js +0 -0
- /package/dist/server/{internal → adapters}/transactionDb.d.ts +0 -0
- /package/dist/server/{internal → adapters}/transactionDb.js +0 -0
- /package/src/server/{internal → adapters}/runSchemaPlan.ts +0 -0
- /package/src/server/{internal → adapters}/schemaIndexBuilds.ts +0 -0
- /package/src/server/{internal → adapters}/testingExpirations.ts +0 -0
- /package/src/server/{internal → adapters}/transactionDb.ts +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
|
+
import { transactionDb } from "#adapters/transactionDb.ts";
|
|
2
3
|
import type { PgBossClient } from "#client.ts";
|
|
3
4
|
import { PgBossPayloadError, toPgBossError } from "#error.ts";
|
|
4
5
|
import { capturePayload } from "#internal/tracing.ts";
|
|
5
|
-
import { transactionDb } from "#internal/transactionDb.ts";
|
|
6
6
|
import type { PgBossService } from "#service.ts";
|
|
7
7
|
|
|
8
8
|
export function enqueueFor(client: PgBossClient): PgBossService["enqueue"] {
|
|
9
9
|
return (queue, payload, options) =>
|
|
10
10
|
Effect.gen(function* () {
|
|
11
|
-
const encoded = yield* Schema.encodeUnknownEffect(queue.schema)(payload).pipe(
|
|
11
|
+
const encoded = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(queue.schema))(payload).pipe(
|
|
12
12
|
Effect.mapError((error) => new PgBossPayloadError({ direction: "encode", original: Redacted.make(error), queue: queue.name })),
|
|
13
13
|
);
|
|
14
14
|
if (typeof encoded !== "object" || encoded === null) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Option } from "effect";
|
|
2
|
+
import { redact } from "@shivaedev/errors/redact.ts";
|
|
2
3
|
import type { PgBossClient } from "#client.ts";
|
|
3
4
|
import { toPgBossError } from "#error.ts";
|
|
4
5
|
import { decodeStoredData } from "#internal/storedRows.ts";
|
|
@@ -18,7 +19,7 @@ export function getJobFor(client: PgBossClient): PgBossService["getJob"] {
|
|
|
18
19
|
Effect.flatMap((job) =>
|
|
19
20
|
job === undefined
|
|
20
21
|
? Effect.succeed(Option.none())
|
|
21
|
-
: decodeStoredData(queue, job.data).pipe(Effect.map((data) => Option.some({ ...job, data }))),
|
|
22
|
+
: decodeStoredData(queue, job.data).pipe(Effect.map((data) => Option.some({ ...job, data: redact(data) }))),
|
|
22
23
|
),
|
|
23
24
|
);
|
|
24
25
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { deadLetterResolutionPlan } from "#internal/deadLetterResolutionPlan.ts";
|
|
2
|
+
import { deferralStorePlan } from "#internal/deferralStorePlan.ts";
|
|
3
|
+
|
|
4
|
+
export function jobStorePlans(schema: string): readonly string[] {
|
|
5
|
+
return [deferralStorePlan(schema), deadLetterResolutionPlan(schema)];
|
|
6
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { ConstructorOptions, StopOptions } from "pg-boss";
|
|
3
|
-
import {
|
|
3
|
+
import type { PgBossClient, PgBossClientFactory } from "#client.ts";
|
|
4
|
+
import { defaultClientFactory } from "#defaultClientFactory.ts";
|
|
4
5
|
import { PgBossClientOptionsError, type PgBossError, toPgBossError } from "#error.ts";
|
|
5
|
-
import {
|
|
6
|
+
import { jobStorePlans } from "#internal/jobStorePlans.ts";
|
|
6
7
|
import { acquireCachedClient, releaseCachedClient } from "./clientCache.ts";
|
|
7
8
|
import { observeClientErrors } from "./clientErrors.ts";
|
|
8
9
|
|
|
@@ -24,7 +25,9 @@ async function startClient(options: AcquireClientOptions, report: (error: Error)
|
|
|
24
25
|
try {
|
|
25
26
|
await client.start();
|
|
26
27
|
if (options.constructor.migrate !== false && client.getDb !== undefined) {
|
|
27
|
-
|
|
28
|
+
for (const plan of jobStorePlans(options.constructor.schema ?? "pgboss")) {
|
|
29
|
+
await client.getDb().executeSql(plan);
|
|
30
|
+
}
|
|
28
31
|
}
|
|
29
32
|
return client;
|
|
30
33
|
} catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
|
+
import { redact } from "@shivaedev/errors/redact.ts";
|
|
2
3
|
import type { PgBossClient } from "#client.ts";
|
|
3
4
|
import type { JobPayloadSchema } from "#definition.ts";
|
|
4
5
|
import { type PgBossError, PgBossPayloadError, toPgBossError } from "#error.ts";
|
|
@@ -50,7 +51,10 @@ export function failedJobsFor(client: PgBossClient, schema: string): PgBossServi
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export function deadLetteredJobsFor(client: PgBossClient, schema: string): PgBossService["deadLetteredJobs"] {
|
|
53
|
-
return (queue, options) =>
|
|
54
|
+
return (queue, options) =>
|
|
55
|
+
listJobs(client, schema, "deadLetteredJobs", queue, options).pipe(
|
|
56
|
+
Effect.map(({ jobs, next }) => ({ jobs: jobs.map((job) => ({ ...job, sourceQueue: queue.name })), next })),
|
|
57
|
+
);
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
const listJobs = Effect.fnUntraced(function* <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
|
|
@@ -59,7 +63,11 @@ const listJobs = Effect.fnUntraced(function* <const TName extends string, const
|
|
|
59
63
|
listing: Listing,
|
|
60
64
|
queue: { readonly name: TName; readonly schema: TPayload },
|
|
61
65
|
options: JobPageOptions,
|
|
62
|
-
): Effect.fn.Return<
|
|
66
|
+
): Effect.fn.Return<
|
|
67
|
+
JobPage<Omit<DeadLetteredJob<unknown>, "sourceQueue">>,
|
|
68
|
+
PgBossError | PgBossPayloadError | Schema.SchemaError,
|
|
69
|
+
TPayload["DecodingServices"]
|
|
70
|
+
> {
|
|
63
71
|
const { cursor: after, limit } = yield* Schema.decodeUnknownEffect(PageOptions)(options);
|
|
64
72
|
const rows = yield* Effect.tryPromise({
|
|
65
73
|
catch: (error) => toPgBossError(listing, error, queue.name),
|
|
@@ -102,7 +110,7 @@ const listJobs = Effect.fnUntraced(function* <const TName extends string, const
|
|
|
102
110
|
const next = decodedRows.length > limit && last !== undefined ? Option.some(Schema.encodeSync(Cursor)([last.cursorAt, last.id])) : Option.none();
|
|
103
111
|
return yield* Effect.forEach(page, ({ cursorAt: _, data, ...row }) =>
|
|
104
112
|
decodeStoredData(queue, data).pipe(
|
|
105
|
-
Effect.map((decoded) => ({ ...row, attempt: Option.isSome(row.startedOn) ? row.retryCount + 1 : 0, data: decoded })),
|
|
113
|
+
Effect.map((decoded) => ({ ...row, attempt: Option.isSome(row.startedOn) ? row.retryCount + 1 : 0, data: redact(decoded) })),
|
|
106
114
|
),
|
|
107
115
|
).pipe(Effect.map((jobs) => ({ jobs, next })));
|
|
108
116
|
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { type Context, Effect, type Scope } from "effect";
|
|
2
|
+
import { registerWorker } from "#adapters/registerWorker.ts";
|
|
2
3
|
import type { PgBossClient } from "#client.ts";
|
|
3
4
|
import type { JobRegistration, QueueDefinition, QueueWorker, ScheduleDefinition, ScheduledWorker } from "#definition.ts";
|
|
4
5
|
import { PgBossQueueOptionsError, toPgBossError } from "#error.ts";
|
|
5
|
-
import type { PgBossRunner } from "#internal/batchHandler.ts";
|
|
6
6
|
import { ensureQueue } from "#internal/ensureQueue.ts";
|
|
7
|
-
import { registerWorker } from "#internal/registerWorker.ts";
|
|
8
7
|
import { runQueueWorker } from "#internal/runQueueWorker.ts";
|
|
9
8
|
|
|
10
9
|
function isQueueWorker(registration: JobRegistration): registration is QueueWorker {
|
|
@@ -62,7 +61,6 @@ const registerQueue = Effect.fnUntraced(function* (
|
|
|
62
61
|
context: Context.Context<unknown>,
|
|
63
62
|
scope: Scope.Scope,
|
|
64
63
|
schema: string,
|
|
65
|
-
execute: PgBossRunner,
|
|
66
64
|
graceful: boolean,
|
|
67
65
|
) {
|
|
68
66
|
const queue = registration._tag === "QueueWorker" ? registration.queue : registration;
|
|
@@ -74,14 +72,12 @@ const registerQueue = Effect.fnUntraced(function* (
|
|
|
74
72
|
client,
|
|
75
73
|
name,
|
|
76
74
|
queue.workerOptions,
|
|
77
|
-
(job) => runQueueWorker(registration, job),
|
|
75
|
+
(job, deadline) => runQueueWorker(registration, job, deadline),
|
|
78
76
|
scope,
|
|
79
77
|
schema,
|
|
80
78
|
queue.maxDeferrals,
|
|
81
|
-
context,
|
|
82
|
-
execute,
|
|
83
79
|
graceful,
|
|
84
|
-
);
|
|
80
|
+
).pipe(Effect.provide(context));
|
|
85
81
|
});
|
|
86
82
|
|
|
87
83
|
const registerSchedule = Effect.fnUntraced(function* (
|
|
@@ -90,21 +86,11 @@ const registerSchedule = Effect.fnUntraced(function* (
|
|
|
90
86
|
context: Context.Context<unknown>,
|
|
91
87
|
scope: Scope.Scope,
|
|
92
88
|
schema: string,
|
|
93
|
-
execute: PgBossRunner,
|
|
94
89
|
graceful: boolean,
|
|
95
90
|
) {
|
|
96
91
|
const { schedule } = worker;
|
|
97
|
-
yield* registerWorker(
|
|
98
|
-
|
|
99
|
-
schedule.name,
|
|
100
|
-
schedule.workerOptions,
|
|
101
|
-
() => worker.effect,
|
|
102
|
-
scope,
|
|
103
|
-
schema,
|
|
104
|
-
schedule.maxDeferrals,
|
|
105
|
-
context,
|
|
106
|
-
execute,
|
|
107
|
-
graceful,
|
|
92
|
+
yield* registerWorker(client, schedule.name, schedule.workerOptions, () => worker.effect, scope, schema, schedule.maxDeferrals, graceful).pipe(
|
|
93
|
+
Effect.provide(context),
|
|
108
94
|
);
|
|
109
95
|
yield* Effect.tryPromise({
|
|
110
96
|
catch: (error) => toPgBossError("register", error, schedule.name),
|
|
@@ -125,7 +111,6 @@ export const registerJobs = Effect.fnUntraced(function* <R>(
|
|
|
125
111
|
registrations: readonly JobRegistration[],
|
|
126
112
|
context: Context.Context<R>,
|
|
127
113
|
replaceWorkers: boolean,
|
|
128
|
-
execute: PgBossRunner,
|
|
129
114
|
schema = "pgboss",
|
|
130
115
|
startWorkers = true,
|
|
131
116
|
graceful = true,
|
|
@@ -139,9 +124,9 @@ export const registerJobs = Effect.fnUntraced(function* <R>(
|
|
|
139
124
|
const provided = workerContext(context);
|
|
140
125
|
for (const registration of registrations) {
|
|
141
126
|
if (isQueueWorker(registration) || isQueueDefinition(registration)) {
|
|
142
|
-
yield* registerQueue(client, registration, provided, scope, schema,
|
|
127
|
+
yield* registerQueue(client, registration, provided, scope, schema, graceful);
|
|
143
128
|
} else if (isScheduledWorker(registration)) {
|
|
144
|
-
yield* registerSchedule(client, registration, provided, scope, schema,
|
|
129
|
+
yield* registerSchedule(client, registration, provided, scope, schema, graceful);
|
|
145
130
|
}
|
|
146
131
|
}
|
|
147
132
|
});
|
|
@@ -3,7 +3,6 @@ import { WorkerRelease } from "@shivaedev/sql/WorkerRelease.ts";
|
|
|
3
3
|
import type { PgBossClient } from "#client.ts";
|
|
4
4
|
import type { JobRegistration } from "#definition.ts";
|
|
5
5
|
import { toPgBossError } from "#error.ts";
|
|
6
|
-
import type { PgBossRunner } from "#internal/batchHandler.ts";
|
|
7
6
|
import { registerJobs, registrationName } from "./register.ts";
|
|
8
7
|
|
|
9
8
|
export const registerReleasedJobs = Effect.fnUntraced(function* <R>(
|
|
@@ -11,20 +10,17 @@ export const registerReleasedJobs = Effect.fnUntraced(function* <R>(
|
|
|
11
10
|
jobs: readonly JobRegistration[],
|
|
12
11
|
context: Context.Context<R>,
|
|
13
12
|
replaceWorkers: boolean,
|
|
14
|
-
execute: PgBossRunner,
|
|
15
13
|
schema: string | undefined,
|
|
16
14
|
graceful: boolean,
|
|
17
15
|
) {
|
|
18
16
|
const signal = yield* Effect.serviceOption(WorkerRelease);
|
|
19
17
|
const workers = jobs.filter((job) => job._tag !== "QueueDefinition");
|
|
20
18
|
const scope = yield* Effect.scope;
|
|
21
|
-
const register = registerJobs(client, jobs, context, replaceWorkers,
|
|
22
|
-
Effect.provideService(Scope.Scope, scope),
|
|
23
|
-
);
|
|
19
|
+
const register = registerJobs(client, jobs, context, replaceWorkers, schema, true, graceful).pipe(Effect.provideService(Scope.Scope, scope));
|
|
24
20
|
if (Option.isNone(signal) || workers.length === 0) {
|
|
25
21
|
return yield* register;
|
|
26
22
|
}
|
|
27
|
-
yield* registerJobs(client, jobs, context, replaceWorkers,
|
|
23
|
+
yield* registerJobs(client, jobs, context, replaceWorkers, schema, false, graceful);
|
|
28
24
|
let working = replaceWorkers;
|
|
29
25
|
let paused = false;
|
|
30
26
|
yield* signal.value.register((active) =>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Effect, Redacted, Schema } from "effect";
|
|
2
2
|
import type { Job } from "pg-boss";
|
|
3
|
+
import { workerDiagnostics } from "#adapters/workerDiagnostics.ts";
|
|
3
4
|
import { jobContext, type QueueWorker } from "#definition.ts";
|
|
4
5
|
import { PgBossPayloadError } from "#error.ts";
|
|
5
6
|
import { payloadOf } from "#internal/tracing.ts";
|
|
6
7
|
|
|
7
|
-
export function runQueueWorker(worker: QueueWorker, job: Job<unknown
|
|
8
|
-
return Schema.decodeUnknownEffect(worker.queue.schema)(payloadOf(job.data)).pipe(
|
|
8
|
+
export function runQueueWorker(worker: QueueWorker, job: Job<unknown>, deadline: number) {
|
|
9
|
+
return Schema.decodeUnknownEffect(Schema.toCodecJson(worker.queue.schema))(payloadOf(job.data)).pipe(
|
|
9
10
|
Effect.mapError(
|
|
10
11
|
(error) =>
|
|
11
12
|
new PgBossPayloadError({
|
|
@@ -14,6 +15,7 @@ export function runQueueWorker(worker: QueueWorker, job: Job<unknown>) {
|
|
|
14
15
|
queue: worker.queue.name,
|
|
15
16
|
}),
|
|
16
17
|
),
|
|
17
|
-
Effect.flatMap((payload) => worker.handler(payload, jobContext(job))),
|
|
18
|
+
Effect.flatMap((payload) => worker.handler(payload, jobContext(job, deadline))),
|
|
19
|
+
workerDiagnostics,
|
|
18
20
|
);
|
|
19
21
|
}
|
|
@@ -11,7 +11,7 @@ export function decodeStoredData<const TName extends string, const TPayload exte
|
|
|
11
11
|
queue: { readonly name: TName; readonly schema: TPayload },
|
|
12
12
|
data: unknown,
|
|
13
13
|
): Effect.Effect<TPayload["Type"], PgBossPayloadError, TPayload["DecodingServices"]> {
|
|
14
|
-
return Schema.decodeUnknownEffect(queue.schema)(payloadOf(data)).pipe(
|
|
14
|
+
return Schema.decodeUnknownEffect(Schema.toCodecJson(queue.schema))(payloadOf(data)).pipe(
|
|
15
15
|
Effect.mapError((error) => new PgBossPayloadError({ direction: "decode", original: Redacted.make(error), queue: queue.name })),
|
|
16
16
|
);
|
|
17
17
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Job, PgBoss, SendOptions, UpdateOptions, UpdateRequest, UpdateResponse } from "pg-boss";
|
|
2
|
+
import { testingExpirations } from "#adapters/testingExpirations.ts";
|
|
2
3
|
import type { PgBossClient } from "#client.ts";
|
|
3
4
|
import { deferralObservers } from "#internal/deferral.ts";
|
|
4
5
|
import { expireAttempt } from "#internal/expireAttempt.ts";
|
|
5
6
|
import { observeTestingSettlements } from "#internal/observeTestingSettlements.ts";
|
|
6
|
-
import { testingExpirations } from "#internal/testingExpirations.ts";
|
|
7
7
|
import type { TestingBatch, TestingWork } from "#internal/testingIdle.ts";
|
|
8
8
|
|
|
9
9
|
export interface TestingControls {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Clock, Duration, Effect, Option, Redacted } from "effect";
|
|
2
|
+
import type { JobWithMetadata, PgBoss } from "pg-boss";
|
|
3
|
+
import type { JobPayloadSchema, QueueDefinition } from "#definition.ts";
|
|
4
|
+
import { type PgBossPayloadError, PgBossTestingError, PgBossWaitError } from "#error.ts";
|
|
5
|
+
import type { TestingFetches } from "#internal/adapters/testingFetches.ts";
|
|
6
|
+
import { decodeStoredData } from "#internal/storedRows.ts";
|
|
7
|
+
import { testingOperation } from "#internal/testingReadout.ts";
|
|
8
|
+
import type { PgBossTestingWaitOptions } from "#testing.ts";
|
|
9
|
+
|
|
10
|
+
interface Reading<A> {
|
|
11
|
+
readonly arrived: Option.Option<A>;
|
|
12
|
+
readonly seen: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Signal {
|
|
16
|
+
readonly revision: Effect.Effect<number>;
|
|
17
|
+
readonly waitForChange: (after: number) => Effect.Effect<number>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function waitUntil<A, E, R>(
|
|
21
|
+
signal: Signal,
|
|
22
|
+
waitingFor: string,
|
|
23
|
+
read: Effect.Effect<Reading<A>, E, R>,
|
|
24
|
+
options: PgBossTestingWaitOptions | undefined,
|
|
25
|
+
) {
|
|
26
|
+
const within = Duration.fromInputUnsafe(options?.within ?? "20 seconds");
|
|
27
|
+
return Effect.suspend(() => {
|
|
28
|
+
let lastSeen = "nothing yet";
|
|
29
|
+
const wait = Effect.gen(function* () {
|
|
30
|
+
while (true) {
|
|
31
|
+
const revision = yield* signal.revision;
|
|
32
|
+
const { arrived, seen } = yield* read;
|
|
33
|
+
lastSeen = seen;
|
|
34
|
+
if (Option.isSome(arrived)) {
|
|
35
|
+
return arrived.value;
|
|
36
|
+
}
|
|
37
|
+
yield* signal.waitForChange(revision);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return wait.pipe(
|
|
41
|
+
Effect.timeoutOrElse({ duration: within, orElse: () => Effect.fail(new PgBossWaitError({ lastSeen, waitingFor, within })) }),
|
|
42
|
+
// The budget is wall time, so a story's TestClock neither ends the wait early nor holds it open.
|
|
43
|
+
Effect.provideService(Clock.Clock, Clock.Clock.defaultValue()),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fetchesRefused(queue: Pick<QueueDefinition, "name">, original: Error) {
|
|
49
|
+
return new PgBossTestingError({ operation: "fetchesPass", original: Redacted.make(original), queue: queue.name });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function testingWaits(client: () => PgBoss, fetches: () => TestingFetches | undefined, signal: Signal) {
|
|
53
|
+
return {
|
|
54
|
+
fetchesPass: (queue: Pick<QueueDefinition, "name">, count: number, options?: PgBossTestingWaitOptions) =>
|
|
55
|
+
Effect.gen(function* () {
|
|
56
|
+
if (!Number.isSafeInteger(count) || count < 1) {
|
|
57
|
+
return yield* fetchesRefused(queue, new RangeError("Fetch count must be a positive integer"));
|
|
58
|
+
}
|
|
59
|
+
const observed = fetches();
|
|
60
|
+
if (observed === undefined) {
|
|
61
|
+
return yield* fetchesRefused(queue, new Error("Provide the testing clientFactory to a jobs Layer first"));
|
|
62
|
+
}
|
|
63
|
+
const begun = observed.begun(queue.name);
|
|
64
|
+
const fetchesHave = count === 1 ? "fetch that began after the wait has" : "fetches that began after the wait have";
|
|
65
|
+
return yield* waitUntil(
|
|
66
|
+
signal,
|
|
67
|
+
`${count} ${queue.name} ${fetchesHave} finished`,
|
|
68
|
+
Effect.sync(() => {
|
|
69
|
+
const passed = observed.passedSince(queue.name, begun);
|
|
70
|
+
return { arrived: passed >= count ? Option.some(undefined) : Option.none(), seen: `${passed} finished` };
|
|
71
|
+
}),
|
|
72
|
+
options,
|
|
73
|
+
);
|
|
74
|
+
}),
|
|
75
|
+
untilJob: <TName extends string, TPayload extends JobPayloadSchema>(
|
|
76
|
+
queue: Pick<QueueDefinition<TName, TPayload>, "name" | "schema">,
|
|
77
|
+
id: string,
|
|
78
|
+
state: JobWithMetadata["state"],
|
|
79
|
+
options?: PgBossTestingWaitOptions,
|
|
80
|
+
) =>
|
|
81
|
+
waitUntil(
|
|
82
|
+
signal,
|
|
83
|
+
`job ${id} in ${queue.name} is ${state}`,
|
|
84
|
+
testingOperation("untilJob", () => client().getJobById<unknown>(queue.name, id), queue.name).pipe(
|
|
85
|
+
Effect.flatMap((job): Effect.Effect<Reading<JobWithMetadata<TPayload["Type"]>>, PgBossPayloadError, TPayload["DecodingServices"]> => {
|
|
86
|
+
if (job === null) {
|
|
87
|
+
return Effect.succeed({ arrived: Option.none(), seen: "no such job" });
|
|
88
|
+
}
|
|
89
|
+
if (job.state !== state) {
|
|
90
|
+
return Effect.succeed({ arrived: Option.none(), seen: job.state });
|
|
91
|
+
}
|
|
92
|
+
return Effect.map(decodeStoredData(queue, job.data), (data) => ({ arrived: Option.some({ ...job, data }), seen: job.state }));
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
options,
|
|
96
|
+
),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -2,15 +2,18 @@ import { Clock, Effect } from "effect";
|
|
|
2
2
|
import type { Job } from "pg-boss";
|
|
3
3
|
import type { PgBossClient } from "#client.ts";
|
|
4
4
|
import { type PgBossError, toPgBossError } from "#error.ts";
|
|
5
|
-
import { deadlineOf } from "#internal/deadlineOf.ts";
|
|
6
5
|
|
|
7
|
-
export function waitForBatchExpiry(
|
|
6
|
+
export function waitForBatchExpiry(
|
|
7
|
+
client: PgBossClient,
|
|
8
|
+
name: string,
|
|
9
|
+
jobs: readonly Job<unknown>[],
|
|
10
|
+
deadline: number,
|
|
11
|
+
): Effect.Effect<void, PgBossError> {
|
|
8
12
|
return Effect.gen(function* () {
|
|
9
13
|
const findJobs = client.findJobs?.bind(client);
|
|
10
14
|
if (findJobs === undefined) {
|
|
11
15
|
return yield* Effect.fail(new TypeError("This pg-boss client does not support reading interrupted claims"));
|
|
12
16
|
}
|
|
13
|
-
const deadline = Math.max(0, ...jobs.map(deadlineOf)) + 1000;
|
|
14
17
|
let delay = 25;
|
|
15
18
|
const poll = Effect.gen(function* () {
|
|
16
19
|
for (const job of jobs) {
|
|
@@ -25,6 +28,6 @@ export function waitForBatchExpiry(client: PgBossClient, name: string, jobs: rea
|
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
});
|
|
28
|
-
return yield* poll.pipe(Effect.timeout(Math.max(0, deadline - (yield* Clock.currentTimeMillis))));
|
|
31
|
+
return yield* poll.pipe(Effect.timeout(Math.max(0, deadline + 1000 - (yield* Clock.currentTimeMillis))));
|
|
29
32
|
}).pipe(Effect.mapError((error) => toPgBossError("settle", error, name)));
|
|
30
33
|
}
|
package/src/server/schema.ts
CHANGED
|
@@ -1,79 +1,23 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
|
-
import type { Connection } from "effect/sql/SqlConnection";
|
|
4
|
-
import { getConstructionPlans, getMigrationPlans, PgBoss } from "pg-boss";
|
|
5
3
|
import pgBossPackage from "pg-boss/package.json" with { type: "json" };
|
|
6
4
|
import type { SchemaComponent } from "@shivaedev/sql/SchemaComponent.ts";
|
|
5
|
+
import { installSchema } from "#adapters/installSchema.ts";
|
|
7
6
|
import { type PgBossError, toPgBossError } from "#error.ts";
|
|
8
|
-
import { deferralStorePlan } from "#internal/deferralStorePlan.ts";
|
|
9
|
-
import { runSchemaPlan } from "#internal/runSchemaPlan.ts";
|
|
10
|
-
import { resumeSchemaIndexes } from "#internal/schemaIndexBuilds.ts";
|
|
11
7
|
|
|
12
|
-
const Partitions = Schema.Array(
|
|
13
|
-
Schema.Struct({
|
|
14
|
-
policy: Schema.String,
|
|
15
|
-
tableName: Schema.String,
|
|
16
|
-
}),
|
|
17
|
-
);
|
|
18
8
|
const InstalledSchema = Schema.Array(Schema.Struct({ name: Schema.NullOr(Schema.String) }));
|
|
19
9
|
const InstalledVersion = Schema.Array(Schema.Struct({ version: Schema.Number }));
|
|
10
|
+
const InstalledResolution = Schema.Array(Schema.Struct({ installed: Schema.Boolean }));
|
|
20
11
|
|
|
21
12
|
export interface PgBossSchemaOptions {
|
|
22
13
|
readonly schema?: string;
|
|
23
14
|
}
|
|
24
15
|
|
|
25
|
-
async function installSchema(
|
|
26
|
-
connection: Connection,
|
|
27
|
-
options: PgBossSchemaOptions,
|
|
28
|
-
run: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>,
|
|
29
|
-
): Promise<void> {
|
|
30
|
-
const schema = options.schema ?? "pgboss";
|
|
31
|
-
const construction = getConstructionPlans(schema);
|
|
32
|
-
const client = new PgBoss({
|
|
33
|
-
db: { executeSql: async (text, values = []) => ({ rows: Array.from(await run(connection.executeUnprepared(text, values, undefined))) }) },
|
|
34
|
-
schema,
|
|
35
|
-
});
|
|
36
|
-
if (!(await client.isInstalled())) {
|
|
37
|
-
await runSchemaPlan(connection, construction, run);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const version = await client.schemaVersion();
|
|
41
|
-
if (version === null) {
|
|
42
|
-
throw new Error("The pg-boss version table is empty");
|
|
43
|
-
}
|
|
44
|
-
if (version > pgBossPackage.pgboss.schema) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (version >= 27) {
|
|
48
|
-
await resumeSchemaIndexes(client, schema);
|
|
49
|
-
}
|
|
50
|
-
if (version < pgBossPackage.pgboss.schema) {
|
|
51
|
-
const partitionTables = Schema.decodeUnknownSync(Partitions)(
|
|
52
|
-
await run(connection.executeUnprepared(`SELECT table_name AS "tableName", policy FROM ${schema}.queue WHERE partition = true`, [], undefined)),
|
|
53
|
-
);
|
|
54
|
-
await runSchemaPlan(connection, getMigrationPlans(schema, version, { partitionTables: Array.from(partitionTables) }), run, {
|
|
55
|
-
schema,
|
|
56
|
-
version: pgBossPackage.pgboss.schema,
|
|
57
|
-
});
|
|
58
|
-
await resumeSchemaIndexes(client, schema);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
16
|
export function installPgBossSchema(options: PgBossSchemaOptions = {}): Effect.Effect<void, PgBossError, SqlClient.SqlClient> {
|
|
63
|
-
return Effect.scoped(
|
|
64
|
-
Effect.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const run = Effect.runPromiseWith(yield* Effect.context<never>());
|
|
68
|
-
yield* Effect.tryPromise({
|
|
69
|
-
catch: (error) => error,
|
|
70
|
-
try: async () => {
|
|
71
|
-
await installSchema(connection, options, run);
|
|
72
|
-
await run(connection.executeRaw(deferralStorePlan(options.schema ?? "pgboss"), []));
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}).pipe(Effect.mapError((error) => toPgBossError("install", error))),
|
|
76
|
-
).pipe(Effect.uninterruptible);
|
|
17
|
+
return Effect.scoped(installSchema(options)).pipe(
|
|
18
|
+
Effect.mapError((error) => toPgBossError("install", error)),
|
|
19
|
+
Effect.uninterruptible,
|
|
20
|
+
);
|
|
77
21
|
}
|
|
78
22
|
|
|
79
23
|
export function pgBossSchema(options: PgBossSchemaOptions = {}): SchemaComponent {
|
|
@@ -92,9 +36,14 @@ export function pgBossSchema(options: PgBossSchemaOptions = {}): SchemaComponent
|
|
|
92
36
|
? []
|
|
93
37
|
: yield* sql`select version from ${table}`.withoutTransform.pipe(Effect.flatMap(Schema.decodeUnknownEffect(InstalledVersion)));
|
|
94
38
|
const required = pgBossPackage.pgboss.schema;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
39
|
+
if (row === undefined || row.version < required) {
|
|
40
|
+
return `schema ${schema}, installed version ${row?.version ?? "missing"}, required version ${required}`;
|
|
41
|
+
}
|
|
42
|
+
const job = sql`${sql(schema)}.${sql("job")}`;
|
|
43
|
+
const [resolution] = yield* sql`SELECT EXISTS (
|
|
44
|
+
SELECT 1 FROM pg_trigger WHERE tgrelid = to_regclass(${job.compile()[0]}) AND tgname = 'effect_dead_letter_resolution'
|
|
45
|
+
) AS installed`.withoutTransform.pipe(Effect.flatMap(Schema.decodeUnknownEffect(InstalledResolution)));
|
|
46
|
+
return resolution?.installed ? undefined : `schema ${schema}, missing dead-letter resolution trigger`;
|
|
98
47
|
}),
|
|
99
48
|
};
|
|
100
49
|
}
|
package/src/server/service.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Context, Effect, Exit, Layer, type Option, Redacted, type Schema, Scope
|
|
|
2
2
|
import type { ConstructorOptions, JobWithMetadata, SendOptions, StopOptions } from "pg-boss";
|
|
3
3
|
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
4
4
|
import { genericMethod } from "@shivaedev/service/genericMethod.ts";
|
|
5
|
+
import { clientErrorReporter } from "#adapters/clientErrorReporter.ts";
|
|
5
6
|
import type { PgBossClientFactory } from "#client.ts";
|
|
6
7
|
import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "#definition.ts";
|
|
7
8
|
import type { PgBossClientOptionsError, PgBossError, PgBossPayloadError, PgBossQueueNamesError, PgBossQueueOptionsError } from "#error.ts";
|
|
@@ -19,7 +20,7 @@ export interface PgBossService {
|
|
|
19
20
|
readonly deadLetteredJobs: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
|
|
20
21
|
queue: { readonly name: TName; readonly schema: TPayload },
|
|
21
22
|
options: JobPageOptions,
|
|
22
|
-
) => Effect.Effect<JobPage<DeadLetteredJob<
|
|
23
|
+
) => Effect.Effect<JobPage<DeadLetteredJob<unknown>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
|
|
23
24
|
readonly enqueue: <const TName extends string, const TPayload extends JobPayloadSchema, const TQueue extends QueueDefinition<TName, TPayload>>(
|
|
24
25
|
queue: QueueDefinition<TName, TPayload> & TQueue,
|
|
25
26
|
payload: NoInfer<Schema.Schema.Type<TPayload>>,
|
|
@@ -28,11 +29,11 @@ export interface PgBossService {
|
|
|
28
29
|
readonly failedJobs: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
|
|
29
30
|
queue: { readonly name: TName; readonly schema: TPayload },
|
|
30
31
|
options: JobPageOptions,
|
|
31
|
-
) => Effect.Effect<JobPage<ListedJob<
|
|
32
|
+
) => Effect.Effect<JobPage<ListedJob<unknown>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
|
|
32
33
|
readonly getJob: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
|
|
33
34
|
queue: { readonly name: TName; readonly schema: TPayload },
|
|
34
35
|
id: string,
|
|
35
|
-
) => Effect.Effect<Option.Option<JobWithMetadata<
|
|
36
|
+
) => Effect.Effect<Option.Option<JobWithMetadata<unknown>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
|
|
36
37
|
readonly health: () => Effect.Effect<JobsHealth, PgBossError>;
|
|
37
38
|
readonly retry: (queue: Pick<QueueDefinition, "name">, id: string) => Effect.Effect<void, PgBossError>;
|
|
38
39
|
}
|
|
@@ -49,8 +50,7 @@ export function pgBossLayer(options: PgBossOptions): Layer.Layer<PgBoss, PgBossE
|
|
|
49
50
|
return Layer.effect(
|
|
50
51
|
PgBoss,
|
|
51
52
|
Effect.gen(function* () {
|
|
52
|
-
const
|
|
53
|
-
const execute = Effect.runPromiseWith(dependencies);
|
|
53
|
+
const report = yield* clientErrorReporter(logClientError);
|
|
54
54
|
const acquired = yield* Effect.acquireRelease(
|
|
55
55
|
acquireClient(
|
|
56
56
|
{
|
|
@@ -61,9 +61,7 @@ export function pgBossLayer(options: PgBossOptions): Layer.Layer<PgBoss, PgBossE
|
|
|
61
61
|
...(connectionString === undefined ? {} : { connectionString: Redacted.value(connectionString) }),
|
|
62
62
|
},
|
|
63
63
|
},
|
|
64
|
-
|
|
65
|
-
void execute(logClientError(error));
|
|
66
|
-
},
|
|
64
|
+
report,
|
|
67
65
|
),
|
|
68
66
|
(client) => releaseClient(client, stop),
|
|
69
67
|
);
|
|
@@ -94,24 +92,17 @@ export function makePgBoss<const TIdentifier extends string>(identifier: TIdenti
|
|
|
94
92
|
initialize: Effect.gen(function* () {
|
|
95
93
|
yield* validateQueueNames(options.jobs, queueNames);
|
|
96
94
|
const acquired = yield* PgBoss;
|
|
97
|
-
const execute = Effect.runPromiseWith(dependencies);
|
|
98
95
|
const workerScope = yield* Scope.make();
|
|
99
96
|
const service = makeService(acquired.client, options.jobs.map(registrationName), acquired.schema);
|
|
100
97
|
const context = Context.add(dependencies, Service, service);
|
|
101
98
|
const reportError = options.onError ?? logClientError;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
});
|
|
99
|
+
const report = yield* clientErrorReporter(reportError).pipe(Effect.provide(context));
|
|
100
|
+
const unsubscribe = subscribeClientErrors(acquired.client, report);
|
|
105
101
|
yield* Effect.addFinalizer(() => Scope.close(workerScope, Exit.void).pipe(Effect.ensuring(Effect.sync(unsubscribe))));
|
|
106
|
-
yield* registerReleasedJobs(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
acquired.reused,
|
|
111
|
-
(effect) => execute(effect),
|
|
112
|
-
acquired.schema,
|
|
113
|
-
acquired.stop?.graceful !== false,
|
|
114
|
-
).pipe(Effect.provideService(Scope.Scope, workerScope), Effect.provide(dependencies));
|
|
102
|
+
yield* registerReleasedJobs(acquired.client, options.jobs, context, acquired.reused, acquired.schema, acquired.stop?.graceful !== false).pipe(
|
|
103
|
+
Effect.provideService(Scope.Scope, workerScope),
|
|
104
|
+
Effect.provide(dependencies),
|
|
105
|
+
);
|
|
115
106
|
return service;
|
|
116
107
|
}),
|
|
117
108
|
methods: (service) => ({
|
package/src/server/testing.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type DateTime, Duration, Effect, Redacted } from "effect";
|
|
2
2
|
import type { PgBoss } from "pg-boss";
|
|
3
3
|
import type { PgBossClientFactory } from "#client.ts";
|
|
4
|
+
import { defaultClientFactory } from "#defaultClientFactory.ts";
|
|
4
5
|
import type { JobPayloadSchema, QueueDefinition } from "#definition.ts";
|
|
5
6
|
import { PgBossTestingError } from "#error.ts";
|
|
6
|
-
import {
|
|
7
|
+
import { type TestingFetches, testingFetches } from "#internal/adapters/testingFetches.ts";
|
|
7
8
|
import { testingClient } from "#internal/testingClient.ts";
|
|
8
9
|
import { testingDeferrals } from "#internal/testingDeferrals.ts";
|
|
9
10
|
import { type TestingWork, testingIdle } from "#internal/testingIdle.ts";
|
|
@@ -11,6 +12,7 @@ import { testingOperation, testingReadout } from "#internal/testingReadout.ts";
|
|
|
11
12
|
import { testingSchedules } from "#internal/testingSchedules.ts";
|
|
12
13
|
import { testingSignal } from "#internal/testingSignal.ts";
|
|
13
14
|
import { testingSnapshot } from "#internal/testingSnapshot.ts";
|
|
15
|
+
import { testingWaits } from "#internal/testingWaits.ts";
|
|
14
16
|
|
|
15
17
|
export type SnapshotQueue = Pick<QueueDefinition<string, JobPayloadSchema>, "name" | "schema">;
|
|
16
18
|
|
|
@@ -22,6 +24,10 @@ export interface UnfinishedJob<TQueue extends SnapshotQueue> {
|
|
|
22
24
|
readonly state: "active" | "created" | "failed" | "retry";
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
export interface PgBossTestingWaitOptions {
|
|
28
|
+
readonly within?: Duration.Input;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export const testRetryPolicy = Object.freeze({ retryBackoff: false, retryDelay: 0 });
|
|
26
32
|
|
|
27
33
|
export interface PgBossTestingOptions {
|
|
@@ -30,6 +36,7 @@ export interface PgBossTestingOptions {
|
|
|
30
36
|
|
|
31
37
|
export function makePgBossTesting(options: PgBossTestingOptions = {}) {
|
|
32
38
|
let current: PgBoss | undefined;
|
|
39
|
+
let fetches: TestingFetches | undefined;
|
|
33
40
|
let schema = "pgboss";
|
|
34
41
|
const signal = testingSignal();
|
|
35
42
|
const work: TestingWork = { batches: new Set(), queues: new Set() };
|
|
@@ -52,12 +59,14 @@ export function makePgBossTesting(options: PgBossTestingOptions = {}) {
|
|
|
52
59
|
throw new Error("A testing controller owns one jobs client; create a controller per runtime");
|
|
53
60
|
}
|
|
54
61
|
schema = clientOptions.schema ?? "pgboss";
|
|
55
|
-
current =
|
|
62
|
+
current = defaultClientFactory({ ...clientOptions, "__test__enableSpies": true });
|
|
63
|
+
fetches = testingFetches(current.getDb(), signal.changed);
|
|
56
64
|
return testingClient(current, clientOptions.schema ?? "pgboss", controls);
|
|
57
65
|
};
|
|
58
66
|
return {
|
|
59
67
|
...testingDeferrals(client, () => schema, signal.changed),
|
|
60
68
|
...testingSchedules(client, signal.changed),
|
|
69
|
+
...testingWaits(client, () => fetches, signal),
|
|
61
70
|
checkBatchWindow: (queue: Pick<QueueDefinition, "name" | "workerOptions">, millisecondsPerJob: number) =>
|
|
62
71
|
testingOperation(
|
|
63
72
|
"checkBatchWindow",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nativeClient.d.ts","sourceRoot":"","sources":["../../../src/server/internal/nativeClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1D,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,UA0BvD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nativeClient.js","sourceRoot":"","sources":["../../../src/server/internal/nativeClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAA2B,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,UAAU,YAAY,CAAC,OAA2B;IACvD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC9C,MAAM,MAAM,GAAG;QACd,GAAG,OAAO;QACV,MAAM,EAAE,GAAG,EAAE;YACZ,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACzB,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC5B,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CACF,CAAC;YACF,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzB,OAAO,MAAM,CAAC;QACf,CAAC;KACD,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,IAAI,GAAG,KAAK,EAAE,WAAW,EAAE,EAAE;QACnC,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,0EAA0E;QAC1E,IAAI,WAAW,EAAE,KAAK,KAAK,KAAK,EAAE,CAAC;YAClC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;QACtC,CAAC;IACF,CAAC,CAAC;IACF,OAAO,MAAM,CAAC;AACf,CAAC"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type Context, Effect, Scope } from "effect";
|
|
2
|
-
import type { Job, WorkOptions } from "pg-boss";
|
|
3
|
-
import type { PgBossClient } from "#client.ts";
|
|
4
|
-
import { type PgBossRunner } from "#internal/batchHandler.ts";
|
|
5
|
-
export declare const registerWorker: (client: PgBossClient, name: string, options: WorkOptions, run: (job: Job<unknown>) => Effect.Effect<unknown, unknown, unknown>, scope: Scope.Scope, schema: string, maxDeferrals: number, context: Context.Context<unknown>, execute: PgBossRunner, graceful: boolean) => Effect.Effect<string, import("#error.ts").PgBossError, never>;
|
|
6
|
-
//# sourceMappingURL=registerWorker.d.ts.map
|