@shivaedev/effect-pg-boss 0.1.3 → 0.2.1
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 +30 -1
- package/README.md +7 -5
- package/dist/definition.d.ts +8 -8
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +6 -6
- 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 +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/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/client.d.ts +2 -2
- 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 +4 -7
- package/dist/internal/health.js.map +1 -1
- package/dist/internal/lifecycle.d.ts +5 -5
- package/dist/internal/lifecycle.d.ts.map +1 -1
- package/dist/internal/lifecycle.js +24 -15
- 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 +30 -14
- 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 +2 -2
- package/dist/internal/service.js.map +1 -1
- package/dist/service.d.ts +5 -6
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +8 -15
- package/dist/service.js.map +1 -1
- package/package.json +35 -29
- package/src/definition.ts +22 -46
- package/src/error.ts +4 -13
- package/src/health.ts +1 -2
- package/src/index.ts +8 -8
- package/src/internal/client.ts +7 -33
- package/src/internal/health.ts +7 -20
- package/src/internal/lifecycle.ts +35 -35
- package/src/internal/register.ts +58 -71
- package/src/internal/service.ts +20 -36
- package/src/service.ts +29 -91
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { ConstructorOptions, StopOptions } from "pg-boss";
|
|
3
|
-
import { toPgBossError } from "
|
|
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,29 +9,35 @@ 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
|
|
28
|
+
readonly clientCacheKey?: string | symbol | undefined;
|
|
29
|
+
readonly clientFactory?: PgBossClientFactory | undefined;
|
|
25
30
|
readonly constructor: ConstructorOptions;
|
|
26
|
-
readonly developmentCacheKey?: string | symbol;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
export interface AcquiredClient {
|
|
30
|
-
readonly client: PgBossClient;
|
|
31
34
|
readonly cacheKey?: string | symbol;
|
|
35
|
+
readonly client: PgBossClient;
|
|
32
36
|
readonly reused: boolean;
|
|
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,11 @@ 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
|
+
catch: (error) => toPgBossError("start", error),
|
|
56
57
|
try: async () => {
|
|
57
|
-
const cacheKey =
|
|
58
|
-
process.env.NODE_ENV === "production"
|
|
59
|
-
? undefined
|
|
60
|
-
: options.developmentCacheKey;
|
|
58
|
+
const cacheKey = options.clientCacheKey;
|
|
61
59
|
if (cacheKey === undefined) {
|
|
62
60
|
return {
|
|
63
61
|
client: await startClient(options),
|
|
@@ -65,7 +63,7 @@ export const acquireClient = (
|
|
|
65
63
|
};
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
const existing =
|
|
66
|
+
const existing = cachedClient(cacheKey);
|
|
69
67
|
if (existing !== undefined) {
|
|
70
68
|
existing.references += 1;
|
|
71
69
|
try {
|
|
@@ -92,30 +90,32 @@ export const acquireClient = (
|
|
|
92
90
|
reused: false,
|
|
93
91
|
};
|
|
94
92
|
} catch (error) {
|
|
95
|
-
if (clientCache.get(cacheKey) === entry)
|
|
93
|
+
if (clientCache.get(cacheKey) === entry) {
|
|
94
|
+
clientCache.delete(cacheKey);
|
|
95
|
+
}
|
|
96
96
|
throw error;
|
|
97
97
|
}
|
|
98
98
|
},
|
|
99
|
-
catch: (error) => toPgBossError("start", error),
|
|
100
99
|
});
|
|
101
100
|
|
|
102
|
-
export const releaseClient = (
|
|
103
|
-
acquired: AcquiredClient,
|
|
104
|
-
stopOptions?: StopOptions,
|
|
105
|
-
): Effect.Effect<void> =>
|
|
101
|
+
export const releaseClient = (acquired: AcquiredClient, stopOptions?: StopOptions): Effect.Effect<void> =>
|
|
106
102
|
Effect.tryPromise({
|
|
103
|
+
catch: (error) => toPgBossError("stop", error),
|
|
107
104
|
try: async () => {
|
|
108
105
|
if (acquired.cacheKey === undefined) {
|
|
109
106
|
await acquired.client.stop(stopOptions);
|
|
110
107
|
return;
|
|
111
108
|
}
|
|
112
109
|
|
|
113
|
-
const entry =
|
|
114
|
-
if (entry === undefined)
|
|
110
|
+
const entry = cachedClient(acquired.cacheKey);
|
|
111
|
+
if (entry === undefined) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
115
114
|
entry.references -= 1;
|
|
116
|
-
if (entry.references > 0)
|
|
115
|
+
if (entry.references > 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
117
118
|
clientCache.delete(acquired.cacheKey);
|
|
118
119
|
await acquired.client.stop(stopOptions);
|
|
119
120
|
},
|
|
120
|
-
catch: (error) => toPgBossError("stop", error),
|
|
121
121
|
}).pipe(Effect.catch((error) => Effect.logError(error)));
|
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,27 @@ 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)) {
|
|
20
|
+
return registration.queue.name;
|
|
21
|
+
}
|
|
22
|
+
if (isScheduledWorker(registration)) {
|
|
23
|
+
return registration.schedule.name;
|
|
24
|
+
}
|
|
25
|
+
throw new TypeError(`Unknown pg-boss registration: ${registration._tag}`);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// The layer requires RegistrationRequirements of its jobs, so its context provides every registered worker.
|
|
29
|
+
function workerContext<R>(context: Context.Context<R>): Context.Context<unknown>;
|
|
30
|
+
function workerContext(context: unknown): unknown {
|
|
31
|
+
return context;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const runQueueWorker = (worker: QueueWorker, job: Job<unknown>, context: Context.Context<unknown>): Promise<unknown> =>
|
|
25
35
|
Effect.runPromise(
|
|
26
36
|
Schema.decodeUnknownEffect(worker.queue.schema)(job.data).pipe(
|
|
27
37
|
Effect.mapError(
|
|
@@ -34,70 +44,56 @@ const runQueueWorker = <R>(
|
|
|
34
44
|
),
|
|
35
45
|
Effect.flatMap((payload) => worker.handler(payload, jobContext(job))),
|
|
36
46
|
Effect.provide(context),
|
|
37
|
-
)
|
|
47
|
+
),
|
|
38
48
|
{ signal: job.signal },
|
|
39
49
|
);
|
|
40
50
|
|
|
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> => {
|
|
51
|
+
const registerQueue = async (client: PgBossClient, worker: QueueWorker, context: Context.Context<unknown>, replaceWorker: boolean): Promise<void> => {
|
|
47
52
|
const name = worker.queue.name;
|
|
48
53
|
const deadLetter = deadLetterQueueName(name);
|
|
49
|
-
if (replaceWorker)
|
|
54
|
+
if (replaceWorker) {
|
|
55
|
+
await client.offWork(name);
|
|
56
|
+
}
|
|
50
57
|
await client.createQueue(deadLetter);
|
|
51
58
|
await client.createQueue(name, {
|
|
52
59
|
...queueOptions(worker.queue.queueOptions),
|
|
53
60
|
deadLetter,
|
|
54
61
|
});
|
|
55
62
|
await client.work(name, worker.queue.workerOptions, async (jobs) => {
|
|
56
|
-
for (const job of jobs)
|
|
63
|
+
for (const job of jobs) {
|
|
64
|
+
await runQueueWorker(worker, job, context);
|
|
65
|
+
}
|
|
57
66
|
});
|
|
58
67
|
};
|
|
59
68
|
|
|
60
|
-
const registerSchedule = async
|
|
69
|
+
const registerSchedule = async (
|
|
61
70
|
client: PgBossClient,
|
|
62
|
-
worker: ScheduledWorker
|
|
63
|
-
context:
|
|
71
|
+
worker: ScheduledWorker,
|
|
72
|
+
context: Context.Context<unknown>,
|
|
64
73
|
replaceWorker: boolean,
|
|
65
74
|
): Promise<void> => {
|
|
66
75
|
const { schedule } = worker;
|
|
67
76
|
const deadLetter = deadLetterQueueName(schedule.name);
|
|
68
|
-
if (replaceWorker)
|
|
77
|
+
if (replaceWorker) {
|
|
78
|
+
await client.offWork(schedule.name);
|
|
79
|
+
}
|
|
69
80
|
await client.createQueue(deadLetter);
|
|
70
81
|
await client.createQueue(schedule.name, {
|
|
71
82
|
...queueOptions(schedule.queueOptions),
|
|
72
83
|
deadLetter,
|
|
73
84
|
});
|
|
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
|
-
);
|
|
85
|
+
await client.work(schedule.name, schedule.workerOptions, async (jobs: readonly Job<unknown>[]) => {
|
|
86
|
+
for (const job of jobs) {
|
|
87
|
+
await Effect.runPromise(Effect.provide(worker.effect, context), {
|
|
88
|
+
signal: job.signal,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
await client.schedule(schedule.name, schedule.cron, null, schedule.scheduleOptions);
|
|
91
93
|
};
|
|
92
94
|
|
|
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
|
-
);
|
|
95
|
+
export const registrationNames = (registrations: readonly JobRegistration[]): readonly string[] => {
|
|
96
|
+
const names = registrations.map(registrationName);
|
|
101
97
|
if (new Set(names).size !== names.length) {
|
|
102
98
|
throw new TypeError("pg-boss job names must be unique");
|
|
103
99
|
}
|
|
@@ -107,29 +103,20 @@ export const registrationNames = (
|
|
|
107
103
|
export const registerJobs = <R>(
|
|
108
104
|
client: PgBossClient,
|
|
109
105
|
registrations: readonly JobRegistration[],
|
|
110
|
-
context:
|
|
106
|
+
context: Context.Context<R>,
|
|
111
107
|
replaceWorkers: boolean,
|
|
112
|
-
): Effect.Effect<void, import("
|
|
108
|
+
): Effect.Effect<void, import("#error.ts").PgBossError> =>
|
|
113
109
|
Effect.tryPromise({
|
|
110
|
+
catch: (error) => toPgBossError("register", error),
|
|
114
111
|
try: async () => {
|
|
115
112
|
registrationNames(registrations);
|
|
113
|
+
const provided = workerContext(context);
|
|
116
114
|
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
|
-
);
|
|
115
|
+
if (isQueueWorker(registration)) {
|
|
116
|
+
await registerQueue(client, registration, provided, replaceWorkers);
|
|
117
|
+
} else if (isScheduledWorker(registration)) {
|
|
118
|
+
await registerSchedule(client, registration, provided, replaceWorkers);
|
|
131
119
|
}
|
|
132
120
|
}
|
|
133
121
|
},
|
|
134
|
-
catch: (error) => toPgBossError("register", error),
|
|
135
122
|
});
|
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
|
+
catch: (error) => toPgBossError("enqueue", error, queue.name),
|
|
30
|
+
try: () => client.send(queue.name, encoded, options),
|
|
31
|
+
}).pipe(Effect.map(Option.fromNullishOr));
|
|
32
|
+
}),
|
|
49
33
|
),
|
|
50
34
|
health: healthFor(client, names),
|
|
51
35
|
});
|
package/src/service.ts
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
import { Context, Effect, Layer, type Option, type Schema } from "effect";
|
|
2
2
|
import type { ConstructorOptions, SendOptions, StopOptions } from "pg-boss";
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "./
|
|
9
|
-
import type {
|
|
10
|
-
import type { JobsHealth } from "./health.js";
|
|
11
|
-
import type { PgBossClientFactory } from "./internal/client.js";
|
|
12
|
-
import { acquireClient, releaseClient } from "./internal/lifecycle.js";
|
|
13
|
-
import { registerJobs } from "./internal/register.js";
|
|
14
|
-
import { makeService } from "./internal/service.js";
|
|
3
|
+
import type { PgBossClientFactory } from "#internal/client.ts";
|
|
4
|
+
import { acquireClient, releaseClient } from "#internal/lifecycle.ts";
|
|
5
|
+
import { registerJobs, registrationName } from "#internal/register.ts";
|
|
6
|
+
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";
|
|
15
10
|
|
|
16
11
|
export interface PgBossService {
|
|
17
|
-
readonly enqueue: <
|
|
18
|
-
const Name extends string,
|
|
19
|
-
const Payload extends JobPayloadSchema,
|
|
20
|
-
>(
|
|
12
|
+
readonly enqueue: <const Name extends string, const Payload extends JobPayloadSchema>(
|
|
21
13
|
queue: QueueDefinition<Name, Payload>,
|
|
22
14
|
payload: Schema.Schema.Type<Payload>,
|
|
23
15
|
options?: SendOptions,
|
|
24
|
-
) => Effect.Effect<
|
|
25
|
-
Option.Option<string>,
|
|
26
|
-
PgBossError | PgBossPayloadError,
|
|
27
|
-
Payload["EncodingServices"]
|
|
28
|
-
>;
|
|
16
|
+
) => Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError, Payload["EncodingServices"]>;
|
|
29
17
|
readonly health: Effect.Effect<JobsHealth, PgBossError>;
|
|
30
18
|
}
|
|
31
19
|
|
|
@@ -33,95 +21,47 @@ interface PgBossIdentifier {
|
|
|
33
21
|
readonly _pgBossIdentifier: unique symbol;
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
export type PgBossLayerOptions<
|
|
37
|
-
Registrations extends readonly JobRegistration[],
|
|
38
|
-
ErrorRequirements,
|
|
39
|
-
> = ConstructorOptions & {
|
|
40
|
-
/** Override client construction for compatible clients or deterministic tests. */
|
|
24
|
+
export type PgBossLayerOptions<Registrations extends readonly JobRegistration[], ErrorRequirements> = ConstructorOptions & {
|
|
41
25
|
readonly clientFactory?: PgBossClientFactory;
|
|
42
|
-
|
|
26
|
+
// One started client is reference-counted per key, so development module reloads reuse it instead of starting another.
|
|
27
|
+
readonly clientCacheKey?: string | symbol | undefined;
|
|
43
28
|
readonly jobs: Registrations;
|
|
44
|
-
readonly onError?: (
|
|
45
|
-
error: Error,
|
|
46
|
-
) => Effect.Effect<unknown, never, ErrorRequirements>;
|
|
29
|
+
readonly onError?: (error: Error) => Effect.Effect<unknown, never, ErrorRequirements>;
|
|
47
30
|
readonly stop?: StopOptions;
|
|
48
31
|
};
|
|
49
32
|
|
|
50
|
-
export interface PgBossDefinition
|
|
51
|
-
extends
|
|
52
|
-
readonly layer: <
|
|
53
|
-
const Registrations extends readonly JobRegistration[],
|
|
54
|
-
ErrorRequirements = never,
|
|
55
|
-
>(
|
|
33
|
+
export interface PgBossDefinition extends Context.Service<PgBossIdentifier, PgBossService> {
|
|
34
|
+
readonly layer: <const Registrations extends readonly JobRegistration[], ErrorRequirements = never>(
|
|
56
35
|
options: PgBossLayerOptions<Registrations, ErrorRequirements>,
|
|
57
|
-
) => Layer.Layer<
|
|
58
|
-
PgBossIdentifier,
|
|
59
|
-
PgBossError,
|
|
60
|
-
RegistrationRequirements<Registrations> | ErrorRequirements
|
|
61
|
-
>;
|
|
36
|
+
) => Layer.Layer<PgBossIdentifier, PgBossError, RegistrationRequirements<Registrations> | ErrorRequirements>;
|
|
62
37
|
}
|
|
63
38
|
|
|
39
|
+
const logClientError = (error: Error): Effect.Effect<void> => Effect.logError({ event: "pg_boss_error", message: error.message });
|
|
40
|
+
|
|
64
41
|
export const makePgBoss = (identifier: string): PgBossDefinition => {
|
|
65
42
|
const Service = Context.Service<PgBossIdentifier, PgBossService>(identifier);
|
|
66
43
|
|
|
67
|
-
const layer = <
|
|
68
|
-
const Registrations extends readonly JobRegistration[],
|
|
69
|
-
ErrorRequirements = never,
|
|
70
|
-
>(
|
|
44
|
+
const layer = <const Registrations extends readonly JobRegistration[], ErrorRequirements = never>(
|
|
71
45
|
options: PgBossLayerOptions<Registrations, ErrorRequirements>,
|
|
72
|
-
): Layer.Layer<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
> => {
|
|
77
|
-
type Requirements =
|
|
78
|
-
| RegistrationRequirements<Registrations>
|
|
79
|
-
| ErrorRequirements;
|
|
80
|
-
const names = options.jobs.map((registration) =>
|
|
81
|
-
registration._tag === "QueueWorker"
|
|
82
|
-
? (registration as import("./definition.js").QueueWorker).queue.name
|
|
83
|
-
: (registration as import("./definition.js").ScheduledWorker).schedule
|
|
84
|
-
.name,
|
|
85
|
-
);
|
|
86
|
-
const {
|
|
87
|
-
clientFactory,
|
|
88
|
-
developmentCacheKey,
|
|
89
|
-
jobs,
|
|
90
|
-
onError,
|
|
91
|
-
stop,
|
|
92
|
-
...constructorOptions
|
|
93
|
-
} = options;
|
|
46
|
+
): Layer.Layer<PgBossIdentifier, PgBossError, RegistrationRequirements<Registrations> | ErrorRequirements> => {
|
|
47
|
+
type Requirements = RegistrationRequirements<Registrations> | ErrorRequirements;
|
|
48
|
+
const names = options.jobs.map(registrationName);
|
|
49
|
+
const { clientFactory, clientCacheKey, jobs, onError, stop, ...constructorOptions } = options;
|
|
94
50
|
|
|
95
51
|
const acquire = Effect.acquireRelease(
|
|
96
52
|
Effect.gen(function* () {
|
|
97
53
|
const context = yield* Effect.context<Requirements>();
|
|
98
54
|
const acquired = yield* acquireClient({
|
|
55
|
+
clientCacheKey,
|
|
99
56
|
clientFactory,
|
|
100
57
|
constructor: constructorOptions,
|
|
101
|
-
developmentCacheKey,
|
|
102
58
|
});
|
|
103
|
-
const reportError: (
|
|
104
|
-
error: Error,
|
|
105
|
-
) => Effect.Effect<unknown, never, Requirements> =
|
|
106
|
-
onError === undefined
|
|
107
|
-
? (error) =>
|
|
108
|
-
Effect.logError({
|
|
109
|
-
event: "pg_boss_error",
|
|
110
|
-
message: error.message,
|
|
111
|
-
})
|
|
112
|
-
: onError;
|
|
59
|
+
const reportError: (error: Error) => Effect.Effect<unknown, never, Requirements> = onError ?? logClientError;
|
|
113
60
|
const errorListener = (error: Error) => {
|
|
114
|
-
void Effect.runPromise(
|
|
115
|
-
Effect.exit(Effect.provide(reportError(error), context)),
|
|
116
|
-
);
|
|
61
|
+
void Effect.runPromise(Effect.exit(Effect.provide(reportError(error), context)));
|
|
117
62
|
};
|
|
118
63
|
acquired.client.on("error", errorListener);
|
|
119
|
-
const registration = registerJobs(
|
|
120
|
-
acquired.client,
|
|
121
|
-
jobs,
|
|
122
|
-
context,
|
|
123
|
-
acquired.reused,
|
|
124
|
-
);
|
|
64
|
+
const registration = registerJobs(acquired.client, jobs, context, acquired.reused);
|
|
125
65
|
return yield* registration.pipe(
|
|
126
66
|
Effect.as({ acquired, context, errorListener }),
|
|
127
67
|
Effect.onError(() => {
|
|
@@ -138,11 +78,9 @@ export const makePgBoss = (identifier: string): PgBossDefinition => {
|
|
|
138
78
|
|
|
139
79
|
return Layer.effect(
|
|
140
80
|
Service,
|
|
141
|
-
Effect.map(acquire, ({ acquired }) =>
|
|
142
|
-
makeService(acquired.client, names),
|
|
143
|
-
),
|
|
81
|
+
Effect.map(acquire, ({ acquired }) => makeService(acquired.client, names)),
|
|
144
82
|
);
|
|
145
83
|
};
|
|
146
84
|
|
|
147
|
-
return Object.assign(Service, { layer })
|
|
85
|
+
return Object.assign(Service, { layer });
|
|
148
86
|
};
|