@shivaedev/effect-pg-boss 0.1.2 → 0.2.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 +17 -1
- package/README.md +7 -5
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js.map +1 -1
- package/dist/error.d.ts +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/internal/client.d.ts +1 -1
- package/dist/internal/client.d.ts.map +1 -1
- package/dist/internal/client.js.map +1 -1
- package/dist/internal/health.d.ts +3 -3
- package/dist/internal/health.d.ts.map +1 -1
- package/dist/internal/health.js +2 -5
- package/dist/internal/health.js.map +1 -1
- package/dist/internal/lifecycle.d.ts +4 -4
- package/dist/internal/lifecycle.d.ts.map +1 -1
- package/dist/internal/lifecycle.js +15 -9
- package/dist/internal/lifecycle.js.map +1 -1
- package/dist/internal/register.d.ts +5 -4
- package/dist/internal/register.d.ts.map +1 -1
- package/dist/internal/register.js +19 -8
- package/dist/internal/register.js.map +1 -1
- package/dist/internal/service.d.ts +2 -2
- package/dist/internal/service.d.ts.map +1 -1
- package/dist/internal/service.js +1 -1
- package/dist/internal/service.js.map +1 -1
- package/dist/service.d.ts +6 -5
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +6 -13
- package/dist/service.js.map +1 -1
- package/package.json +3 -3
- package/src/definition.ts +9 -33
- package/src/error.ts +3 -12
- package/src/health.ts +1 -2
- package/src/index.ts +5 -5
- package/src/internal/client.ts +6 -32
- package/src/internal/health.ts +6 -19
- package/src/internal/lifecycle.ts +23 -29
- package/src/internal/register.ts +44 -67
- package/src/internal/service.ts +20 -36
- package/src/service.ts +29 -90
package/src/definition.ts
CHANGED
|
@@ -14,33 +14,21 @@ export interface JobContext {
|
|
|
14
14
|
readonly heartbeatSeconds: number | null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export interface QueueDefinition<
|
|
18
|
-
Name extends string = string,
|
|
19
|
-
Payload extends JobPayloadSchema = JobPayloadSchema,
|
|
20
|
-
> {
|
|
17
|
+
export interface QueueDefinition<Name extends string = string, Payload extends JobPayloadSchema = JobPayloadSchema> {
|
|
21
18
|
readonly _tag: "QueueDefinition";
|
|
22
19
|
readonly name: Name;
|
|
23
20
|
readonly schema: Payload;
|
|
24
21
|
readonly queueOptions: Omit<QueueOptions, "deadLetter">;
|
|
25
22
|
readonly workerOptions: WorkOptions;
|
|
26
23
|
readonly handle: <E, R>(
|
|
27
|
-
handler: (
|
|
28
|
-
payload: Schema.Schema.Type<Payload>,
|
|
29
|
-
job: JobContext,
|
|
30
|
-
) => Effect.Effect<unknown, E, R>,
|
|
24
|
+
handler: (payload: Schema.Schema.Type<Payload>, job: JobContext) => Effect.Effect<unknown, E, R>,
|
|
31
25
|
) => QueueWorker<Payload, R>;
|
|
32
26
|
}
|
|
33
27
|
|
|
34
|
-
export interface QueueWorker<
|
|
35
|
-
Payload extends JobPayloadSchema = JobPayloadSchema,
|
|
36
|
-
Requirements = unknown,
|
|
37
|
-
> {
|
|
28
|
+
export interface QueueWorker<Payload extends JobPayloadSchema = JobPayloadSchema, Requirements = unknown> {
|
|
38
29
|
readonly _tag: "QueueWorker";
|
|
39
30
|
readonly queue: QueueDefinition<string, Payload>;
|
|
40
|
-
readonly handler: (
|
|
41
|
-
payload: Schema.Schema.Type<Payload>,
|
|
42
|
-
job: JobContext,
|
|
43
|
-
) => Effect.Effect<unknown, unknown, Requirements>;
|
|
31
|
+
readonly handler: (payload: Schema.Schema.Type<Payload>, job: JobContext) => Effect.Effect<unknown, unknown, Requirements>;
|
|
44
32
|
}
|
|
45
33
|
|
|
46
34
|
export interface JobRegistration {
|
|
@@ -54,9 +42,7 @@ export interface ScheduleDefinition<Name extends string = string> {
|
|
|
54
42
|
readonly queueOptions: Omit<QueueOptions, "deadLetter">;
|
|
55
43
|
readonly scheduleOptions: ScheduleOptions;
|
|
56
44
|
readonly workerOptions: WorkOptions;
|
|
57
|
-
readonly run: <E, R>(
|
|
58
|
-
effect: Effect.Effect<unknown, E, R>,
|
|
59
|
-
) => ScheduledWorker<R>;
|
|
45
|
+
readonly run: <E, R>(effect: Effect.Effect<unknown, E, R>) => ScheduledWorker<R>;
|
|
60
46
|
}
|
|
61
47
|
|
|
62
48
|
export interface ScheduledWorker<Requirements = unknown> {
|
|
@@ -65,9 +51,7 @@ export interface ScheduledWorker<Requirements = unknown> {
|
|
|
65
51
|
readonly effect: Effect.Effect<unknown, unknown, Requirements>;
|
|
66
52
|
}
|
|
67
53
|
|
|
68
|
-
export type RegistrationRequirements<
|
|
69
|
-
Registrations extends readonly JobRegistration[],
|
|
70
|
-
> = Registrations[number] extends infer Registration
|
|
54
|
+
export type RegistrationRequirements<Registrations extends readonly JobRegistration[]> = Registrations[number] extends infer Registration
|
|
71
55
|
? Registration extends QueueWorker<infer Payload, infer Requirements>
|
|
72
56
|
? Requirements | Payload["DecodingServices"]
|
|
73
57
|
: Registration extends ScheduledWorker<infer Requirements>
|
|
@@ -75,20 +59,14 @@ export type RegistrationRequirements<
|
|
|
75
59
|
: never
|
|
76
60
|
: never;
|
|
77
61
|
|
|
78
|
-
export interface DefineQueueOptions<
|
|
79
|
-
Name extends string,
|
|
80
|
-
Payload extends JobPayloadSchema,
|
|
81
|
-
> {
|
|
62
|
+
export interface DefineQueueOptions<Name extends string, Payload extends JobPayloadSchema> {
|
|
82
63
|
readonly name: Name;
|
|
83
64
|
readonly schema: Payload;
|
|
84
65
|
readonly queue?: Omit<QueueOptions, "deadLetter">;
|
|
85
66
|
readonly worker?: WorkOptions;
|
|
86
67
|
}
|
|
87
68
|
|
|
88
|
-
export const defineQueue = <
|
|
89
|
-
const Name extends string,
|
|
90
|
-
const Payload extends JobPayloadSchema,
|
|
91
|
-
>(
|
|
69
|
+
export const defineQueue = <const Name extends string, const Payload extends JobPayloadSchema>(
|
|
92
70
|
options: DefineQueueOptions<Name, Payload>,
|
|
93
71
|
): QueueDefinition<Name, Payload> => {
|
|
94
72
|
const definition: QueueDefinition<Name, Payload> = {
|
|
@@ -115,9 +93,7 @@ export interface DefineScheduleOptions<Name extends string> {
|
|
|
115
93
|
readonly worker?: WorkOptions;
|
|
116
94
|
}
|
|
117
95
|
|
|
118
|
-
export const defineSchedule = <const Name extends string>(
|
|
119
|
-
options: DefineScheduleOptions<Name>,
|
|
120
|
-
): ScheduleDefinition<Name> => {
|
|
96
|
+
export const defineSchedule = <const Name extends string>(options: DefineScheduleOptions<Name>): ScheduleDefinition<Name> => {
|
|
121
97
|
const definition: ScheduleDefinition<Name> = {
|
|
122
98
|
_tag: "ScheduleDefinition",
|
|
123
99
|
cron: options.cron,
|
package/src/error.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { Data, Redacted } from "effect";
|
|
2
2
|
|
|
3
|
-
export type PgBossOperation =
|
|
4
|
-
| "enqueue"
|
|
5
|
-
| "health"
|
|
6
|
-
| "register"
|
|
7
|
-
| "start"
|
|
8
|
-
| "stop";
|
|
3
|
+
export type PgBossOperation = "enqueue" | "health" | "register" | "start" | "stop";
|
|
9
4
|
|
|
10
5
|
export class PgBossError extends Data.TaggedError("PgBossError")<{
|
|
11
6
|
readonly operation: PgBossOperation;
|
|
12
|
-
readonly queue?: string;
|
|
7
|
+
readonly queue?: string | undefined;
|
|
13
8
|
readonly original: Redacted.Redacted<unknown>;
|
|
14
9
|
}> {}
|
|
15
10
|
|
|
@@ -19,11 +14,7 @@ export class PgBossPayloadError extends Data.TaggedError("PgBossPayloadError")<{
|
|
|
19
14
|
readonly original: Redacted.Redacted<unknown>;
|
|
20
15
|
}> {}
|
|
21
16
|
|
|
22
|
-
export const toPgBossError = (
|
|
23
|
-
operation: PgBossOperation,
|
|
24
|
-
error: unknown,
|
|
25
|
-
queue?: string,
|
|
26
|
-
): PgBossError =>
|
|
17
|
+
export const toPgBossError = (operation: PgBossOperation, error: unknown, queue?: string): PgBossError =>
|
|
27
18
|
new PgBossError({
|
|
28
19
|
operation,
|
|
29
20
|
queue,
|
package/src/health.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,24 +10,24 @@ export {
|
|
|
10
10
|
type QueueWorker,
|
|
11
11
|
type ScheduleDefinition,
|
|
12
12
|
type ScheduledWorker,
|
|
13
|
-
} from "./definition.
|
|
13
|
+
} from "./definition.ts";
|
|
14
14
|
export {
|
|
15
15
|
PgBossError,
|
|
16
16
|
type PgBossOperation,
|
|
17
17
|
PgBossPayloadError,
|
|
18
|
-
} from "./error.
|
|
18
|
+
} from "./error.ts";
|
|
19
19
|
export {
|
|
20
20
|
deadLetterQueueName,
|
|
21
21
|
type JobsHealth,
|
|
22
22
|
type QueueHealth,
|
|
23
|
-
} from "./health.
|
|
23
|
+
} from "./health.ts";
|
|
24
24
|
export type {
|
|
25
25
|
PgBossClient,
|
|
26
26
|
PgBossClientFactory,
|
|
27
|
-
} from "./internal/client.
|
|
27
|
+
} from "./internal/client.ts";
|
|
28
28
|
export {
|
|
29
29
|
makePgBoss,
|
|
30
30
|
type PgBossDefinition,
|
|
31
31
|
type PgBossLayerOptions,
|
|
32
32
|
type PgBossService,
|
|
33
|
-
} from "./service.
|
|
33
|
+
} from "./service.ts";
|
package/src/internal/client.ts
CHANGED
|
@@ -1,45 +1,19 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ConstructorOptions,
|
|
3
|
-
Job,
|
|
4
|
-
Queue,
|
|
5
|
-
QueueResult,
|
|
6
|
-
ScheduleOptions,
|
|
7
|
-
SendOptions,
|
|
8
|
-
StopOptions,
|
|
9
|
-
WorkOptions,
|
|
10
|
-
} from "pg-boss";
|
|
1
|
+
import type { ConstructorOptions, Job, Queue, QueueResult, ScheduleOptions, SendOptions, StopOptions, WorkOptions } from "pg-boss";
|
|
11
2
|
import { PgBoss } from "pg-boss";
|
|
12
3
|
|
|
13
4
|
export interface PgBossClient {
|
|
14
|
-
readonly createQueue: (
|
|
15
|
-
name: string,
|
|
16
|
-
options?: Omit<Queue, "name">,
|
|
17
|
-
) => Promise<void>;
|
|
5
|
+
readonly createQueue: (name: string, options?: Omit<Queue, "name">) => Promise<void>;
|
|
18
6
|
readonly getQueue: (name: string) => Promise<QueueResult | null>;
|
|
19
7
|
readonly offWork: (name: string) => Promise<void>;
|
|
20
8
|
readonly on: (event: "error", listener: (error: Error) => void) => unknown;
|
|
21
9
|
readonly off: (event: "error", listener: (error: Error) => void) => unknown;
|
|
22
|
-
readonly schedule: (
|
|
23
|
-
|
|
24
|
-
cron: string,
|
|
25
|
-
data?: object | null,
|
|
26
|
-
options?: ScheduleOptions,
|
|
27
|
-
) => Promise<void>;
|
|
28
|
-
readonly send: (
|
|
29
|
-
name: string,
|
|
30
|
-
data?: object | null,
|
|
31
|
-
options?: SendOptions,
|
|
32
|
-
) => Promise<string | null>;
|
|
10
|
+
readonly schedule: (name: string, cron: string, data?: object | null, options?: ScheduleOptions) => Promise<void>;
|
|
11
|
+
readonly send: (name: string, data?: object | null, options?: SendOptions) => Promise<string | null>;
|
|
33
12
|
readonly start: () => Promise<unknown>;
|
|
34
13
|
readonly stop: (options?: StopOptions) => Promise<void>;
|
|
35
|
-
readonly work: <
|
|
36
|
-
name: string,
|
|
37
|
-
options: WorkOptions,
|
|
38
|
-
handler: (jobs: readonly Job<Payload>[]) => Promise<unknown>,
|
|
39
|
-
) => Promise<string>;
|
|
14
|
+
readonly work: (name: string, options: WorkOptions, handler: (jobs: readonly Job<unknown>[]) => Promise<unknown>) => Promise<string>;
|
|
40
15
|
}
|
|
41
16
|
|
|
42
17
|
export type PgBossClientFactory = (options: ConstructorOptions) => PgBossClient;
|
|
43
18
|
|
|
44
|
-
export const defaultClientFactory: PgBossClientFactory = (options) =>
|
|
45
|
-
new PgBoss(options);
|
|
19
|
+
export const defaultClientFactory: PgBossClientFactory = (options) => new PgBoss(options);
|
package/src/internal/health.ts
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { toPgBossError } from "../error.
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
type JobsHealth,
|
|
6
|
-
type QueueHealth,
|
|
7
|
-
} from "../health.js";
|
|
8
|
-
import type { PgBossClient } from "./client.js";
|
|
2
|
+
import { toPgBossError } from "../error.ts";
|
|
3
|
+
import { deadLetterQueueName, type JobsHealth, type QueueHealth } from "../health.ts";
|
|
4
|
+
import type { PgBossClient } from "./client.ts";
|
|
9
5
|
|
|
10
|
-
export const healthFor = (
|
|
11
|
-
client: PgBossClient,
|
|
12
|
-
names: readonly string[],
|
|
13
|
-
): Effect.Effect<JobsHealth, import("../error.js").PgBossError> =>
|
|
6
|
+
export const healthFor = (client: PgBossClient, names: readonly string[]): Effect.Effect<JobsHealth, import("../error.ts").PgBossError> =>
|
|
14
7
|
Effect.forEach(
|
|
15
8
|
names,
|
|
16
9
|
(name) =>
|
|
17
10
|
Effect.tryPromise({
|
|
18
11
|
try: async (): Promise<QueueHealth> => {
|
|
19
|
-
const [queue, deadLetter] = await Promise.all([
|
|
20
|
-
client.getQueue(name),
|
|
21
|
-
client.getQueue(deadLetterQueueName(name)),
|
|
22
|
-
]);
|
|
12
|
+
const [queue, deadLetter] = await Promise.all([client.getQueue(name), client.getQueue(deadLetterQueueName(name))]);
|
|
23
13
|
return {
|
|
24
14
|
activeCount: queue?.activeCount ?? 0,
|
|
25
15
|
deadLetteredCount: deadLetter?.queuedCount ?? 0,
|
|
@@ -35,10 +25,7 @@ export const healthFor = (
|
|
|
35
25
|
).pipe(
|
|
36
26
|
Effect.map((jobs) => ({
|
|
37
27
|
activeTotal: jobs.reduce((sum, job) => sum + job.activeCount, 0),
|
|
38
|
-
deadLetteredTotal: jobs.reduce(
|
|
39
|
-
(sum, job) => sum + job.deadLetteredCount,
|
|
40
|
-
0,
|
|
41
|
-
),
|
|
28
|
+
deadLetteredTotal: jobs.reduce((sum, job) => sum + job.deadLetteredCount, 0),
|
|
42
29
|
failedTotal: jobs.reduce((sum, job) => sum + job.failedCount, 0),
|
|
43
30
|
jobs,
|
|
44
31
|
queuedTotal: jobs.reduce((sum, job) => sum + job.queuedCount, 0),
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { ConstructorOptions, StopOptions } from "pg-boss";
|
|
3
|
-
import { toPgBossError } from "../error.
|
|
4
|
-
import {
|
|
5
|
-
defaultClientFactory,
|
|
6
|
-
type PgBossClient,
|
|
7
|
-
type PgBossClientFactory,
|
|
8
|
-
} from "./client.js";
|
|
3
|
+
import { toPgBossError } from "../error.ts";
|
|
4
|
+
import { defaultClientFactory, type PgBossClient, type PgBossClientFactory } from "./client.ts";
|
|
9
5
|
|
|
10
6
|
interface CachedClient {
|
|
11
7
|
readonly client: Promise<PgBossClient>;
|
|
@@ -13,17 +9,25 @@ interface CachedClient {
|
|
|
13
9
|
}
|
|
14
10
|
|
|
15
11
|
const CacheKey = Symbol.for("@shivaedev/effect-pg-boss/client-cache");
|
|
16
|
-
const
|
|
17
|
-
|
|
12
|
+
const sharedCache: unknown = Reflect.get(globalThis, CacheKey);
|
|
13
|
+
const clientCache: Map<string | symbol, unknown> = sharedCache instanceof Map ? sharedCache : new Map<string | symbol, unknown>();
|
|
14
|
+
Reflect.set(globalThis, CacheKey, clientCache);
|
|
15
|
+
|
|
16
|
+
const isCachedClient = (value: unknown): value is CachedClient =>
|
|
17
|
+
typeof value === "object" &&
|
|
18
|
+
value !== null &&
|
|
19
|
+
Reflect.get(value, "client") instanceof Promise &&
|
|
20
|
+
typeof Reflect.get(value, "references") === "number";
|
|
21
|
+
|
|
22
|
+
const cachedClient = (key: string | symbol): CachedClient | undefined => {
|
|
23
|
+
const entry = clientCache.get(key);
|
|
24
|
+
return isCachedClient(entry) ? entry : undefined;
|
|
18
25
|
};
|
|
19
|
-
const clientCache =
|
|
20
|
-
globalCache[CacheKey] ?? new Map<string | symbol, CachedClient>();
|
|
21
|
-
globalCache[CacheKey] = clientCache;
|
|
22
26
|
|
|
23
27
|
export interface AcquireClientOptions {
|
|
24
|
-
readonly clientFactory?: PgBossClientFactory;
|
|
28
|
+
readonly clientFactory?: PgBossClientFactory | undefined;
|
|
25
29
|
readonly constructor: ConstructorOptions;
|
|
26
|
-
readonly
|
|
30
|
+
readonly clientCacheKey?: string | symbol | undefined;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
export interface AcquiredClient {
|
|
@@ -33,9 +37,7 @@ export interface AcquiredClient {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
const startClient = (options: AcquireClientOptions): Promise<PgBossClient> => {
|
|
36
|
-
const client = (options.clientFactory ?? defaultClientFactory)(
|
|
37
|
-
options.constructor,
|
|
38
|
-
);
|
|
40
|
+
const client = (options.clientFactory ?? defaultClientFactory)(options.constructor);
|
|
39
41
|
return client.start().then(
|
|
40
42
|
() => client,
|
|
41
43
|
async (error) => {
|
|
@@ -49,15 +51,10 @@ const startClient = (options: AcquireClientOptions): Promise<PgBossClient> => {
|
|
|
49
51
|
);
|
|
50
52
|
};
|
|
51
53
|
|
|
52
|
-
export const acquireClient = (
|
|
53
|
-
options: AcquireClientOptions,
|
|
54
|
-
): Effect.Effect<AcquiredClient, import("../error.js").PgBossError> =>
|
|
54
|
+
export const acquireClient = (options: AcquireClientOptions): Effect.Effect<AcquiredClient, import("../error.ts").PgBossError> =>
|
|
55
55
|
Effect.tryPromise({
|
|
56
56
|
try: async () => {
|
|
57
|
-
const cacheKey =
|
|
58
|
-
process.env.NODE_ENV === "production"
|
|
59
|
-
? undefined
|
|
60
|
-
: options.developmentCacheKey;
|
|
57
|
+
const cacheKey = options.clientCacheKey;
|
|
61
58
|
if (cacheKey === undefined) {
|
|
62
59
|
return {
|
|
63
60
|
client: await startClient(options),
|
|
@@ -65,7 +62,7 @@ export const acquireClient = (
|
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
const existing =
|
|
65
|
+
const existing = cachedClient(cacheKey);
|
|
69
66
|
if (existing !== undefined) {
|
|
70
67
|
existing.references += 1;
|
|
71
68
|
try {
|
|
@@ -99,10 +96,7 @@ export const acquireClient = (
|
|
|
99
96
|
catch: (error) => toPgBossError("start", error),
|
|
100
97
|
});
|
|
101
98
|
|
|
102
|
-
export const releaseClient = (
|
|
103
|
-
acquired: AcquiredClient,
|
|
104
|
-
stopOptions?: StopOptions,
|
|
105
|
-
): Effect.Effect<void> =>
|
|
99
|
+
export const releaseClient = (acquired: AcquiredClient, stopOptions?: StopOptions): Effect.Effect<void> =>
|
|
106
100
|
Effect.tryPromise({
|
|
107
101
|
try: async () => {
|
|
108
102
|
if (acquired.cacheKey === undefined) {
|
|
@@ -110,7 +104,7 @@ export const releaseClient = (
|
|
|
110
104
|
return;
|
|
111
105
|
}
|
|
112
106
|
|
|
113
|
-
const entry =
|
|
107
|
+
const entry = cachedClient(acquired.cacheKey);
|
|
114
108
|
if (entry === undefined) return;
|
|
115
109
|
entry.references -= 1;
|
|
116
110
|
if (entry.references > 0) return;
|
package/src/internal/register.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { Effect, Redacted, Schema } from "effect";
|
|
1
|
+
import { type Context, Effect, Redacted, Schema } from "effect";
|
|
2
2
|
import type { Job } from "pg-boss";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type QueueWorker,
|
|
8
|
-
type ScheduledWorker,
|
|
9
|
-
} from "../definition.js";
|
|
10
|
-
import { PgBossPayloadError, toPgBossError } from "../error.js";
|
|
11
|
-
import { deadLetterQueueName } from "../health.js";
|
|
12
|
-
import type { PgBossClient } from "./client.js";
|
|
3
|
+
import { type JobRegistration, jobContext, type QueueWorker, type ScheduledWorker } from "../definition.ts";
|
|
4
|
+
import { PgBossPayloadError, toPgBossError } from "../error.ts";
|
|
5
|
+
import { deadLetterQueueName } from "../health.ts";
|
|
6
|
+
import type { PgBossClient } from "./client.ts";
|
|
13
7
|
|
|
14
8
|
const queueOptions = (options: Readonly<Record<string, unknown>>) => ({
|
|
15
9
|
retryBackoff: true,
|
|
@@ -17,11 +11,23 @@ const queueOptions = (options: Readonly<Record<string, unknown>>) => ({
|
|
|
17
11
|
...options,
|
|
18
12
|
});
|
|
19
13
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
):
|
|
14
|
+
const isQueueWorker = (registration: JobRegistration): registration is QueueWorker => registration._tag === "QueueWorker";
|
|
15
|
+
|
|
16
|
+
const isScheduledWorker = (registration: JobRegistration): registration is ScheduledWorker => registration._tag === "ScheduledWorker";
|
|
17
|
+
|
|
18
|
+
export const registrationName = (registration: JobRegistration): string => {
|
|
19
|
+
if (isQueueWorker(registration)) return registration.queue.name;
|
|
20
|
+
if (isScheduledWorker(registration)) return registration.schedule.name;
|
|
21
|
+
throw new TypeError(`Unknown pg-boss registration: ${registration._tag}`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// The layer requires RegistrationRequirements of its jobs, so its context provides every registered worker.
|
|
25
|
+
function workerContext<R>(context: Context.Context<R>): Context.Context<unknown>;
|
|
26
|
+
function workerContext(context: unknown): unknown {
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const runQueueWorker = (worker: QueueWorker, job: Job<unknown>, context: Context.Context<unknown>): Promise<unknown> =>
|
|
25
31
|
Effect.runPromise(
|
|
26
32
|
Schema.decodeUnknownEffect(worker.queue.schema)(job.data).pipe(
|
|
27
33
|
Effect.mapError(
|
|
@@ -34,16 +40,11 @@ const runQueueWorker = <R>(
|
|
|
34
40
|
),
|
|
35
41
|
Effect.flatMap((payload) => worker.handler(payload, jobContext(job))),
|
|
36
42
|
Effect.provide(context),
|
|
37
|
-
)
|
|
43
|
+
),
|
|
38
44
|
{ signal: job.signal },
|
|
39
45
|
);
|
|
40
46
|
|
|
41
|
-
const registerQueue = async <
|
|
42
|
-
client: PgBossClient,
|
|
43
|
-
worker: QueueWorker<JobPayloadSchema, R>,
|
|
44
|
-
context: import("effect").Context.Context<R>,
|
|
45
|
-
replaceWorker: boolean,
|
|
46
|
-
): Promise<void> => {
|
|
47
|
+
const registerQueue = async (client: PgBossClient, worker: QueueWorker, context: Context.Context<unknown>, replaceWorker: boolean): Promise<void> => {
|
|
47
48
|
const name = worker.queue.name;
|
|
48
49
|
const deadLetter = deadLetterQueueName(name);
|
|
49
50
|
if (replaceWorker) await client.offWork(name);
|
|
@@ -57,10 +58,10 @@ const registerQueue = async <R>(
|
|
|
57
58
|
});
|
|
58
59
|
};
|
|
59
60
|
|
|
60
|
-
const registerSchedule = async
|
|
61
|
+
const registerSchedule = async (
|
|
61
62
|
client: PgBossClient,
|
|
62
|
-
worker: ScheduledWorker
|
|
63
|
-
context:
|
|
63
|
+
worker: ScheduledWorker,
|
|
64
|
+
context: Context.Context<unknown>,
|
|
64
65
|
replaceWorker: boolean,
|
|
65
66
|
): Promise<void> => {
|
|
66
67
|
const { schedule } = worker;
|
|
@@ -71,33 +72,18 @@ const registerSchedule = async <R>(
|
|
|
71
72
|
...queueOptions(schedule.queueOptions),
|
|
72
73
|
deadLetter,
|
|
73
74
|
});
|
|
74
|
-
await client.work(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
);
|
|
85
|
-
await client.schedule(
|
|
86
|
-
schedule.name,
|
|
87
|
-
schedule.cron,
|
|
88
|
-
null,
|
|
89
|
-
schedule.scheduleOptions,
|
|
90
|
-
);
|
|
75
|
+
await client.work(schedule.name, schedule.workerOptions, async (jobs: readonly Job<unknown>[]) => {
|
|
76
|
+
for (const job of jobs) {
|
|
77
|
+
await Effect.runPromise(Effect.provide(worker.effect, context), {
|
|
78
|
+
signal: job.signal,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
await client.schedule(schedule.name, schedule.cron, null, schedule.scheduleOptions);
|
|
91
83
|
};
|
|
92
84
|
|
|
93
|
-
export const registrationNames = (
|
|
94
|
-
|
|
95
|
-
): readonly string[] => {
|
|
96
|
-
const names = registrations.map((registration) =>
|
|
97
|
-
registration._tag === "QueueWorker"
|
|
98
|
-
? (registration as QueueWorker).queue.name
|
|
99
|
-
: (registration as ScheduledWorker).schedule.name,
|
|
100
|
-
);
|
|
85
|
+
export const registrationNames = (registrations: readonly JobRegistration[]): readonly string[] => {
|
|
86
|
+
const names = registrations.map(registrationName);
|
|
101
87
|
if (new Set(names).size !== names.length) {
|
|
102
88
|
throw new TypeError("pg-boss job names must be unique");
|
|
103
89
|
}
|
|
@@ -107,27 +93,18 @@ export const registrationNames = (
|
|
|
107
93
|
export const registerJobs = <R>(
|
|
108
94
|
client: PgBossClient,
|
|
109
95
|
registrations: readonly JobRegistration[],
|
|
110
|
-
context:
|
|
96
|
+
context: Context.Context<R>,
|
|
111
97
|
replaceWorkers: boolean,
|
|
112
|
-
): Effect.Effect<void, import("../error.
|
|
98
|
+
): Effect.Effect<void, import("../error.ts").PgBossError> =>
|
|
113
99
|
Effect.tryPromise({
|
|
114
100
|
try: async () => {
|
|
115
101
|
registrationNames(registrations);
|
|
102
|
+
const provided = workerContext(context);
|
|
116
103
|
for (const registration of registrations) {
|
|
117
|
-
if (registration
|
|
118
|
-
await registerQueue(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
context,
|
|
122
|
-
replaceWorkers,
|
|
123
|
-
);
|
|
124
|
-
} else {
|
|
125
|
-
await registerSchedule(
|
|
126
|
-
client,
|
|
127
|
-
registration as ScheduledWorker<R>,
|
|
128
|
-
context,
|
|
129
|
-
replaceWorkers,
|
|
130
|
-
);
|
|
104
|
+
if (isQueueWorker(registration)) {
|
|
105
|
+
await registerQueue(client, registration, provided, replaceWorkers);
|
|
106
|
+
} else if (isScheduledWorker(registration)) {
|
|
107
|
+
await registerSchedule(client, registration, provided, replaceWorkers);
|
|
131
108
|
}
|
|
132
109
|
}
|
|
133
110
|
},
|
package/src/internal/service.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../error.js";
|
|
7
|
-
import type { PgBossService } from "../service.js";
|
|
8
|
-
import type { PgBossClient } from "./client.js";
|
|
9
|
-
import { healthFor } from "./health.js";
|
|
2
|
+
import { type PgBossError, PgBossPayloadError, toPgBossError } from "../error.ts";
|
|
3
|
+
import type { PgBossService } from "../service.ts";
|
|
4
|
+
import type { PgBossClient } from "./client.ts";
|
|
5
|
+
import { healthFor } from "./health.ts";
|
|
10
6
|
|
|
11
|
-
export const makeService = (
|
|
12
|
-
client: PgBossClient,
|
|
13
|
-
names: readonly string[],
|
|
14
|
-
): PgBossService => ({
|
|
7
|
+
export const makeService = (client: PgBossClient, names: readonly string[]): PgBossService => ({
|
|
15
8
|
enqueue: (queue, payload, options) =>
|
|
16
9
|
Schema.encodeUnknownEffect(queue.schema)(payload).pipe(
|
|
17
10
|
Effect.mapError(
|
|
@@ -22,30 +15,21 @@ export const makeService = (
|
|
|
22
15
|
queue: queue.name,
|
|
23
16
|
}),
|
|
24
17
|
),
|
|
25
|
-
Effect.flatMap(
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}),
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
return Effect.tryPromise({
|
|
44
|
-
try: () => client.send(queue.name, encoded, options),
|
|
45
|
-
catch: (error) => toPgBossError("enqueue", error, queue.name),
|
|
46
|
-
}).pipe(Effect.map(Option.fromNullishOr));
|
|
47
|
-
},
|
|
48
|
-
),
|
|
18
|
+
Effect.flatMap((encoded): Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError> => {
|
|
19
|
+
if (typeof encoded !== "object" || encoded === null) {
|
|
20
|
+
return Effect.fail(
|
|
21
|
+
new PgBossPayloadError({
|
|
22
|
+
direction: "encode",
|
|
23
|
+
original: Redacted.make(new TypeError("pg-boss payloads must encode to objects")),
|
|
24
|
+
queue: queue.name,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return Effect.tryPromise({
|
|
29
|
+
try: () => client.send(queue.name, encoded, options),
|
|
30
|
+
catch: (error) => toPgBossError("enqueue", error, queue.name),
|
|
31
|
+
}).pipe(Effect.map(Option.fromNullishOr));
|
|
32
|
+
}),
|
|
49
33
|
),
|
|
50
34
|
health: healthFor(client, names),
|
|
51
35
|
});
|