@shivaedev/jobs 0.25.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 +37 -0
- package/README.md +44 -19
- 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 +3 -2
- 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 +0 -1
- package/dist/server/definition.d.ts.map +1 -1
- package/dist/server/definition.js +0 -4
- package/dist/server/definition.js.map +1 -1
- package/dist/server/error.d.ts +11 -1
- package/dist/server/error.d.ts.map +1 -1
- package/dist/server/error.js +6 -1
- 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 +9 -1
- 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.map +1 -1
- package/dist/server/internal/runQueueWorker.js +3 -2
- 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/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/clientErrorReporter.ts +8 -0
- package/src/server/adapters/installSchema.ts +68 -0
- package/src/server/{internal → adapters}/registerWorker.ts +6 -7
- 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 +0 -5
- package/src/server/error.ts +11 -1
- package/src/server/health.ts +1 -0
- package/src/server/internal/adapters/testingFetches.ts +36 -0
- package/src/server/internal/batchHandler.ts +11 -8
- 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 +6 -21
- package/src/server/internal/registerReleasedJobs.ts +2 -6
- package/src/server/internal/runQueueWorker.ts +3 -1
- 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/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
|
@@ -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
|
+
}
|
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>, deadline: number) => 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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registerWorker.d.ts","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAS,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAK5E,eAAO,MAAM,cAAc,wEAIf,GAAG,CAAC,OAAO,CAAC,YAAY,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,yMAmCrF,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registerWorker.js","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduleDefinition.d.ts","sourceRoot":"","sources":["../../../src/server/internal/scheduleDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGhF,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,OAAO,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAiB/H"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scheduleDefinition.js","sourceRoot":"","sources":["../../../src/server/internal/scheduleDefinition.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAElF,MAAM,UAAU,kBAAkB,CAA6B,OAAqC;IACnG,MAAM,UAAU,GAA8B;QAC7C,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;QACzC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC;QAC/C,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CACf,MAAM,CAAC,MAAM,CAAC;YACb,IAAI,EAAE,iBAA0B;YAChC,MAAM;YACN,QAAQ,EAAE,UAAU;SACpB,CAAC;QACH,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAClE,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;KAC5F,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|