@shivaedev/effect-pg-boss 0.4.0 → 0.6.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 +23 -0
- package/README.md +57 -12
- package/dist/{client.d.ts → server/client.d.ts} +7 -1
- package/dist/server/client.d.ts.map +1 -0
- package/dist/server/client.js.map +1 -0
- package/dist/{definition.d.ts → server/definition.d.ts} +6 -6
- package/dist/server/definition.d.ts.map +1 -0
- package/dist/server/definition.js.map +1 -0
- package/dist/{error.d.ts → server/error.d.ts} +10 -1
- package/dist/server/error.d.ts.map +1 -0
- package/dist/{error.js → server/error.js} +5 -0
- package/dist/server/error.js.map +1 -0
- package/dist/{health.d.ts → server/health.d.ts} +6 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/internal/countsFor.d.ts +5 -0
- package/dist/server/internal/countsFor.d.ts.map +1 -0
- package/dist/server/internal/countsFor.js +27 -0
- package/dist/server/internal/countsFor.js.map +1 -0
- package/dist/server/internal/ensureQueue.d.ts +4 -0
- package/dist/server/internal/ensureQueue.d.ts.map +1 -0
- package/dist/server/internal/ensureQueue.js +27 -0
- package/dist/server/internal/ensureQueue.js.map +1 -0
- package/dist/server/internal/health.d.ts.map +1 -0
- package/dist/server/internal/health.js.map +1 -0
- package/dist/server/internal/jobControl.d.ts +5 -0
- package/dist/server/internal/jobControl.d.ts.map +1 -0
- package/dist/server/internal/jobControl.js +34 -0
- package/dist/server/internal/jobControl.js.map +1 -0
- package/dist/server/internal/lifecycle.d.ts.map +1 -0
- package/dist/server/internal/lifecycle.js.map +1 -0
- package/dist/{internal → server/internal}/register.d.ts +2 -1
- package/dist/server/internal/register.d.ts.map +1 -0
- package/dist/{internal → server/internal}/register.js +21 -28
- package/dist/server/internal/register.js.map +1 -0
- package/dist/{internal → server/internal}/service.d.ts +1 -1
- package/dist/server/internal/service.d.ts.map +1 -0
- package/dist/{internal → server/internal}/service.js +7 -2
- package/dist/server/internal/service.js.map +1 -0
- package/dist/{service.d.ts → server/service.d.ts} +8 -5
- package/dist/server/service.d.ts.map +1 -0
- package/dist/{service.js → server/service.js} +1 -1
- package/dist/server/service.js.map +1 -0
- package/docs/north-star.md +1 -1
- package/docs/roadmap.md +9 -3
- package/package.json +14 -14
- package/src/{client.ts → server/client.ts} +17 -1
- package/src/{definition.ts → server/definition.ts} +6 -6
- package/src/{error.ts → server/error.ts} +10 -1
- package/src/{health.ts → server/health.ts} +7 -0
- package/src/server/internal/countsFor.ts +33 -0
- package/src/server/internal/ensureQueue.ts +29 -0
- package/src/server/internal/jobControl.ts +46 -0
- package/src/{internal → server/internal}/register.ts +29 -31
- package/src/{internal → server/internal}/service.ts +7 -2
- package/src/{service.ts → server/service.ts} +13 -7
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/definition.d.ts.map +0 -1
- package/dist/definition.js.map +0 -1
- package/dist/error.d.ts.map +0 -1
- package/dist/error.js.map +0 -1
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js.map +0 -1
- package/dist/internal/health.d.ts.map +0 -1
- package/dist/internal/health.js.map +0 -1
- package/dist/internal/lifecycle.d.ts.map +0 -1
- package/dist/internal/lifecycle.js.map +0 -1
- package/dist/internal/register.d.ts.map +0 -1
- package/dist/internal/register.js.map +0 -1
- package/dist/internal/service.d.ts.map +0 -1
- package/dist/internal/service.js.map +0 -1
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js.map +0 -1
- /package/dist/{client.js → server/client.js} +0 -0
- /package/dist/{definition.js → server/definition.js} +0 -0
- /package/dist/{health.js → server/health.js} +0 -0
- /package/dist/{internal → server/internal}/health.d.ts +0 -0
- /package/dist/{internal → server/internal}/health.js +0 -0
- /package/dist/{internal → server/internal}/lifecycle.d.ts +0 -0
- /package/dist/{internal → server/internal}/lifecycle.js +0 -0
- /package/src/{internal → server/internal}/health.ts +0 -0
- /package/src/{internal → server/internal}/lifecycle.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { PgBossClient } from "#client.ts";
|
|
2
2
|
import type { PgBossService } from "#service.ts";
|
|
3
|
-
export declare const makeService: (client: PgBossClient, names: readonly string[]) => PgBossService;
|
|
3
|
+
export declare const makeService: (client: PgBossClient, names: readonly string[], schema?: string) => PgBossService;
|
|
4
4
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/server/internal/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,eAAO,MAAM,WAAW,WAAY,YAAY,SAAS,SAAS,MAAM,EAAE,sBAAsB,aA+B9F,CAAC"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
2
|
import { PgBossPayloadError, toPgBossError } from "#error.ts";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { countsFor } from "#internal/countsFor.ts";
|
|
4
|
+
import { healthFor } from "#internal/health.ts";
|
|
5
|
+
import { getJobFor, retryFor } from "#internal/jobControl.ts";
|
|
6
|
+
export const makeService = (client, names, schema = "pgboss") => ({
|
|
7
|
+
counts: (queue) => countsFor(client, schema, queue.name),
|
|
5
8
|
enqueue: (queue, payload, options) => Schema.encodeUnknownEffect(queue.schema)(payload).pipe(Effect.mapError((error) => new PgBossPayloadError({
|
|
6
9
|
direction: "encode",
|
|
7
10
|
original: Redacted.make(error),
|
|
@@ -19,6 +22,8 @@ export const makeService = (client, names) => ({
|
|
|
19
22
|
try: () => client.send(queue.name, encoded, options),
|
|
20
23
|
}).pipe(Effect.map(Option.fromNullishOr));
|
|
21
24
|
})),
|
|
25
|
+
getJob: getJobFor(client),
|
|
22
26
|
health: healthFor(client, names),
|
|
27
|
+
retry: retryFor(client),
|
|
23
28
|
});
|
|
24
29
|
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/server/internal/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAoB,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAG9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAoB,EAAE,KAAwB,EAAE,MAAM,GAAG,QAAQ,EAAiB,EAAE,CAAC,CAAC;IACjH,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC;IACxD,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CACpC,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,QAAQ,CACd,CAAC,KAAK,EAAE,EAAE,CACT,IAAI,kBAAkB,CAAC;QACtB,SAAS,EAAE,QAAQ;QACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QAC9B,KAAK,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CACH,EACD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAA0E,EAAE;QAClG,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrD,OAAO,MAAM,CAAC,IAAI,CACjB,IAAI,kBAAkB,CAAC;gBACtB,SAAS,EAAE,QAAQ;gBACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;gBACjF,KAAK,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC,CACF,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,UAAU,CAAC;YACxB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC;YAC7D,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC;SACpD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CACF;IACF,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;IACzB,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC;IAChC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC;CACvB,CAAC,CAAC"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { Context, Effect, Layer, type Option, type Schema } from "effect";
|
|
2
|
-
import type { ConstructorOptions, SendOptions, StopOptions } from "pg-boss";
|
|
2
|
+
import type { ConstructorOptions, JobWithMetadata, SendOptions, StopOptions } from "pg-boss";
|
|
3
3
|
import type { PgBossClientFactory } from "#client.ts";
|
|
4
|
-
import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "
|
|
5
|
-
import type { PgBossError, PgBossPayloadError } from "
|
|
6
|
-
import type { JobsHealth } from "
|
|
4
|
+
import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "#definition.ts";
|
|
5
|
+
import type { PgBossError, PgBossPayloadError, PgBossQueueOptionsError } from "#error.ts";
|
|
6
|
+
import type { JobsHealth, QueueCounts } from "#health.ts";
|
|
7
7
|
export interface PgBossService {
|
|
8
|
+
readonly counts: (queue: QueueDefinition) => Effect.Effect<QueueCounts, PgBossError>;
|
|
8
9
|
readonly enqueue: <const TName extends string, const TPayload extends JobPayloadSchema>(queue: QueueDefinition<TName, TPayload>, payload: Schema.Schema.Type<TPayload>, options?: SendOptions) => Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError, TPayload["EncodingServices"]>;
|
|
10
|
+
readonly getJob: <const TName extends string, const TPayload extends JobPayloadSchema>(queue: QueueDefinition<TName, TPayload>, id: string) => Effect.Effect<Option.Option<JobWithMetadata<TPayload["Type"]>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
|
|
9
11
|
readonly health: Effect.Effect<JobsHealth, PgBossError>;
|
|
12
|
+
readonly retry: (queue: QueueDefinition, id: string) => Effect.Effect<void, PgBossError>;
|
|
10
13
|
}
|
|
11
14
|
export type PgBossLayerOptions<TRegistrations extends readonly JobRegistration[], TErrorRequirements> = ConstructorOptions & {
|
|
12
15
|
readonly clientFactory?: PgBossClientFactory;
|
|
@@ -16,7 +19,7 @@ export type PgBossLayerOptions<TRegistrations extends readonly JobRegistration[]
|
|
|
16
19
|
readonly stop?: StopOptions;
|
|
17
20
|
};
|
|
18
21
|
export interface PgBossDefinition<TIdentifier extends string = string> extends Context.Service<TIdentifier, PgBossService> {
|
|
19
|
-
readonly layer: <const TRegistrations extends readonly JobRegistration[], TErrorRequirements = never>(options: PgBossLayerOptions<TRegistrations, TErrorRequirements>) => Layer.Layer<TIdentifier, PgBossError, RegistrationRequirements<TRegistrations> | TErrorRequirements>;
|
|
22
|
+
readonly layer: <const TRegistrations extends readonly JobRegistration[], TErrorRequirements = never>(options: PgBossLayerOptions<TRegistrations, TErrorRequirements>) => Layer.Layer<TIdentifier, PgBossError | PgBossQueueOptionsError, RegistrationRequirements<TRegistrations> | TErrorRequirements>;
|
|
20
23
|
}
|
|
21
24
|
export declare const makePgBoss: <const TIdentifier extends string>(identifier: TIdentifier) => PgBossDefinition<TIdentifier>;
|
|
22
25
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/server/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,7 +31,7 @@ export const makePgBoss = (identifier) => {
|
|
|
31
31
|
resource.acquired.client.off("error", resource.errorListener);
|
|
32
32
|
return releaseClient(resource.acquired, stop);
|
|
33
33
|
});
|
|
34
|
-
return Layer.effect(Service, Effect.map(acquire, ({ acquired }) => makeService(acquired.client, names)));
|
|
34
|
+
return Layer.effect(Service, Effect.map(acquire, ({ acquired }) => makeService(acquired.client, names, constructorOptions.schema)));
|
|
35
35
|
};
|
|
36
36
|
return Object.assign(Service, { layer });
|
|
37
37
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/server/service.ts"],"names":[],"mappings":"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"}
|
package/docs/north-star.md
CHANGED
|
@@ -43,6 +43,6 @@ An application that needs only queueing should not import a repository framework
|
|
|
43
43
|
- Domain workers, authorization rules, external-provider interfaces and application logging or health policy.
|
|
44
44
|
- Another general service declaration system; native Context and Layer stay available, and general declaration ergonomics belong to `effect-service`.
|
|
45
45
|
- Application repositories or migration ownership; those belong to `effect-sql`.
|
|
46
|
-
-
|
|
46
|
+
- Transactional enqueueing: jobs use a separate connection from application SQL transactions. Applications own any required outbox policy; after-commit changes have their own path through `effect-changes`.
|
|
47
47
|
- A promise of exactly-once domain side effects or provider idempotency. Those need application-specific decisions and evidence.
|
|
48
48
|
- Browser, device or offline synchronization behavior. A PostgreSQL queue boundary cannot establish those product contracts.
|
package/docs/roadmap.md
CHANGED
|
@@ -8,9 +8,13 @@ This file owns the package's implementation inventory and open API choices. The
|
|
|
8
8
|
- [x] Object payload Schema definitions, decoded enqueue types and queue workers. Type tests reject primitive schemas, missing fields and encoded values passed as decoded payloads.
|
|
9
9
|
- [x] Encoding at enqueue and decoding before the handler. The PostgreSQL test round-trips a transformed payload and checks that malformed stored data reaches the dead-letter queue without calling the domain handler.
|
|
10
10
|
- [x] Queue and schedule registration, with dead-letter queues, default retry settings and UTC schedule registration. Fake-client tests verify the supplied settings and a retry-limit override.
|
|
11
|
+
- [x] Producer-only queue definitions create queues and dead-letter queues through `Jobs.layer({ jobs: [Queue] })`, without starting a consumer. The [PostgreSQL producer test](../src/server/producerPostgres.spec.ts) enqueues a transformed payload and observes worker retry exhaustion and dead-letter delivery. The [starter producer](../../../examples/starter/src/server/noteProducer.ts) uses this API.
|
|
11
12
|
- [x] Worker and scheduled Effect service capture. Runtime and packed-consumer type tests preserve handler requirements.
|
|
12
13
|
- [x] Scoped startup and shutdown, including cleanup when startup or registration fails.
|
|
13
14
|
- [x] Explicit keyed client reuse and unkeyed per-build acquisition. Lifecycle tests cover two builds sharing one client and replacing the registered worker.
|
|
15
|
+
- [x] Full pg-boss queue creation options, excluding the adapter's name and dead-letter setup. [PostgreSQL evidence](../src/server/notifyPostgres.spec.ts) verifies notification delivery to a waiting worker and immediate live counts after completion; [type tests](../src/server/service.typecheck.test.ts) enforce option ownership.
|
|
16
|
+
- [x] Schema-decoded lookup by job ID and retry under the same ID for failed or retry-waiting jobs. [PostgreSQL evidence](../src/server/jobControlPostgres.spec.ts) verifies both states complete after a manual retry.
|
|
17
|
+
- [x] Startup reconciles declared mutable queue settings and rejects immutable policy or partition changes with a typed error. [PostgreSQL restart evidence](../src/server/queueOptionsPostgres.spec.ts) checks expiry and retry-limit changes on an existing queue.
|
|
14
18
|
- [x] Queue health totals for active, failed, queued, ready and dead-lettered counts, verified with a populated fake queue.
|
|
15
19
|
- [x] Public defining-module imports and an installed-package type fixture.
|
|
16
20
|
|
|
@@ -18,9 +22,11 @@ This file owns the package's implementation inventory and open API choices. The
|
|
|
18
22
|
|
|
19
23
|
No further package API expansion is selected. Let the framework's jobs integration work identify the next package change, and preserve the existing payload and lifecycle contracts while it does. That integration's status stays in the framework roadmap.
|
|
20
24
|
|
|
25
|
+
## Decisions
|
|
26
|
+
|
|
27
|
+
Enqueueing is separate from application SQL transactions. The package supplies no transactional enqueue adapter. Applications use transactions for database invariants and handle queue errors without failing those transactions. The starter enqueues after its note write transaction and logs queue failures; its [PostgreSQL queue-failure story](../../../examples/starter/src/server/queueFailure.spec.ts) proves that rejected enqueueing leaves create and edit writes saved. Its [rollback story](../../../examples/starter/src/server/committedJobs.spec.ts) also proves that an independently enqueued job survives an outer application rollback.
|
|
28
|
+
|
|
21
29
|
## Open questions
|
|
22
30
|
|
|
23
|
-
- Which application boundary should choose how a database write becomes a durable job: an explicit after-commit action, an outbox, or another demonstrated approach? The enqueue API is independent of application SQL; no transaction coupling is selected.
|
|
24
31
|
- Should a shared client key be checked against constructor settings and registration changes? The lifecycle fixture uses the same Layer twice; it does not settle incompatible configurations under one key.
|
|
25
|
-
- Which public hooks need dedicated regression coverage before the guide makes stronger claims? Error-event reporting, cancellation through a job's AbortSignal, a declined send result, multi-queue health and actual cron
|
|
26
|
-
- Should the package grow only when an application's integration needs a helper, or is a wider jobs API intended? The package has no in-repo application consumer to settle that question.
|
|
32
|
+
- Which public hooks need dedicated regression coverage before the guide makes stronger claims? Error-event reporting, cancellation through a job's AbortSignal, a declined send result, multi-queue health and actual cron execution are implemented boundaries or engine behaviors without focused evidence here.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-pg-boss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Effect-native jobs and lifecycle management for pg-boss",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-pg-boss#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"imports": {
|
|
18
18
|
"#test/*": "./src/test-support/*",
|
|
19
19
|
"#*.ts": {
|
|
20
|
-
"source": "./src/*.ts",
|
|
21
|
-
"types": "./dist/*.d.ts",
|
|
22
|
-
"default": "./dist/*.js"
|
|
20
|
+
"source": "./src/server/*.ts",
|
|
21
|
+
"types": "./dist/server/*.d.ts",
|
|
22
|
+
"default": "./dist/server/*.js"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
26
|
"./*.ts": {
|
|
27
|
-
"types": "./dist/*.d.ts",
|
|
28
|
-
"source": "./src/*.ts",
|
|
29
|
-
"import": "./dist/*.js",
|
|
30
|
-
"default": "./dist/*.js"
|
|
27
|
+
"types": "./dist/server/*.d.ts",
|
|
28
|
+
"source": "./src/server/*.ts",
|
|
29
|
+
"import": "./dist/server/*.js",
|
|
30
|
+
"default": "./dist/server/*.js"
|
|
31
31
|
},
|
|
32
32
|
"./internal/*": null,
|
|
33
33
|
"./package.json": "./package.json"
|
|
@@ -43,17 +43,17 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@shivaedev/quality": "0.
|
|
46
|
+
"@shivaedev/quality": "0.22.0",
|
|
47
47
|
"@types/node": "24.19.1",
|
|
48
48
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
49
|
-
"effect": "4.0.
|
|
50
|
-
"pg-boss": "12.
|
|
49
|
+
"effect": "4.0.4",
|
|
50
|
+
"pg-boss": "12.37.0",
|
|
51
51
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
52
|
-
"vitest": "
|
|
52
|
+
"vitest": "5.0.3"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"effect": "4.0.
|
|
56
|
-
"pg-boss": "12.
|
|
55
|
+
"effect": "4.0.4",
|
|
56
|
+
"pg-boss": "12.37.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=24"
|
|
@@ -1,16 +1,32 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ConstructorOptions,
|
|
3
|
+
FindJobsOptions,
|
|
4
|
+
Job,
|
|
5
|
+
JobWithMetadata,
|
|
6
|
+
Queue,
|
|
7
|
+
QueueResult,
|
|
8
|
+
ScheduleOptions,
|
|
9
|
+
SendOptions,
|
|
10
|
+
StopOptions,
|
|
11
|
+
WorkOptions,
|
|
12
|
+
} from "pg-boss";
|
|
2
13
|
import { PgBoss } from "pg-boss";
|
|
3
14
|
|
|
4
15
|
export interface PgBossClient {
|
|
5
16
|
readonly createQueue: (name: string, options?: Omit<Queue, "name">) => Promise<void>;
|
|
17
|
+
readonly findJobs?: (name: string, options?: FindJobsOptions) => Promise<readonly JobWithMetadata<unknown>[]>;
|
|
18
|
+
readonly getDb?: PgBoss["getDb"];
|
|
6
19
|
readonly getQueue: (name: string) => Promise<QueueResult | null>;
|
|
7
20
|
readonly off: (event: "error", listener: (error: Error) => void) => unknown;
|
|
8
21
|
readonly offWork: (name: string) => Promise<void>;
|
|
9
22
|
readonly on: (event: "error", listener: (error: Error) => void) => unknown;
|
|
23
|
+
readonly retry?: PgBoss["retry"];
|
|
10
24
|
readonly schedule: (name: string, cron: string, data?: object | null, options?: ScheduleOptions) => Promise<void>;
|
|
11
25
|
readonly send: (name: string, data?: object | null, options?: SendOptions) => Promise<string | null>;
|
|
12
26
|
readonly start: () => Promise<unknown>;
|
|
13
27
|
readonly stop: (options?: StopOptions) => Promise<void>;
|
|
28
|
+
readonly update?: PgBoss["update"];
|
|
29
|
+
readonly updateQueue?: PgBoss["updateQueue"];
|
|
14
30
|
readonly work: (name: string, options: WorkOptions, handler: (jobs: readonly Job<unknown>[]) => Promise<unknown>) => Promise<string>;
|
|
15
31
|
}
|
|
16
32
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Effect, Schema } from "effect";
|
|
2
|
-
import type { Job,
|
|
2
|
+
import type { Job, Queue, ScheduleOptions, WorkOptions } from "pg-boss";
|
|
3
3
|
|
|
4
4
|
export interface JobPayloadSchema extends Schema.Top {
|
|
5
5
|
readonly Encoded: object;
|
|
@@ -20,7 +20,7 @@ export interface QueueDefinition<Name extends string = string, Payload extends J
|
|
|
20
20
|
handler: (payload: Schema.Schema.Type<Payload>, job: JobContext) => Effect.Effect<unknown, E, R>,
|
|
21
21
|
) => QueueWorker<Payload, R>;
|
|
22
22
|
readonly name: Name;
|
|
23
|
-
readonly queueOptions: Omit<
|
|
23
|
+
readonly queueOptions: Omit<Queue, "name" | "deadLetter">;
|
|
24
24
|
readonly schema: Payload;
|
|
25
25
|
readonly workerOptions: WorkOptions;
|
|
26
26
|
}
|
|
@@ -32,14 +32,14 @@ export interface QueueWorker<Payload extends JobPayloadSchema = JobPayloadSchema
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface JobRegistration {
|
|
35
|
-
readonly _tag: "QueueWorker" | "ScheduledWorker";
|
|
35
|
+
readonly _tag: "QueueDefinition" | "QueueWorker" | "ScheduledWorker";
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface ScheduleDefinition<Name extends string = string> {
|
|
39
39
|
readonly _tag: "ScheduleDefinition";
|
|
40
40
|
readonly cron: string;
|
|
41
41
|
readonly name: Name;
|
|
42
|
-
readonly queueOptions: Omit<
|
|
42
|
+
readonly queueOptions: Omit<Queue, "name" | "deadLetter">;
|
|
43
43
|
readonly run: <E, R>(effect: Effect.Effect<unknown, E, R>) => ScheduledWorker<R>;
|
|
44
44
|
readonly scheduleOptions: ScheduleOptions;
|
|
45
45
|
readonly workerOptions: WorkOptions;
|
|
@@ -61,7 +61,7 @@ export type RegistrationRequirements<Registrations extends readonly JobRegistrat
|
|
|
61
61
|
|
|
62
62
|
export interface DefineQueueOptions<Name extends string, Payload extends JobPayloadSchema> {
|
|
63
63
|
readonly name: Name;
|
|
64
|
-
readonly queue?: Omit<
|
|
64
|
+
readonly queue?: Omit<Queue, "name" | "deadLetter">;
|
|
65
65
|
readonly schema: Payload;
|
|
66
66
|
readonly worker?: WorkOptions;
|
|
67
67
|
}
|
|
@@ -88,7 +88,7 @@ export const defineQueue = <const Name extends string, const Payload extends Job
|
|
|
88
88
|
export interface DefineScheduleOptions<Name extends string> {
|
|
89
89
|
readonly cron: string;
|
|
90
90
|
readonly name: Name;
|
|
91
|
-
readonly queue?: Omit<
|
|
91
|
+
readonly queue?: Omit<Queue, "name" | "deadLetter">;
|
|
92
92
|
readonly schedule?: ScheduleOptions;
|
|
93
93
|
readonly worker?: WorkOptions;
|
|
94
94
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Data, Redacted } from "effect";
|
|
2
2
|
|
|
3
|
-
export type PgBossOperation = "enqueue" | "health" | "register" | "start" | "stop";
|
|
3
|
+
export type PgBossOperation = "counts" | "enqueue" | "getJob" | "health" | "register" | "retry" | "start" | "stop";
|
|
4
4
|
|
|
5
5
|
export class PgBossError extends Data.TaggedError("PgBossError")<{
|
|
6
6
|
readonly operation: PgBossOperation;
|
|
@@ -8,6 +8,15 @@ export class PgBossError extends Data.TaggedError("PgBossError")<{
|
|
|
8
8
|
readonly original: Redacted.Redacted<unknown>;
|
|
9
9
|
}> {}
|
|
10
10
|
|
|
11
|
+
export class PgBossQueueOptionsError extends Data.TaggedError("PgBossQueueOptionsError")<{
|
|
12
|
+
readonly queue: string;
|
|
13
|
+
readonly options: readonly ("partition" | "policy")[];
|
|
14
|
+
}> {
|
|
15
|
+
override get message(): string {
|
|
16
|
+
return `Queue ${this.queue} cannot change options after creation: ${this.options.join(", ")}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
export class PgBossPayloadError extends Data.TaggedError("PgBossPayloadError")<{
|
|
12
21
|
readonly direction: "decode" | "encode";
|
|
13
22
|
readonly queue: string;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export interface QueueCounts {
|
|
2
|
+
readonly activeCount: number;
|
|
3
|
+
readonly failedCount: number;
|
|
4
|
+
readonly queuedCount: number;
|
|
5
|
+
readonly totalCount: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
export interface QueueHealth {
|
|
2
9
|
readonly activeCount: number;
|
|
3
10
|
readonly deadLetteredCount: number;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import type { PgBossClient } from "#client.ts";
|
|
3
|
+
import { toPgBossError } from "#error.ts";
|
|
4
|
+
import type { QueueCounts } from "#health.ts";
|
|
5
|
+
|
|
6
|
+
const Counts = Schema.Struct({
|
|
7
|
+
activeCount: Schema.Number,
|
|
8
|
+
failedCount: Schema.Number,
|
|
9
|
+
queuedCount: Schema.Number,
|
|
10
|
+
totalCount: Schema.Number,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export function countsFor(client: PgBossClient, schema: string, name: string): Effect.Effect<QueueCounts, import("#error.ts").PgBossError> {
|
|
14
|
+
return Effect.tryPromise({
|
|
15
|
+
catch: (error) => toPgBossError("counts", error, name),
|
|
16
|
+
try: async () => {
|
|
17
|
+
if (client.getDb === undefined) {
|
|
18
|
+
throw new TypeError("This pg-boss client does not support getDb");
|
|
19
|
+
}
|
|
20
|
+
const quotedSchema = `"${schema.replaceAll('"', '""')}"`;
|
|
21
|
+
const { rows } = await client.getDb().executeSql(
|
|
22
|
+
`SELECT
|
|
23
|
+
count(*) FILTER (WHERE state = 'active')::int AS "activeCount",
|
|
24
|
+
count(*) FILTER (WHERE state = 'failed')::int AS "failedCount",
|
|
25
|
+
count(*) FILTER (WHERE state IN ('created', 'retry'))::int AS "queuedCount",
|
|
26
|
+
count(*)::int AS "totalCount"
|
|
27
|
+
FROM ${quotedSchema}.job WHERE name = $1`,
|
|
28
|
+
[name],
|
|
29
|
+
);
|
|
30
|
+
return Schema.decodeUnknownPromise(Counts)(rows[0]);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Queue } from "pg-boss";
|
|
2
|
+
import type { PgBossClient } from "#client.ts";
|
|
3
|
+
import { PgBossQueueOptionsError } from "#error.ts";
|
|
4
|
+
import { deadLetterQueueName } from "#health.ts";
|
|
5
|
+
|
|
6
|
+
export async function ensureQueue(client: PgBossClient, name: string, options: Omit<Queue, "name" | "deadLetter">): Promise<void> {
|
|
7
|
+
const deadLetter = deadLetterQueueName(name);
|
|
8
|
+
const desired = { retryBackoff: true, retryLimit: 3, ...options, deadLetter };
|
|
9
|
+
await client.createQueue(deadLetter);
|
|
10
|
+
await client.createQueue(name, desired);
|
|
11
|
+
const existing = await client.getQueue(name);
|
|
12
|
+
if (existing === null) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const immutable = (["partition", "policy"] as const).filter((key) => desired[key] !== undefined && desired[key] !== existing[key]);
|
|
16
|
+
if (immutable.length > 0) {
|
|
17
|
+
throw new PgBossQueueOptionsError({ options: immutable, queue: name });
|
|
18
|
+
}
|
|
19
|
+
const { partition: _partition, policy: _policy, ...mutable } = desired;
|
|
20
|
+
const changed = Object.entries(mutable).some(([key, value]) => value !== undefined && value !== Reflect.get(existing, key));
|
|
21
|
+
if (!changed) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (client.updateQueue === undefined) {
|
|
25
|
+
throw new TypeError("This pg-boss client does not support updateQueue");
|
|
26
|
+
}
|
|
27
|
+
// pg-boss validates retryDelayMax against retryBackoff in the same update.
|
|
28
|
+
await client.updateQueue(name, mutable);
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
|
+
import type { PgBossClient } from "#client.ts";
|
|
3
|
+
import { PgBossPayloadError, toPgBossError } from "#error.ts";
|
|
4
|
+
import type { PgBossService } from "#service.ts";
|
|
5
|
+
|
|
6
|
+
export function getJobFor(client: PgBossClient): PgBossService["getJob"] {
|
|
7
|
+
return (queue, id) =>
|
|
8
|
+
Effect.tryPromise({
|
|
9
|
+
catch: (error) => toPgBossError("getJob", error, queue.name),
|
|
10
|
+
try: async () => {
|
|
11
|
+
if (client.findJobs === undefined) {
|
|
12
|
+
throw new TypeError("This pg-boss client does not support findJobs");
|
|
13
|
+
}
|
|
14
|
+
return (await client.findJobs(queue.name, { id }))[0];
|
|
15
|
+
},
|
|
16
|
+
}).pipe(
|
|
17
|
+
Effect.flatMap((job) =>
|
|
18
|
+
job === undefined
|
|
19
|
+
? Effect.succeed(Option.none())
|
|
20
|
+
: Schema.decodeUnknownEffect(queue.schema)(job.data).pipe(
|
|
21
|
+
Effect.mapError((error) => new PgBossPayloadError({ direction: "decode", original: Redacted.make(error), queue: queue.name })),
|
|
22
|
+
Effect.map((data) => Option.some({ ...job, data })),
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function retryFor(client: PgBossClient): PgBossService["retry"] {
|
|
29
|
+
return (queue, id) =>
|
|
30
|
+
Effect.tryPromise({
|
|
31
|
+
catch: (error) => toPgBossError("retry", error, queue.name),
|
|
32
|
+
try: async () => {
|
|
33
|
+
if (client.findJobs === undefined || client.retry === undefined || client.update === undefined) {
|
|
34
|
+
throw new TypeError("This pg-boss client does not support findJobs, retry and update");
|
|
35
|
+
}
|
|
36
|
+
const job = (await client.findJobs(queue.name, { id }))[0];
|
|
37
|
+
if (job?.state !== "failed" && job?.state !== "retry") {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (job.state === "failed") {
|
|
41
|
+
await client.retry(queue.name, id);
|
|
42
|
+
}
|
|
43
|
+
await client.update(queue.name, undefined, { id, startAfter: 0 });
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { type Context, Effect, Redacted, Schema } from "effect";
|
|
2
2
|
import type { Job } from "pg-boss";
|
|
3
3
|
import type { PgBossClient } from "#client.ts";
|
|
4
|
-
import { type JobRegistration, jobContext, type QueueWorker, type ScheduledWorker } from "#definition.ts";
|
|
5
|
-
import { PgBossPayloadError, toPgBossError } from "#error.ts";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
function queueOptions(options: Readonly<Record<string, unknown>>) {
|
|
9
|
-
return {
|
|
10
|
-
retryBackoff: true,
|
|
11
|
-
retryLimit: 3,
|
|
12
|
-
...options,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
4
|
+
import { type JobRegistration, jobContext, type QueueDefinition, type QueueWorker, type ScheduledWorker } from "#definition.ts";
|
|
5
|
+
import { PgBossPayloadError, PgBossQueueOptionsError, toPgBossError } from "#error.ts";
|
|
6
|
+
import { ensureQueue } from "#internal/ensureQueue.ts";
|
|
15
7
|
|
|
16
8
|
function isQueueWorker(registration: JobRegistration): registration is QueueWorker {
|
|
17
9
|
return registration._tag === "QueueWorker";
|
|
@@ -21,7 +13,14 @@ function isScheduledWorker(registration: JobRegistration): registration is Sched
|
|
|
21
13
|
return registration._tag === "ScheduledWorker";
|
|
22
14
|
}
|
|
23
15
|
|
|
16
|
+
function isQueueDefinition(registration: JobRegistration): registration is QueueDefinition {
|
|
17
|
+
return registration._tag === "QueueDefinition";
|
|
18
|
+
}
|
|
19
|
+
|
|
24
20
|
export const registrationName = (registration: JobRegistration): string => {
|
|
21
|
+
if (isQueueDefinition(registration)) {
|
|
22
|
+
return registration.name;
|
|
23
|
+
}
|
|
25
24
|
if (isQueueWorker(registration)) {
|
|
26
25
|
return registration.queue.name;
|
|
27
26
|
}
|
|
@@ -55,20 +54,24 @@ function runQueueWorker(worker: QueueWorker, job: Job<unknown>, context: Context
|
|
|
55
54
|
);
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
async function registerQueue(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
async function registerQueue(
|
|
58
|
+
client: PgBossClient,
|
|
59
|
+
registration: QueueWorker | QueueDefinition,
|
|
60
|
+
context: Context.Context<unknown>,
|
|
61
|
+
replaceWorker: boolean,
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
const queue = registration._tag === "QueueWorker" ? registration.queue : registration;
|
|
64
|
+
const name = queue.name;
|
|
65
|
+
if (replaceWorker && registration._tag === "QueueWorker") {
|
|
62
66
|
await client.offWork(name);
|
|
63
67
|
}
|
|
64
|
-
await client.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
await client.work(name, worker.queue.workerOptions, async (jobs) => {
|
|
68
|
+
await ensureQueue(client, name, queue.queueOptions);
|
|
69
|
+
if (registration._tag === "QueueDefinition") {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await client.work(name, queue.workerOptions, async (jobs) => {
|
|
70
73
|
for (const job of jobs) {
|
|
71
|
-
await runQueueWorker(
|
|
74
|
+
await runQueueWorker(registration, job, context);
|
|
72
75
|
}
|
|
73
76
|
});
|
|
74
77
|
}
|
|
@@ -80,15 +83,10 @@ async function registerSchedule(
|
|
|
80
83
|
replaceWorker: boolean,
|
|
81
84
|
): Promise<void> {
|
|
82
85
|
const { schedule } = worker;
|
|
83
|
-
const deadLetter = deadLetterQueueName(schedule.name);
|
|
84
86
|
if (replaceWorker) {
|
|
85
87
|
await client.offWork(schedule.name);
|
|
86
88
|
}
|
|
87
|
-
await client.
|
|
88
|
-
await client.createQueue(schedule.name, {
|
|
89
|
-
...queueOptions(schedule.queueOptions),
|
|
90
|
-
deadLetter,
|
|
91
|
-
});
|
|
89
|
+
await ensureQueue(client, schedule.name, schedule.queueOptions);
|
|
92
90
|
await client.work(schedule.name, schedule.workerOptions, async (jobs: readonly Job<unknown>[]) => {
|
|
93
91
|
for (const job of jobs) {
|
|
94
92
|
await Effect.runPromise(Effect.provide(worker.effect, context), {
|
|
@@ -112,14 +110,14 @@ export const registerJobs = <R>(
|
|
|
112
110
|
registrations: readonly JobRegistration[],
|
|
113
111
|
context: Context.Context<R>,
|
|
114
112
|
replaceWorkers: boolean,
|
|
115
|
-
): Effect.Effect<void, import("#error.ts").PgBossError> =>
|
|
113
|
+
): Effect.Effect<void, import("#error.ts").PgBossError | PgBossQueueOptionsError> =>
|
|
116
114
|
Effect.tryPromise({
|
|
117
|
-
catch: (error) => toPgBossError("register", error),
|
|
115
|
+
catch: (error) => (error instanceof PgBossQueueOptionsError ? error : toPgBossError("register", error)),
|
|
118
116
|
try: async () => {
|
|
119
117
|
registrationNames(registrations);
|
|
120
118
|
const provided = workerContext(context);
|
|
121
119
|
for (const registration of registrations) {
|
|
122
|
-
if (isQueueWorker(registration)) {
|
|
120
|
+
if (isQueueWorker(registration) || isQueueDefinition(registration)) {
|
|
123
121
|
await registerQueue(client, registration, provided, replaceWorkers);
|
|
124
122
|
} else if (isScheduledWorker(registration)) {
|
|
125
123
|
await registerSchedule(client, registration, provided, replaceWorkers);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
2
|
import type { PgBossClient } from "#client.ts";
|
|
3
3
|
import { type PgBossError, PgBossPayloadError, toPgBossError } from "#error.ts";
|
|
4
|
+
import { countsFor } from "#internal/countsFor.ts";
|
|
5
|
+
import { healthFor } from "#internal/health.ts";
|
|
6
|
+
import { getJobFor, retryFor } from "#internal/jobControl.ts";
|
|
4
7
|
import type { PgBossService } from "#service.ts";
|
|
5
|
-
import { healthFor } from "./health.ts";
|
|
6
8
|
|
|
7
|
-
export const makeService = (client: PgBossClient, names: readonly string[]): PgBossService => ({
|
|
9
|
+
export const makeService = (client: PgBossClient, names: readonly string[], schema = "pgboss"): PgBossService => ({
|
|
10
|
+
counts: (queue) => countsFor(client, schema, queue.name),
|
|
8
11
|
enqueue: (queue, payload, options) =>
|
|
9
12
|
Schema.encodeUnknownEffect(queue.schema)(payload).pipe(
|
|
10
13
|
Effect.mapError(
|
|
@@ -31,5 +34,7 @@ export const makeService = (client: PgBossClient, names: readonly string[]): PgB
|
|
|
31
34
|
}).pipe(Effect.map(Option.fromNullishOr));
|
|
32
35
|
}),
|
|
33
36
|
),
|
|
37
|
+
getJob: getJobFor(client),
|
|
34
38
|
health: healthFor(client, names),
|
|
39
|
+
retry: retryFor(client),
|
|
35
40
|
});
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { Context, Effect, Layer, type Option, type Schema } from "effect";
|
|
2
|
-
import type { ConstructorOptions, SendOptions, StopOptions } from "pg-boss";
|
|
2
|
+
import type { ConstructorOptions, JobWithMetadata, SendOptions, StopOptions } from "pg-boss";
|
|
3
3
|
import type { PgBossClientFactory } from "#client.ts";
|
|
4
|
+
import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "#definition.ts";
|
|
5
|
+
import type { PgBossError, PgBossPayloadError, PgBossQueueOptionsError } from "#error.ts";
|
|
6
|
+
import type { JobsHealth, QueueCounts } from "#health.ts";
|
|
4
7
|
import { acquireClient, releaseClient } from "#internal/lifecycle.ts";
|
|
5
8
|
import { registerJobs, registrationName } from "#internal/register.ts";
|
|
6
9
|
import { makeService } from "#internal/service.ts";
|
|
7
|
-
import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "./definition.ts";
|
|
8
|
-
import type { PgBossError, PgBossPayloadError } from "./error.ts";
|
|
9
|
-
import type { JobsHealth } from "./health.ts";
|
|
10
10
|
|
|
11
11
|
export interface PgBossService {
|
|
12
|
+
readonly counts: (queue: QueueDefinition) => Effect.Effect<QueueCounts, PgBossError>;
|
|
12
13
|
readonly enqueue: <const TName extends string, const TPayload extends JobPayloadSchema>(
|
|
13
14
|
queue: QueueDefinition<TName, TPayload>,
|
|
14
15
|
payload: Schema.Schema.Type<TPayload>,
|
|
15
16
|
options?: SendOptions,
|
|
16
17
|
) => Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError, TPayload["EncodingServices"]>;
|
|
18
|
+
readonly getJob: <const TName extends string, const TPayload extends JobPayloadSchema>(
|
|
19
|
+
queue: QueueDefinition<TName, TPayload>,
|
|
20
|
+
id: string,
|
|
21
|
+
) => Effect.Effect<Option.Option<JobWithMetadata<TPayload["Type"]>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
|
|
17
22
|
readonly health: Effect.Effect<JobsHealth, PgBossError>;
|
|
23
|
+
readonly retry: (queue: QueueDefinition, id: string) => Effect.Effect<void, PgBossError>;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
export type PgBossLayerOptions<TRegistrations extends readonly JobRegistration[], TErrorRequirements> = ConstructorOptions & {
|
|
@@ -29,7 +35,7 @@ export type PgBossLayerOptions<TRegistrations extends readonly JobRegistration[]
|
|
|
29
35
|
export interface PgBossDefinition<TIdentifier extends string = string> extends Context.Service<TIdentifier, PgBossService> {
|
|
30
36
|
readonly layer: <const TRegistrations extends readonly JobRegistration[], TErrorRequirements = never>(
|
|
31
37
|
options: PgBossLayerOptions<TRegistrations, TErrorRequirements>,
|
|
32
|
-
) => Layer.Layer<TIdentifier, PgBossError, RegistrationRequirements<TRegistrations> | TErrorRequirements>;
|
|
38
|
+
) => Layer.Layer<TIdentifier, PgBossError | PgBossQueueOptionsError, RegistrationRequirements<TRegistrations> | TErrorRequirements>;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
function logClientError(error: Error): Effect.Effect<void> {
|
|
@@ -41,7 +47,7 @@ export const makePgBoss = <const TIdentifier extends string>(identifier: TIdenti
|
|
|
41
47
|
|
|
42
48
|
const layer = <const TRegistrations extends readonly JobRegistration[], TErrorRequirements = never>(
|
|
43
49
|
options: PgBossLayerOptions<TRegistrations, TErrorRequirements>,
|
|
44
|
-
): Layer.Layer<TIdentifier, PgBossError, RegistrationRequirements<TRegistrations> | TErrorRequirements> => {
|
|
50
|
+
): Layer.Layer<TIdentifier, PgBossError | PgBossQueueOptionsError, RegistrationRequirements<TRegistrations> | TErrorRequirements> => {
|
|
45
51
|
type Requirements = RegistrationRequirements<TRegistrations> | TErrorRequirements;
|
|
46
52
|
const names = options.jobs.map(registrationName);
|
|
47
53
|
const { clientFactory, clientCacheKey, jobs, onError, stop, ...constructorOptions } = options;
|
|
@@ -76,7 +82,7 @@ export const makePgBoss = <const TIdentifier extends string>(identifier: TIdenti
|
|
|
76
82
|
|
|
77
83
|
return Layer.effect(
|
|
78
84
|
Service,
|
|
79
|
-
Effect.map(acquire, ({ acquired }) => makeService(acquired.client, names)),
|
|
85
|
+
Effect.map(acquire, ({ acquired }) => makeService(acquired.client, names, constructorOptions.schema)),
|
|
80
86
|
);
|
|
81
87
|
};
|
|
82
88
|
|
package/dist/client.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnI,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjE,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,OAAO,CAAC;IAC5E,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,OAAO,CAAC;IAC3E,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClH,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrG,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACrI;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,YAAY,CAAC;AAEhF,eAAO,MAAM,oBAAoB,EAAE,mBAAsD,CAAC"}
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAiBjC,MAAM,CAAC,MAAM,oBAAoB,GAAwB,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC"}
|