@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +7 -5
  3. package/dist/definition.d.ts +8 -8
  4. package/dist/definition.d.ts.map +1 -1
  5. package/dist/definition.js +6 -6
  6. package/dist/definition.js.map +1 -1
  7. package/dist/error.d.ts +1 -1
  8. package/dist/error.d.ts.map +1 -1
  9. package/dist/error.js +1 -1
  10. package/dist/error.js.map +1 -1
  11. package/dist/health.d.ts.map +1 -1
  12. package/dist/health.js.map +1 -1
  13. package/dist/index.d.ts +5 -5
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/internal/client.d.ts +2 -2
  17. package/dist/internal/client.d.ts.map +1 -1
  18. package/dist/internal/client.js.map +1 -1
  19. package/dist/internal/health.d.ts +3 -3
  20. package/dist/internal/health.d.ts.map +1 -1
  21. package/dist/internal/health.js +4 -7
  22. package/dist/internal/health.js.map +1 -1
  23. package/dist/internal/lifecycle.d.ts +5 -5
  24. package/dist/internal/lifecycle.d.ts.map +1 -1
  25. package/dist/internal/lifecycle.js +24 -15
  26. package/dist/internal/lifecycle.js.map +1 -1
  27. package/dist/internal/register.d.ts +5 -4
  28. package/dist/internal/register.d.ts.map +1 -1
  29. package/dist/internal/register.js +30 -14
  30. package/dist/internal/register.js.map +1 -1
  31. package/dist/internal/service.d.ts +2 -2
  32. package/dist/internal/service.d.ts.map +1 -1
  33. package/dist/internal/service.js +2 -2
  34. package/dist/internal/service.js.map +1 -1
  35. package/dist/service.d.ts +5 -6
  36. package/dist/service.d.ts.map +1 -1
  37. package/dist/service.js +8 -15
  38. package/dist/service.js.map +1 -1
  39. package/package.json +35 -29
  40. package/src/definition.ts +22 -46
  41. package/src/error.ts +4 -13
  42. package/src/health.ts +1 -2
  43. package/src/index.ts +8 -8
  44. package/src/internal/client.ts +7 -33
  45. package/src/internal/health.ts +7 -20
  46. package/src/internal/lifecycle.ts +35 -35
  47. package/src/internal/register.ts +58 -71
  48. package/src/internal/service.ts +20 -36
  49. 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 "../error.js";
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 globalCache = globalThis as typeof globalThis & {
17
- [CacheKey]?: Map<string | symbol, CachedClient>;
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 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 = clientCache.get(cacheKey);
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) clientCache.delete(cacheKey);
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 = clientCache.get(acquired.cacheKey);
114
- if (entry === undefined) return;
110
+ const entry = cachedClient(acquired.cacheKey);
111
+ if (entry === undefined) {
112
+ return;
113
+ }
115
114
  entry.references -= 1;
116
- if (entry.references > 0) return;
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)));
@@ -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
- 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";
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 runQueueWorker = <R>(
21
- worker: QueueWorker<JobPayloadSchema, R>,
22
- job: Job<unknown>,
23
- context: import("effect").Context.Context<R>,
24
- ): Promise<unknown> =>
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
- ) as Effect.Effect<unknown, unknown>,
47
+ ),
38
48
  { signal: job.signal },
39
49
  );
40
50
 
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> => {
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) await client.offWork(name);
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) await runQueueWorker(worker, job, context);
63
+ for (const job of jobs) {
64
+ await runQueueWorker(worker, job, context);
65
+ }
57
66
  });
58
67
  };
59
68
 
60
- const registerSchedule = async <R>(
69
+ const registerSchedule = async (
61
70
  client: PgBossClient,
62
- worker: ScheduledWorker<R>,
63
- context: import("effect").Context.Context<R>,
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) await client.offWork(schedule.name);
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
- 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
- );
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
- 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
- );
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: import("effect").Context.Context<R>,
106
+ context: Context.Context<R>,
111
107
  replaceWorkers: boolean,
112
- ): Effect.Effect<void, import("../error.js").PgBossError> =>
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._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
- );
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
  });
@@ -1,17 +1,10 @@
1
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";
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
- 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
- ),
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
- 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";
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
- readonly developmentCacheKey?: string | symbol;
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 Context.Service<PgBossIdentifier, PgBossService> {
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
- 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;
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 }) as PgBossDefinition;
85
+ return Object.assign(Service, { layer });
148
86
  };