@shivaedev/jobs 0.25.0 → 0.27.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 +44 -0
- package/README.md +58 -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/adapters/retireJobs.d.ts +5 -0
- package/dist/server/adapters/retireJobs.d.ts.map +1 -0
- package/dist/server/adapters/retireJobs.js +28 -0
- package/dist/server/adapters/retireJobs.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 +2 -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/makeService.js +1 -1
- package/dist/server/internal/makeService.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 +5 -4
- 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 +5 -4
- 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/retireJobs.ts +30 -0
- package/src/server/adapters/workerDiagnostics.ts +28 -0
- package/src/server/client.ts +2 -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/makeService.ts +1 -1
- package/src/server/internal/register.ts +6 -21
- package/src/server/internal/registerReleasedJobs.ts +6 -5
- 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 +11 -14
- 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
package/dist/server/testing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Duration, Effect, Redacted } from "effect";
|
|
2
|
+
import { defaultClientFactory } from "#defaultClientFactory.ts";
|
|
2
3
|
import { PgBossTestingError } from "#error.ts";
|
|
3
|
-
import {
|
|
4
|
+
import { testingFetches } from "#internal/adapters/testingFetches.ts";
|
|
4
5
|
import { testingClient } from "#internal/testingClient.ts";
|
|
5
6
|
import { testingDeferrals } from "#internal/testingDeferrals.ts";
|
|
6
7
|
import { testingIdle } from "#internal/testingIdle.ts";
|
|
@@ -8,9 +9,11 @@ import { testingOperation, testingReadout } from "#internal/testingReadout.ts";
|
|
|
8
9
|
import { testingSchedules } from "#internal/testingSchedules.ts";
|
|
9
10
|
import { testingSignal } from "#internal/testingSignal.ts";
|
|
10
11
|
import { testingSnapshot } from "#internal/testingSnapshot.ts";
|
|
12
|
+
import { testingWaits } from "#internal/testingWaits.ts";
|
|
11
13
|
export const testRetryPolicy = Object.freeze({ retryBackoff: false, retryDelay: 0 });
|
|
12
14
|
export function makePgBossTesting(options = {}) {
|
|
13
15
|
let current;
|
|
16
|
+
let fetches;
|
|
14
17
|
let schema = "pgboss";
|
|
15
18
|
const signal = testingSignal();
|
|
16
19
|
const work = { batches: new Set(), queues: new Set() };
|
|
@@ -33,12 +36,14 @@ export function makePgBossTesting(options = {}) {
|
|
|
33
36
|
throw new Error("A testing controller owns one jobs client; create a controller per runtime");
|
|
34
37
|
}
|
|
35
38
|
schema = clientOptions.schema ?? "pgboss";
|
|
36
|
-
current =
|
|
39
|
+
current = defaultClientFactory({ ...clientOptions, "__test__enableSpies": true });
|
|
40
|
+
fetches = testingFetches(current.getDb(), signal.changed);
|
|
37
41
|
return testingClient(current, clientOptions.schema ?? "pgboss", controls);
|
|
38
42
|
};
|
|
39
43
|
return {
|
|
40
44
|
...testingDeferrals(client, () => schema, signal.changed),
|
|
41
45
|
...testingSchedules(client, signal.changed),
|
|
46
|
+
...testingWaits(client, () => fetches, signal),
|
|
42
47
|
checkBatchWindow: (queue, millisecondsPerJob) => testingOperation("checkBatchWindow", async () => {
|
|
43
48
|
const stored = await client().getQueue(queue.name);
|
|
44
49
|
if (stored === null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/server/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/server/testing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/jobs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Effect-native jobs and lifecycle management for pg-boss",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
"README.md"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@shivaedev/errors": "0.
|
|
51
|
+
"@shivaedev/errors": "0.3.0",
|
|
52
52
|
"@shivaedev/service": "0.6.0",
|
|
53
53
|
"@shivaedev/sql": "0.27.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@shivaedev/http": "0.3.1",
|
|
57
|
-
"@shivaedev/quality": "0.
|
|
57
|
+
"@shivaedev/quality": "0.50.0",
|
|
58
58
|
"@shivaedev/test": "0.12.0",
|
|
59
59
|
"@shivaedev/test-sql": "0.15.0",
|
|
60
|
-
"@shivaedev/test-story": "0.
|
|
60
|
+
"@shivaedev/test-story": "0.30.0",
|
|
61
61
|
"@types/node": "24.19.2",
|
|
62
62
|
"effect": "4.0.6",
|
|
63
63
|
"pg-boss": "12.38.0",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
export const clientErrorReporter = Effect.fnUntraced(function* <R>(report: (error: Error) => Effect.Effect<unknown, never, R>) {
|
|
4
|
+
const run = Effect.runPromiseWith(yield* Effect.context<R>());
|
|
5
|
+
return (error: Error): void => {
|
|
6
|
+
void run(Effect.exit(report(error)));
|
|
7
|
+
};
|
|
8
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import type { Connection } from "effect/sql/SqlConnection";
|
|
4
|
+
import { getConstructionPlans, getMigrationPlans, PgBoss } from "pg-boss";
|
|
5
|
+
import pgBossPackage from "pg-boss/package.json" with { type: "json" };
|
|
6
|
+
import { jobStorePlans } from "#internal/jobStorePlans.ts";
|
|
7
|
+
import type { PgBossSchemaOptions } from "#schema.ts";
|
|
8
|
+
import { runSchemaPlan } from "./runSchemaPlan.ts";
|
|
9
|
+
import { resumeSchemaIndexes } from "./schemaIndexBuilds.ts";
|
|
10
|
+
|
|
11
|
+
const Partitions = Schema.Array(
|
|
12
|
+
Schema.Struct({
|
|
13
|
+
policy: Schema.String,
|
|
14
|
+
tableName: Schema.String,
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
async function installNativeSchema(
|
|
18
|
+
connection: Connection,
|
|
19
|
+
options: PgBossSchemaOptions,
|
|
20
|
+
run: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const schema = options.schema ?? "pgboss";
|
|
23
|
+
const construction = getConstructionPlans(schema);
|
|
24
|
+
const client = new PgBoss({
|
|
25
|
+
db: { executeSql: async (text, values = []) => ({ rows: Array.from(await run(connection.executeUnprepared(text, values, undefined))) }) },
|
|
26
|
+
schema,
|
|
27
|
+
});
|
|
28
|
+
if (!(await client.isInstalled())) {
|
|
29
|
+
await runSchemaPlan(connection, construction, run);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const version = await client.schemaVersion();
|
|
33
|
+
if (version === null) {
|
|
34
|
+
throw new Error("The pg-boss version table is empty");
|
|
35
|
+
}
|
|
36
|
+
if (version > pgBossPackage.pgboss.schema) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (version >= 27) {
|
|
40
|
+
await resumeSchemaIndexes(client, schema);
|
|
41
|
+
}
|
|
42
|
+
if (version < pgBossPackage.pgboss.schema) {
|
|
43
|
+
const partitionTables = Schema.decodeUnknownSync(Partitions)(
|
|
44
|
+
await run(connection.executeUnprepared(`SELECT table_name AS "tableName", policy FROM ${schema}.queue WHERE partition = true`, [], undefined)),
|
|
45
|
+
);
|
|
46
|
+
await runSchemaPlan(connection, getMigrationPlans(schema, version, { partitionTables: Array.from(partitionTables) }), run, {
|
|
47
|
+
schema,
|
|
48
|
+
version: pgBossPackage.pgboss.schema,
|
|
49
|
+
});
|
|
50
|
+
await resumeSchemaIndexes(client, schema);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const installSchema = Effect.fnUntraced(function* (options: PgBossSchemaOptions) {
|
|
55
|
+
const sql = yield* SqlClient.SqlClient;
|
|
56
|
+
const connection = yield* sql.reserve;
|
|
57
|
+
const run = Effect.runPromiseWith(yield* Effect.context<never>());
|
|
58
|
+
yield* Effect.tryPromise({
|
|
59
|
+
catch: (error) => error,
|
|
60
|
+
try: async (signal) => {
|
|
61
|
+
const execute: typeof run = (effect) => run(effect, { signal });
|
|
62
|
+
await installNativeSchema(connection, options, execute);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
for (const plan of jobStorePlans(options.schema ?? "pgboss")) {
|
|
66
|
+
yield* connection.executeRaw(plan, []);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Effect, Fiber, Scope } from "effect";
|
|
2
2
|
import type { Job, WorkOptions } from "pg-boss";
|
|
3
3
|
import type { PgBossClient } from "#client.ts";
|
|
4
4
|
import { toPgBossError } from "#error.ts";
|
|
5
|
-
import { batchHandler
|
|
5
|
+
import { batchHandler } from "#internal/batchHandler.ts";
|
|
6
6
|
import { deferrableAttempt } from "#internal/deferrableAttempt.ts";
|
|
7
7
|
import { retryWake } from "#internal/retryWake.ts";
|
|
8
8
|
import { traceJob } from "#internal/tracing.ts";
|
|
9
9
|
|
|
10
|
-
export const registerWorker = Effect.fnUntraced(function* (
|
|
10
|
+
export const registerWorker = Effect.fnUntraced(function* <R>(
|
|
11
11
|
client: PgBossClient,
|
|
12
12
|
name: string,
|
|
13
13
|
options: WorkOptions,
|
|
14
|
-
run: (job: Job<unknown>, deadline: number) => Effect.Effect<unknown, unknown,
|
|
14
|
+
run: (job: Job<unknown>, deadline: number) => Effect.Effect<unknown, unknown, R>,
|
|
15
15
|
scope: Scope.Scope,
|
|
16
16
|
schema: string,
|
|
17
17
|
maxDeferrals: number,
|
|
18
|
-
context: Context.Context<unknown>,
|
|
19
|
-
execute: PgBossRunner,
|
|
20
18
|
graceful: boolean,
|
|
21
19
|
) {
|
|
20
|
+
const execute = Effect.runPromiseWith(yield* Effect.context<R>());
|
|
22
21
|
const fibers = yield* Scope.fork(scope);
|
|
23
22
|
function runJob(job: Job<unknown>, deadline: number) {
|
|
24
|
-
return
|
|
23
|
+
return traceJob(deferrableAttempt(run(job, deadline), client, schema, job, maxDeferrals), job, name);
|
|
25
24
|
}
|
|
26
25
|
function afterFailure(attempt: Job<unknown>) {
|
|
27
26
|
return retryWake(client, name, attempt.id, registration).pipe(Effect.forkIn(fibers), Effect.asVoid);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { PgBossClient } from "#client.ts";
|
|
3
|
+
import type { JobRegistration } from "#definition.ts";
|
|
4
|
+
import { toPgBossError } from "#error.ts";
|
|
5
|
+
import { deadLetterQueueName } from "#health.ts";
|
|
6
|
+
import { registrationNames } from "#internal/register.ts";
|
|
7
|
+
|
|
8
|
+
export const retireJobs = Effect.fnUntraced(function* (client: PgBossClient, jobs: readonly JobRegistration[], retired: readonly string[]) {
|
|
9
|
+
yield* Effect.try({
|
|
10
|
+
catch: (error) => toPgBossError("register", error),
|
|
11
|
+
try: () => {
|
|
12
|
+
const active = registrationNames(jobs).flatMap((name) => [name, deadLetterQueueName(name)]);
|
|
13
|
+
if (retired.some((name) => active.includes(name))) {
|
|
14
|
+
throw new TypeError("Retired queues must not be registered or used as dead-letter queues");
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
for (const name of retired) {
|
|
19
|
+
yield* Effect.tryPromise({
|
|
20
|
+
catch: (error) => toPgBossError("register", error, name),
|
|
21
|
+
try: async () => {
|
|
22
|
+
if (client.deleteQueue === undefined) {
|
|
23
|
+
throw new TypeError("This pg-boss client does not support deleteQueue");
|
|
24
|
+
}
|
|
25
|
+
await client.offWork(name);
|
|
26
|
+
await client.deleteQueue(name);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context, Effect, Logger, References } from "effect";
|
|
2
|
+
import { redact } from "@shivaedev/errors/redact.ts";
|
|
3
|
+
import { redactDiagnosticCause } from "@shivaedev/errors/redactCause.ts";
|
|
4
|
+
import { isSensitiveKey, REDACTED } from "@shivaedev/errors/sensitive.ts";
|
|
5
|
+
|
|
6
|
+
function maskedLogger(logger: Logger.Logger<unknown, unknown>) {
|
|
7
|
+
return Logger.make((options) => {
|
|
8
|
+
const context = options.fiber.context;
|
|
9
|
+
const annotations = Object.fromEntries(
|
|
10
|
+
Object.entries(options.fiber.getRef(References.CurrentLogAnnotations)).map(([key, value]) => [
|
|
11
|
+
key,
|
|
12
|
+
isSensitiveKey(key) ? REDACTED : redact(value),
|
|
13
|
+
]),
|
|
14
|
+
);
|
|
15
|
+
options.fiber.setContext(Context.add(context, References.CurrentLogAnnotations, annotations));
|
|
16
|
+
try {
|
|
17
|
+
return logger.log({ ...options, cause: redactDiagnosticCause(options.cause), message: redact(options.message) });
|
|
18
|
+
} finally {
|
|
19
|
+
options.fiber.setContext(context);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function workerDiagnostics<A, E, R>(effect: Effect.Effect<A, E, R>) {
|
|
25
|
+
return Effect.flatMap(Logger.CurrentLoggers, (loggers) =>
|
|
26
|
+
Effect.provideService(effect, Logger.CurrentLoggers, new Set([...loggers].map(maskedLogger))),
|
|
27
|
+
);
|
|
28
|
+
}
|
package/src/server/client.ts
CHANGED
|
@@ -11,11 +11,11 @@ import type {
|
|
|
11
11
|
StopOptions,
|
|
12
12
|
WorkOptions,
|
|
13
13
|
} from "pg-boss";
|
|
14
|
-
import { nativeClient } from "#internal/nativeClient.ts";
|
|
15
14
|
|
|
16
15
|
export interface PgBossClient {
|
|
17
16
|
readonly complete?: PgBoss["complete"];
|
|
18
17
|
readonly createQueue: (name: string, options?: Omit<Queue, "name">) => Promise<void>;
|
|
18
|
+
readonly deleteQueue?: PgBoss["deleteQueue"];
|
|
19
19
|
readonly fail?: PgBoss["fail"];
|
|
20
20
|
readonly findJobs?: (name: string, options?: FindJobsOptions) => Promise<readonly JobWithMetadata<unknown>[]>;
|
|
21
21
|
readonly getDb?: PgBoss["getDb"];
|
|
@@ -30,11 +30,10 @@ export interface PgBossClient {
|
|
|
30
30
|
readonly send: (name: string, data?: object | null, options?: SendOptions) => Promise<string | null>;
|
|
31
31
|
readonly start: () => Promise<unknown>;
|
|
32
32
|
readonly stop: (options?: StopOptions) => Promise<void>;
|
|
33
|
+
readonly unschedule?: PgBoss["unschedule"];
|
|
33
34
|
readonly update?: PgBoss["update"];
|
|
34
35
|
readonly updateQueue?: PgBoss["updateQueue"];
|
|
35
36
|
readonly work: (name: string, options: WorkOptions, handler: (jobs: readonly Job<unknown>[]) => Promise<unknown>) => Promise<string>;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export type PgBossClientFactory = (options: ConstructorOptions) => PgBossClient;
|
|
39
|
-
|
|
40
|
-
export const defaultClientFactory: PgBossClientFactory = nativeClient;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Socket } from "node:net";
|
|
2
2
|
import { type ConstructorOptions, PgBoss } from "pg-boss";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function defaultClientFactory(options: ConstructorOptions) {
|
|
5
5
|
const disconnected = new Set<Promise<void>>();
|
|
6
6
|
const config = {
|
|
7
7
|
...options,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DefineScheduleOptions, ScheduleDefinition } from "#definition.ts";
|
|
2
2
|
import { maxDeferrals, nativeQueueOptions } from "#internal/definitionOptions.ts";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function defineSchedule<const TName extends string>(options: DefineScheduleOptions<TName>): ScheduleDefinition<TName> {
|
|
5
5
|
const definition: ScheduleDefinition<TName> = {
|
|
6
6
|
_tag: "ScheduleDefinition",
|
|
7
7
|
cron: options.cron,
|
package/src/server/definition.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Clock, Duration, Effect, Schema } from "effect";
|
|
2
2
|
import type { Job, policies, Queue, ScheduleOptions, WorkOptions } from "pg-boss";
|
|
3
3
|
import { maxDeferrals, nativeQueueOptions } from "#internal/definitionOptions.ts";
|
|
4
|
-
import { scheduleDefinition } from "#internal/scheduleDefinition.ts";
|
|
5
4
|
|
|
6
5
|
export class RetryAt extends Schema.TaggedError<RetryAt>()("RetryAt", {
|
|
7
6
|
at: Schema.DateTimeUtc,
|
|
@@ -123,10 +122,6 @@ export interface DefineScheduleOptions<TName extends string> {
|
|
|
123
122
|
readonly worker?: WorkOptions;
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
export function defineSchedule<const TName extends string>(options: DefineScheduleOptions<TName>): ScheduleDefinition<TName> {
|
|
127
|
-
return scheduleDefinition(options);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
125
|
export function jobContext(job: Job<unknown>, deadline: number): JobContext {
|
|
131
126
|
return {
|
|
132
127
|
expireInSeconds: job.expireInSeconds,
|
package/src/server/error.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Cause, Data, Redacted } from "effect";
|
|
1
|
+
import { Cause, Data, Duration, Redacted } from "effect";
|
|
2
2
|
import { failureOutput } from "#internal/failureOutput.ts";
|
|
3
3
|
|
|
4
4
|
export type PgBossOperation =
|
|
@@ -76,6 +76,16 @@ export class PgBossTestingError extends Data.TaggedError("PgBossTestingError")<{
|
|
|
76
76
|
readonly queue?: string | undefined;
|
|
77
77
|
}> {}
|
|
78
78
|
|
|
79
|
+
export class PgBossWaitError extends Data.TaggedError("PgBossWaitError")<{
|
|
80
|
+
readonly lastSeen: string;
|
|
81
|
+
readonly waitingFor: string;
|
|
82
|
+
readonly within: Duration.Duration;
|
|
83
|
+
}> {
|
|
84
|
+
override get message(): string {
|
|
85
|
+
return `Gave up after ${Duration.format(this.within)} waiting until ${this.waitingFor}; last seen: ${this.lastSeen}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
export function toPgBossError(operation: PgBossOperation, error: unknown, queue?: string): PgBossError {
|
|
80
90
|
return new PgBossError({
|
|
81
91
|
operation,
|
package/src/server/health.ts
CHANGED
|
@@ -48,6 +48,7 @@ export interface ListedJob<TData> {
|
|
|
48
48
|
export interface DeadLetteredJob<TData> extends ListedJob<TData> {
|
|
49
49
|
readonly sourceId: Option.Option<string>;
|
|
50
50
|
readonly sourceOutput: JobFailure | null;
|
|
51
|
+
readonly sourceQueue: string;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export interface JobPageOptions {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PgBoss } from "pg-boss";
|
|
2
|
+
|
|
3
|
+
// pg-boss offers no hook for a worker fetch that finds nothing, so the controller recognizes its claim statement on the client's database.
|
|
4
|
+
const claim = /UPDATE\s+\S+\s+j\s+SET\s+state\s*=\s*'active'/u;
|
|
5
|
+
const claimedQueue = /\bj\.name\s*=\s*'(?<queue>[^']+)'/u;
|
|
6
|
+
|
|
7
|
+
export interface TestingFetches {
|
|
8
|
+
readonly begun: (queue: string) => number;
|
|
9
|
+
readonly passedSince: (queue: string, begun: number) => number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function testingFetches(db: ReturnType<PgBoss["getDb"]>, changed: () => void): TestingFetches {
|
|
13
|
+
const begun = new Map<string, number>();
|
|
14
|
+
const passed = new Map<string, number[]>();
|
|
15
|
+
const executeSql = db.executeSql.bind(db);
|
|
16
|
+
db.executeSql = async (text: string, values?: unknown[]) => {
|
|
17
|
+
const queue = claim.test(text) ? claimedQueue.exec(text)?.groups?.queue : undefined;
|
|
18
|
+
if (queue === undefined) {
|
|
19
|
+
return executeSql(text, values);
|
|
20
|
+
}
|
|
21
|
+
const ordinal = (begun.get(queue) ?? 0) + 1;
|
|
22
|
+
begun.set(queue, ordinal);
|
|
23
|
+
try {
|
|
24
|
+
return await executeSql(text, values);
|
|
25
|
+
} finally {
|
|
26
|
+
const ordinals = passed.get(queue) ?? [];
|
|
27
|
+
ordinals.push(ordinal);
|
|
28
|
+
passed.set(queue, ordinals);
|
|
29
|
+
changed();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
begun: (queue) => begun.get(queue) ?? 0,
|
|
34
|
+
passedSince: (queue, mark) => (passed.get(queue) ?? []).filter((ordinal) => ordinal > mark).length,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -24,7 +24,14 @@ function settleAttempt(
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const interruptAt = Effect.fnUntraced(function* (deadline: number) {
|
|
28
|
+
let remaining = deadline - (yield* Clock.currentTimeMillis);
|
|
29
|
+
while (remaining > 0) {
|
|
30
|
+
yield* Effect.sleep(remaining);
|
|
31
|
+
remaining = deadline - (yield* Clock.currentTimeMillis);
|
|
32
|
+
}
|
|
33
|
+
return yield* Effect.interrupt;
|
|
34
|
+
});
|
|
28
35
|
|
|
29
36
|
function aborted(signal: AbortSignal) {
|
|
30
37
|
return Effect.callback<never>((resume) => {
|
|
@@ -40,11 +47,11 @@ function aborted(signal: AbortSignal) {
|
|
|
40
47
|
});
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
export function batchHandler(
|
|
50
|
+
export function batchHandler<R>(
|
|
44
51
|
client: PgBossClient,
|
|
45
52
|
name: string,
|
|
46
53
|
schema: string,
|
|
47
|
-
run: (job: Job<unknown>, deadline: number) => Effect.Effect<unknown, unknown>,
|
|
54
|
+
run: (job: Job<unknown>, deadline: number) => Effect.Effect<unknown, unknown, R>,
|
|
48
55
|
afterFailure: (job: Job<unknown>) => Effect.Effect<void, unknown> = () => Effect.void,
|
|
49
56
|
) {
|
|
50
57
|
const complete = client.complete?.bind(client);
|
|
@@ -61,11 +68,7 @@ export function batchHandler(
|
|
|
61
68
|
if (remaining <= 0) {
|
|
62
69
|
return "expired";
|
|
63
70
|
}
|
|
64
|
-
const exit = yield* run(job, deadline).pipe(
|
|
65
|
-
Effect.timeoutOrElse({ duration: remaining, orElse: () => Effect.interrupt }),
|
|
66
|
-
Effect.raceFirst(aborted(job.signal)),
|
|
67
|
-
Effect.exit,
|
|
68
|
-
);
|
|
71
|
+
const exit = yield* run(job, deadline).pipe(Effect.raceFirst(interruptAt(deadline)), Effect.raceFirst(aborted(job.signal)), Effect.exit);
|
|
69
72
|
if (job.signal.aborted || (Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause))) {
|
|
70
73
|
return "interrupted";
|
|
71
74
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { quotedSchema } from "#internal/storedRows.ts";
|
|
2
|
+
|
|
3
|
+
export function deadLetterResolutionPlan(schema: string): string {
|
|
4
|
+
const quoted = quotedSchema(schema);
|
|
5
|
+
const jobTable = `${quoted}.job`.replaceAll("'", "''");
|
|
6
|
+
const body = `BEGIN
|
|
7
|
+
PERFORM pg_advisory_xact_lock(hashtextextended('${jobTable}.effect_dead_letter_resolution', 0));
|
|
8
|
+
CREATE OR REPLACE FUNCTION ${quoted}.effect_dead_letter_resolution() RETURNS trigger LANGUAGE plpgsql AS $resolve$
|
|
9
|
+
BEGIN
|
|
10
|
+
DELETE FROM ${quoted}.job
|
|
11
|
+
WHERE name = NEW.name || '-dlq' AND source_name = NEW.name AND source_id = NEW.id
|
|
12
|
+
AND source_created_on = NEW.created_on;
|
|
13
|
+
RETURN NEW;
|
|
14
|
+
END
|
|
15
|
+
$resolve$;
|
|
16
|
+
IF NOT EXISTS (
|
|
17
|
+
SELECT 1 FROM pg_trigger WHERE tgrelid = '${jobTable}'::regclass AND tgname = 'effect_dead_letter_resolution'
|
|
18
|
+
) THEN
|
|
19
|
+
CREATE TRIGGER effect_dead_letter_resolution AFTER UPDATE OF state ON ${quoted}.job
|
|
20
|
+
FOR EACH ROW WHEN (NEW.state = 'completed' AND OLD.state IS DISTINCT FROM NEW.state)
|
|
21
|
+
EXECUTE FUNCTION ${quoted}.effect_dead_letter_resolution();
|
|
22
|
+
DELETE FROM ${quoted}.job AS copy USING ${quoted}.job AS source
|
|
23
|
+
WHERE source.state = 'completed' AND copy.name = source.name || '-dlq'
|
|
24
|
+
AND copy.source_name = source.name AND copy.source_id = source.id AND copy.source_created_on = source.created_on;
|
|
25
|
+
END IF;
|
|
26
|
+
END`;
|
|
27
|
+
return `DO '${body.replaceAll("'", "''")}'`;
|
|
28
|
+
}
|
|
@@ -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
|
});
|
|
@@ -13,7 +13,7 @@ export function makeService(client: PgBossClient, names: readonly string[], sche
|
|
|
13
13
|
enqueue: enqueueFor(client),
|
|
14
14
|
failedJobs: failedJobsFor(client, schema),
|
|
15
15
|
getJob: getJobFor(client),
|
|
16
|
-
health: () => healthFor(client, names),
|
|
16
|
+
health: (queues) => healthFor(client, queues === undefined ? names : queues.map((queue) => queue.name)),
|
|
17
17
|
retry: retryFor(client),
|
|
18
18
|
};
|
|
19
19
|
}
|