@shivaedev/effect-pg-boss 0.0.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 +11 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/definition.d.ts +61 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +39 -0
- package/dist/definition.js.map +1 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +11 -0
- package/dist/error.js.map +1 -0
- package/dist/health.d.ts +18 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +2 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/client.d.ts +16 -0
- package/dist/internal/client.d.ts.map +1 -0
- package/dist/internal/client.js +3 -0
- package/dist/internal/client.js.map +1 -0
- package/dist/internal/health.d.ts +5 -0
- package/dist/internal/health.d.ts.map +1 -0
- package/dist/internal/health.js +28 -0
- package/dist/internal/health.js.map +1 -0
- package/dist/internal/lifecycle.d.ts +16 -0
- package/dist/internal/lifecycle.d.ts.map +1 -0
- package/dist/internal/lifecycle.js +83 -0
- package/dist/internal/lifecycle.js.map +1 -0
- package/dist/internal/register.d.ts +6 -0
- package/dist/internal/register.d.ts.map +1 -0
- package/dist/internal/register.js +72 -0
- package/dist/internal/register.js.map +1 -0
- package/dist/internal/service.d.ts +4 -0
- package/dist/internal/service.d.ts.map +1 -0
- package/dist/internal/service.js +24 -0
- package/dist/internal/service.js.map +1 -0
- package/dist/service.d.ts +27 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +43 -0
- package/dist/service.js.map +1 -0
- package/package.json +59 -0
- package/src/definition.ts +144 -0
- package/src/error.ts +31 -0
- package/src/health.ts +20 -0
- package/src/index.ts +33 -0
- package/src/internal/client.ts +45 -0
- package/src/internal/health.ts +47 -0
- package/src/internal/lifecycle.ts +121 -0
- package/src/internal/register.ts +135 -0
- package/src/internal/service.ts +51 -0
- package/src/service.ts +148 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { ConstructorOptions, StopOptions } from "pg-boss";
|
|
3
|
+
import { toPgBossError } from "../error.js";
|
|
4
|
+
import {
|
|
5
|
+
defaultClientFactory,
|
|
6
|
+
type PgBossClient,
|
|
7
|
+
type PgBossClientFactory,
|
|
8
|
+
} from "./client.js";
|
|
9
|
+
|
|
10
|
+
interface CachedClient {
|
|
11
|
+
readonly client: Promise<PgBossClient>;
|
|
12
|
+
references: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CacheKey = Symbol.for("@shivaedev/effect-pg-boss/client-cache");
|
|
16
|
+
const globalCache = globalThis as typeof globalThis & {
|
|
17
|
+
[CacheKey]?: Map<string | symbol, CachedClient>;
|
|
18
|
+
};
|
|
19
|
+
const clientCache =
|
|
20
|
+
globalCache[CacheKey] ?? new Map<string | symbol, CachedClient>();
|
|
21
|
+
globalCache[CacheKey] = clientCache;
|
|
22
|
+
|
|
23
|
+
export interface AcquireClientOptions {
|
|
24
|
+
readonly clientFactory?: PgBossClientFactory;
|
|
25
|
+
readonly constructor: ConstructorOptions;
|
|
26
|
+
readonly developmentCacheKey?: string | symbol;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AcquiredClient {
|
|
30
|
+
readonly client: PgBossClient;
|
|
31
|
+
readonly cacheKey?: string | symbol;
|
|
32
|
+
readonly reused: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const startClient = (options: AcquireClientOptions): Promise<PgBossClient> => {
|
|
36
|
+
const client = (options.clientFactory ?? defaultClientFactory)(
|
|
37
|
+
options.constructor,
|
|
38
|
+
);
|
|
39
|
+
return client.start().then(
|
|
40
|
+
() => client,
|
|
41
|
+
async (error) => {
|
|
42
|
+
try {
|
|
43
|
+
await client.stop();
|
|
44
|
+
} catch {
|
|
45
|
+
// Preserve the startup failure; stop is best effort on failed acquisition.
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const acquireClient = (
|
|
53
|
+
options: AcquireClientOptions,
|
|
54
|
+
): Effect.Effect<AcquiredClient, import("../error.js").PgBossError> =>
|
|
55
|
+
Effect.tryPromise({
|
|
56
|
+
try: async () => {
|
|
57
|
+
const cacheKey =
|
|
58
|
+
process.env.NODE_ENV === "production"
|
|
59
|
+
? undefined
|
|
60
|
+
: options.developmentCacheKey;
|
|
61
|
+
if (cacheKey === undefined) {
|
|
62
|
+
return {
|
|
63
|
+
client: await startClient(options),
|
|
64
|
+
reused: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const existing = clientCache.get(cacheKey);
|
|
69
|
+
if (existing !== undefined) {
|
|
70
|
+
existing.references += 1;
|
|
71
|
+
try {
|
|
72
|
+
return {
|
|
73
|
+
cacheKey,
|
|
74
|
+
client: await existing.client,
|
|
75
|
+
reused: true,
|
|
76
|
+
};
|
|
77
|
+
} catch (error) {
|
|
78
|
+
existing.references -= 1;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const entry: CachedClient = {
|
|
84
|
+
client: startClient(options),
|
|
85
|
+
references: 1,
|
|
86
|
+
};
|
|
87
|
+
clientCache.set(cacheKey, entry);
|
|
88
|
+
try {
|
|
89
|
+
return {
|
|
90
|
+
cacheKey,
|
|
91
|
+
client: await entry.client,
|
|
92
|
+
reused: false,
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (clientCache.get(cacheKey) === entry) clientCache.delete(cacheKey);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
catch: (error) => toPgBossError("start", error),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const releaseClient = (
|
|
103
|
+
acquired: AcquiredClient,
|
|
104
|
+
stopOptions?: StopOptions,
|
|
105
|
+
): Effect.Effect<void> =>
|
|
106
|
+
Effect.tryPromise({
|
|
107
|
+
try: async () => {
|
|
108
|
+
if (acquired.cacheKey === undefined) {
|
|
109
|
+
await acquired.client.stop(stopOptions);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const entry = clientCache.get(acquired.cacheKey);
|
|
114
|
+
if (entry === undefined) return;
|
|
115
|
+
entry.references -= 1;
|
|
116
|
+
if (entry.references > 0) return;
|
|
117
|
+
clientCache.delete(acquired.cacheKey);
|
|
118
|
+
await acquired.client.stop(stopOptions);
|
|
119
|
+
},
|
|
120
|
+
catch: (error) => toPgBossError("stop", error),
|
|
121
|
+
}).pipe(Effect.catch((error) => Effect.logError(error)));
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Effect, Redacted, Schema } from "effect";
|
|
2
|
+
import type { Job } from "pg-boss";
|
|
3
|
+
import {
|
|
4
|
+
type JobPayloadSchema,
|
|
5
|
+
type JobRegistration,
|
|
6
|
+
jobContext,
|
|
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";
|
|
13
|
+
|
|
14
|
+
const queueOptions = (options: Readonly<Record<string, unknown>>) => ({
|
|
15
|
+
retryBackoff: true,
|
|
16
|
+
retryLimit: 3,
|
|
17
|
+
...options,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const runQueueWorker = <R>(
|
|
21
|
+
worker: QueueWorker<JobPayloadSchema, R>,
|
|
22
|
+
job: Job<unknown>,
|
|
23
|
+
context: import("effect").Context.Context<R>,
|
|
24
|
+
): Promise<unknown> =>
|
|
25
|
+
Effect.runPromise(
|
|
26
|
+
Schema.decodeUnknownEffect(worker.queue.schema)(job.data).pipe(
|
|
27
|
+
Effect.mapError(
|
|
28
|
+
(error) =>
|
|
29
|
+
new PgBossPayloadError({
|
|
30
|
+
direction: "decode",
|
|
31
|
+
original: Redacted.make(error),
|
|
32
|
+
queue: worker.queue.name,
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
Effect.flatMap((payload) => worker.handler(payload, jobContext(job))),
|
|
36
|
+
Effect.provide(context),
|
|
37
|
+
) as Effect.Effect<unknown, unknown>,
|
|
38
|
+
{ signal: job.signal },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const registerQueue = async <R>(
|
|
42
|
+
client: PgBossClient,
|
|
43
|
+
worker: QueueWorker<JobPayloadSchema, R>,
|
|
44
|
+
context: import("effect").Context.Context<R>,
|
|
45
|
+
replaceWorker: boolean,
|
|
46
|
+
): Promise<void> => {
|
|
47
|
+
const name = worker.queue.name;
|
|
48
|
+
const deadLetter = deadLetterQueueName(name);
|
|
49
|
+
if (replaceWorker) await client.offWork(name);
|
|
50
|
+
await client.createQueue(deadLetter);
|
|
51
|
+
await client.createQueue(name, {
|
|
52
|
+
...queueOptions(worker.queue.queueOptions),
|
|
53
|
+
deadLetter,
|
|
54
|
+
});
|
|
55
|
+
await client.work(name, worker.queue.workerOptions, async (jobs) => {
|
|
56
|
+
for (const job of jobs) await runQueueWorker(worker, job, context);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const registerSchedule = async <R>(
|
|
61
|
+
client: PgBossClient,
|
|
62
|
+
worker: ScheduledWorker<R>,
|
|
63
|
+
context: import("effect").Context.Context<R>,
|
|
64
|
+
replaceWorker: boolean,
|
|
65
|
+
): Promise<void> => {
|
|
66
|
+
const { schedule } = worker;
|
|
67
|
+
const deadLetter = deadLetterQueueName(schedule.name);
|
|
68
|
+
if (replaceWorker) await client.offWork(schedule.name);
|
|
69
|
+
await client.createQueue(deadLetter);
|
|
70
|
+
await client.createQueue(schedule.name, {
|
|
71
|
+
...queueOptions(schedule.queueOptions),
|
|
72
|
+
deadLetter,
|
|
73
|
+
});
|
|
74
|
+
await client.work(
|
|
75
|
+
schedule.name,
|
|
76
|
+
schedule.workerOptions,
|
|
77
|
+
async (jobs: readonly Job<unknown>[]) => {
|
|
78
|
+
for (const job of jobs) {
|
|
79
|
+
await Effect.runPromise(Effect.provide(worker.effect, context), {
|
|
80
|
+
signal: job.signal,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
await client.schedule(
|
|
86
|
+
schedule.name,
|
|
87
|
+
schedule.cron,
|
|
88
|
+
null,
|
|
89
|
+
schedule.scheduleOptions,
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const registrationNames = (
|
|
94
|
+
registrations: readonly JobRegistration[],
|
|
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
|
+
);
|
|
101
|
+
if (new Set(names).size !== names.length) {
|
|
102
|
+
throw new TypeError("pg-boss job names must be unique");
|
|
103
|
+
}
|
|
104
|
+
return names;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const registerJobs = <R>(
|
|
108
|
+
client: PgBossClient,
|
|
109
|
+
registrations: readonly JobRegistration[],
|
|
110
|
+
context: import("effect").Context.Context<R>,
|
|
111
|
+
replaceWorkers: boolean,
|
|
112
|
+
): Effect.Effect<void, import("../error.js").PgBossError> =>
|
|
113
|
+
Effect.tryPromise({
|
|
114
|
+
try: async () => {
|
|
115
|
+
registrationNames(registrations);
|
|
116
|
+
for (const registration of registrations) {
|
|
117
|
+
if (registration._tag === "QueueWorker") {
|
|
118
|
+
await registerQueue(
|
|
119
|
+
client,
|
|
120
|
+
registration as QueueWorker<JobPayloadSchema, R>,
|
|
121
|
+
context,
|
|
122
|
+
replaceWorkers,
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
await registerSchedule(
|
|
126
|
+
client,
|
|
127
|
+
registration as ScheduledWorker<R>,
|
|
128
|
+
context,
|
|
129
|
+
replaceWorkers,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
catch: (error) => toPgBossError("register", error),
|
|
135
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
|
+
import {
|
|
3
|
+
type PgBossError,
|
|
4
|
+
PgBossPayloadError,
|
|
5
|
+
toPgBossError,
|
|
6
|
+
} from "../error.js";
|
|
7
|
+
import type { PgBossService } from "../service.js";
|
|
8
|
+
import type { PgBossClient } from "./client.js";
|
|
9
|
+
import { healthFor } from "./health.js";
|
|
10
|
+
|
|
11
|
+
export const makeService = (
|
|
12
|
+
client: PgBossClient,
|
|
13
|
+
names: readonly string[],
|
|
14
|
+
): PgBossService => ({
|
|
15
|
+
enqueue: (queue, payload, options) =>
|
|
16
|
+
Schema.encodeUnknownEffect(queue.schema)(payload).pipe(
|
|
17
|
+
Effect.mapError(
|
|
18
|
+
(error) =>
|
|
19
|
+
new PgBossPayloadError({
|
|
20
|
+
direction: "encode",
|
|
21
|
+
original: Redacted.make(error),
|
|
22
|
+
queue: queue.name,
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
Effect.flatMap(
|
|
26
|
+
(
|
|
27
|
+
encoded,
|
|
28
|
+
): Effect.Effect<
|
|
29
|
+
Option.Option<string>,
|
|
30
|
+
PgBossError | PgBossPayloadError
|
|
31
|
+
> => {
|
|
32
|
+
if (typeof encoded !== "object" || encoded === null) {
|
|
33
|
+
return Effect.fail(
|
|
34
|
+
new PgBossPayloadError({
|
|
35
|
+
direction: "encode",
|
|
36
|
+
original: Redacted.make(
|
|
37
|
+
new TypeError("pg-boss payloads must encode to objects"),
|
|
38
|
+
),
|
|
39
|
+
queue: queue.name,
|
|
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
|
+
),
|
|
49
|
+
),
|
|
50
|
+
health: healthFor(client, names),
|
|
51
|
+
});
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Context, Effect, Layer, type Option, type Schema } from "effect";
|
|
2
|
+
import type { ConstructorOptions, SendOptions, StopOptions } from "pg-boss";
|
|
3
|
+
import type {
|
|
4
|
+
JobPayloadSchema,
|
|
5
|
+
JobRegistration,
|
|
6
|
+
QueueDefinition,
|
|
7
|
+
RegistrationRequirements,
|
|
8
|
+
} from "./definition.js";
|
|
9
|
+
import type { PgBossError, PgBossPayloadError } from "./error.js";
|
|
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";
|
|
15
|
+
|
|
16
|
+
export interface PgBossService {
|
|
17
|
+
readonly enqueue: <
|
|
18
|
+
const Name extends string,
|
|
19
|
+
const Payload extends JobPayloadSchema,
|
|
20
|
+
>(
|
|
21
|
+
queue: QueueDefinition<Name, Payload>,
|
|
22
|
+
payload: Schema.Schema.Type<Payload>,
|
|
23
|
+
options?: SendOptions,
|
|
24
|
+
) => Effect.Effect<
|
|
25
|
+
Option.Option<string>,
|
|
26
|
+
PgBossError | PgBossPayloadError,
|
|
27
|
+
Payload["EncodingServices"]
|
|
28
|
+
>;
|
|
29
|
+
readonly health: Effect.Effect<JobsHealth, PgBossError>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PgBossIdentifier {
|
|
33
|
+
readonly _pgBossIdentifier: unique symbol;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PgBossLayerOptions<
|
|
37
|
+
Registrations extends readonly JobRegistration[],
|
|
38
|
+
ErrorRequirements,
|
|
39
|
+
> = ConstructorOptions & {
|
|
40
|
+
/** Override client construction for compatible clients or deterministic tests. */
|
|
41
|
+
readonly clientFactory?: PgBossClientFactory;
|
|
42
|
+
readonly developmentCacheKey?: string | symbol;
|
|
43
|
+
readonly jobs: Registrations;
|
|
44
|
+
readonly onError?: (
|
|
45
|
+
error: Error,
|
|
46
|
+
) => Effect.Effect<unknown, never, ErrorRequirements>;
|
|
47
|
+
readonly stop?: StopOptions;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export interface PgBossDefinition
|
|
51
|
+
extends Context.Service<PgBossIdentifier, PgBossService> {
|
|
52
|
+
readonly layer: <
|
|
53
|
+
const Registrations extends readonly JobRegistration[],
|
|
54
|
+
ErrorRequirements = never,
|
|
55
|
+
>(
|
|
56
|
+
options: PgBossLayerOptions<Registrations, ErrorRequirements>,
|
|
57
|
+
) => Layer.Layer<
|
|
58
|
+
PgBossIdentifier,
|
|
59
|
+
PgBossError,
|
|
60
|
+
RegistrationRequirements<Registrations> | ErrorRequirements
|
|
61
|
+
>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const makePgBoss = (identifier: string): PgBossDefinition => {
|
|
65
|
+
const Service = Context.Service<PgBossIdentifier, PgBossService>(identifier);
|
|
66
|
+
|
|
67
|
+
const layer = <
|
|
68
|
+
const Registrations extends readonly JobRegistration[],
|
|
69
|
+
ErrorRequirements = never,
|
|
70
|
+
>(
|
|
71
|
+
options: PgBossLayerOptions<Registrations, ErrorRequirements>,
|
|
72
|
+
): Layer.Layer<
|
|
73
|
+
PgBossIdentifier,
|
|
74
|
+
PgBossError,
|
|
75
|
+
RegistrationRequirements<Registrations> | ErrorRequirements
|
|
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;
|
|
94
|
+
|
|
95
|
+
const acquire = Effect.acquireRelease(
|
|
96
|
+
Effect.gen(function* () {
|
|
97
|
+
const context = yield* Effect.context<Requirements>();
|
|
98
|
+
const acquired = yield* acquireClient({
|
|
99
|
+
clientFactory,
|
|
100
|
+
constructor: constructorOptions,
|
|
101
|
+
developmentCacheKey,
|
|
102
|
+
});
|
|
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;
|
|
113
|
+
const errorListener = (error: Error) => {
|
|
114
|
+
void Effect.runPromise(
|
|
115
|
+
Effect.exit(Effect.provide(reportError(error), context)),
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
acquired.client.on("error", errorListener);
|
|
119
|
+
const registration = registerJobs(
|
|
120
|
+
acquired.client,
|
|
121
|
+
jobs,
|
|
122
|
+
context,
|
|
123
|
+
acquired.reused,
|
|
124
|
+
);
|
|
125
|
+
return yield* registration.pipe(
|
|
126
|
+
Effect.as({ acquired, context, errorListener }),
|
|
127
|
+
Effect.onError(() => {
|
|
128
|
+
acquired.client.off("error", errorListener);
|
|
129
|
+
return releaseClient(acquired, stop);
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}),
|
|
133
|
+
(resource) => {
|
|
134
|
+
resource.acquired.client.off("error", resource.errorListener);
|
|
135
|
+
return releaseClient(resource.acquired, stop);
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return Layer.effect(
|
|
140
|
+
Service,
|
|
141
|
+
Effect.map(acquire, ({ acquired }) =>
|
|
142
|
+
makeService(acquired.client, names),
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return Object.assign(Service, { layer }) as PgBossDefinition;
|
|
148
|
+
};
|