@shivaedev/jobs 0.22.0 → 0.23.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +166 -172
  3. package/dist/JobFailure.d.ts +7 -0
  4. package/dist/JobFailure.d.ts.map +1 -0
  5. package/dist/JobFailure.js +8 -0
  6. package/dist/JobFailure.js.map +1 -0
  7. package/dist/server/PgBoss.d.ts +11 -0
  8. package/dist/server/PgBoss.d.ts.map +1 -0
  9. package/dist/server/PgBoss.js +4 -0
  10. package/dist/server/PgBoss.js.map +1 -0
  11. package/dist/server/client.d.ts +1 -1
  12. package/dist/server/client.d.ts.map +1 -1
  13. package/dist/server/health.d.ts +3 -2
  14. package/dist/server/health.d.ts.map +1 -1
  15. package/dist/server/health.js.map +1 -1
  16. package/dist/server/internal/batchHandler.d.ts +2 -1
  17. package/dist/server/internal/batchHandler.d.ts.map +1 -1
  18. package/dist/server/internal/batchHandler.js +49 -31
  19. package/dist/server/internal/batchHandler.js.map +1 -1
  20. package/dist/server/internal/clientErrors.d.ts.map +1 -1
  21. package/dist/server/internal/clientErrors.js +2 -1
  22. package/dist/server/internal/clientErrors.js.map +1 -1
  23. package/dist/server/internal/deferrableAttempt.d.ts +1 -1
  24. package/dist/server/internal/deferrableAttempt.d.ts.map +1 -1
  25. package/dist/server/internal/deferrableAttempt.js +5 -1
  26. package/dist/server/internal/deferrableAttempt.js.map +1 -1
  27. package/dist/server/internal/ensureQueue.d.ts +2 -1
  28. package/dist/server/internal/ensureQueue.d.ts.map +1 -1
  29. package/dist/server/internal/ensureQueue.js +16 -21
  30. package/dist/server/internal/ensureQueue.js.map +1 -1
  31. package/dist/server/internal/failureOutput.d.ts +4 -0
  32. package/dist/server/internal/failureOutput.d.ts.map +1 -0
  33. package/dist/server/internal/failureOutput.js +9 -0
  34. package/dist/server/internal/failureOutput.js.map +1 -0
  35. package/dist/server/internal/lifecycle.d.ts +1 -1
  36. package/dist/server/internal/lifecycle.d.ts.map +1 -1
  37. package/dist/server/internal/lifecycle.js +10 -16
  38. package/dist/server/internal/lifecycle.js.map +1 -1
  39. package/dist/server/internal/listJobs.d.ts.map +1 -1
  40. package/dist/server/internal/listJobs.js +44 -39
  41. package/dist/server/internal/listJobs.js.map +1 -1
  42. package/dist/server/internal/makeService.js +1 -1
  43. package/dist/server/internal/makeService.js.map +1 -1
  44. package/dist/server/internal/register.d.ts +4 -3
  45. package/dist/server/internal/register.d.ts.map +1 -1
  46. package/dist/server/internal/register.js +34 -48
  47. package/dist/server/internal/register.js.map +1 -1
  48. package/dist/server/internal/registerReleasedJobs.d.ts +2 -1
  49. package/dist/server/internal/registerReleasedJobs.d.ts.map +1 -1
  50. package/dist/server/internal/registerReleasedJobs.js +3 -3
  51. package/dist/server/internal/registerReleasedJobs.js.map +1 -1
  52. package/dist/server/internal/registerWorker.d.ts +3 -2
  53. package/dist/server/internal/registerWorker.d.ts.map +1 -1
  54. package/dist/server/internal/registerWorker.js +26 -33
  55. package/dist/server/internal/registerWorker.js.map +1 -1
  56. package/dist/server/internal/retryWake.d.ts +4 -0
  57. package/dist/server/internal/retryWake.d.ts.map +1 -0
  58. package/dist/server/internal/retryWake.js +32 -0
  59. package/dist/server/internal/retryWake.js.map +1 -0
  60. package/dist/server/internal/runQueueWorker.d.ts +5 -0
  61. package/dist/server/internal/runQueueWorker.d.ts.map +1 -0
  62. package/dist/server/internal/runQueueWorker.js +12 -0
  63. package/dist/server/internal/runQueueWorker.js.map +1 -0
  64. package/dist/server/internal/schemaIndexBuilds.d.ts.map +1 -1
  65. package/dist/server/internal/schemaIndexBuilds.js +5 -2
  66. package/dist/server/internal/schemaIndexBuilds.js.map +1 -1
  67. package/dist/server/schema.d.ts.map +1 -1
  68. package/dist/server/schema.js +16 -5
  69. package/dist/server/schema.js.map +1 -1
  70. package/dist/server/service.d.ts +13 -8
  71. package/dist/server/service.d.ts.map +1 -1
  72. package/dist/server/service.js +53 -22
  73. package/dist/server/service.js.map +1 -1
  74. package/package.json +16 -10
  75. package/src/JobFailure.ts +9 -0
  76. package/src/server/PgBoss.ts +8 -0
  77. package/src/server/client.ts +1 -1
  78. package/src/server/health.ts +3 -2
  79. package/src/server/internal/batchHandler.ts +61 -42
  80. package/src/server/internal/clientErrors.ts +2 -1
  81. package/src/server/internal/deferrableAttempt.ts +5 -1
  82. package/src/server/internal/ensureQueue.ts +18 -20
  83. package/src/server/internal/failureOutput.ts +9 -0
  84. package/src/server/internal/lifecycle.ts +13 -16
  85. package/src/server/internal/listJobs.ts +57 -47
  86. package/src/server/internal/makeService.ts +1 -1
  87. package/src/server/internal/register.ts +68 -66
  88. package/src/server/internal/registerReleasedJobs.ts +8 -3
  89. package/src/server/internal/registerWorker.ts +35 -46
  90. package/src/server/internal/retryWake.ts +33 -0
  91. package/src/server/internal/runQueueWorker.ts +19 -0
  92. package/src/server/internal/schemaIndexBuilds.ts +6 -2
  93. package/src/server/schema.ts +22 -6
  94. package/src/server/service.ts +84 -49
@@ -1,5 +1,7 @@
1
- import { Context, Effect, Layer, type Option, type Schema } from "effect";
1
+ import { Context, Effect, Exit, Layer, type Option, Redacted, type Schema, Scope } from "effect";
2
2
  import type { ConstructorOptions, JobWithMetadata, SendOptions, StopOptions } from "pg-boss";
3
+ import { defineService } from "@shivaedev/service/defineService.ts";
4
+ import { genericMethod } from "@shivaedev/service/genericMethod.ts";
3
5
  import type { PgBossClientFactory } from "#client.ts";
4
6
  import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "#definition.ts";
5
7
  import type { PgBossClientOptionsError, PgBossError, PgBossPayloadError, PgBossQueueOptionsError } from "#error.ts";
@@ -9,13 +11,14 @@ import { acquireClient, releaseClient } from "#internal/lifecycle.ts";
9
11
  import { makeService } from "#internal/makeService.ts";
10
12
  import { registrationName } from "#internal/register.ts";
11
13
  import { registerReleasedJobs } from "#internal/registerReleasedJobs.ts";
14
+ import { PgBoss } from "#PgBoss.ts";
12
15
 
13
16
  export interface PgBossService {
14
17
  readonly counts: (queue: Pick<QueueDefinition, "name">) => Effect.Effect<QueueCounts, PgBossError>;
15
18
  readonly deadLetteredJobs: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
16
19
  queue: { readonly name: TName; readonly schema: TPayload },
17
20
  options: JobPageOptions,
18
- ) => Effect.Effect<JobPage<DeadLetteredJob<TPayload["Type"]>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
21
+ ) => Effect.Effect<JobPage<DeadLetteredJob<TPayload["Type"]>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
19
22
  readonly enqueue: <const TName extends string, const TPayload extends JobPayloadSchema, const TQueue extends QueueDefinition<TName, TPayload>>(
20
23
  queue: QueueDefinition<TName, TPayload> & TQueue,
21
24
  payload: NoInfer<Schema.Schema.Type<TPayload>>,
@@ -24,77 +27,109 @@ export interface PgBossService {
24
27
  readonly failedJobs: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
25
28
  queue: { readonly name: TName; readonly schema: TPayload },
26
29
  options: JobPageOptions,
27
- ) => Effect.Effect<JobPage<ListedJob<TPayload["Type"]>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
30
+ ) => Effect.Effect<JobPage<ListedJob<TPayload["Type"]>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
28
31
  readonly getJob: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
29
32
  queue: { readonly name: TName; readonly schema: TPayload },
30
33
  id: string,
31
34
  ) => Effect.Effect<Option.Option<JobWithMetadata<TPayload["Type"]>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
32
- readonly health: Effect.Effect<JobsHealth, PgBossError>;
35
+ readonly health: () => Effect.Effect<JobsHealth, PgBossError>;
33
36
  readonly retry: (queue: Pick<QueueDefinition, "name">, id: string) => Effect.Effect<void, PgBossError>;
34
37
  }
35
38
 
36
- export type PgBossLayerOptions<TRegistrations extends readonly JobRegistration[], TErrorRequirements> = ConstructorOptions & {
39
+ export type PgBossOptions = Omit<ConstructorOptions, "connectionString"> & {
40
+ readonly connectionString?: Redacted.Redacted<string>;
37
41
  readonly clientFactory?: PgBossClientFactory;
38
- // One started client is reference-counted per key, so development module reloads reuse it instead of starting another.
39
42
  readonly clientCacheKey?: string | symbol | undefined;
40
- readonly jobs: TRegistrations;
41
- readonly onError?: (error: Error) => Effect.Effect<unknown, never, TErrorRequirements>;
42
43
  readonly stop?: StopOptions;
43
44
  };
44
45
 
46
+ export function pgBossLayer(options: PgBossOptions): Layer.Layer<PgBoss, PgBossError | PgBossClientOptionsError> {
47
+ const { connectionString, clientFactory, clientCacheKey, stop, ...settings } = options;
48
+ return Layer.effect(
49
+ PgBoss,
50
+ Effect.gen(function* () {
51
+ const dependencies = yield* Effect.context<never>();
52
+ const execute = Effect.runPromiseWith(dependencies);
53
+ const acquired = yield* Effect.acquireRelease(
54
+ acquireClient(
55
+ {
56
+ clientCacheKey,
57
+ clientFactory,
58
+ constructor: {
59
+ ...settings,
60
+ ...(connectionString === undefined ? {} : { connectionString: Redacted.value(connectionString) }),
61
+ },
62
+ },
63
+ (error) => {
64
+ void execute(logClientError(error));
65
+ },
66
+ ),
67
+ (client) => releaseClient(client, stop),
68
+ );
69
+ return { ...acquired, schema: settings.schema, stop };
70
+ }),
71
+ );
72
+ }
73
+
74
+ export interface PgBossLayerOptions<TRegistrations extends readonly JobRegistration[], TErrorRequirements> {
75
+ readonly jobs: TRegistrations;
76
+ readonly onError?: (error: Error) => Effect.Effect<unknown, never, TErrorRequirements>;
77
+ }
78
+
45
79
  export interface PgBossDefinition<TIdentifier extends string = string> extends Context.Service<TIdentifier, PgBossService> {
46
80
  readonly layer: <const TRegistrations extends readonly JobRegistration[], TErrorRequirements = never>(
47
81
  options: PgBossLayerOptions<TRegistrations, TErrorRequirements>,
48
82
  ) => Layer.Layer<
49
83
  TIdentifier,
50
- PgBossError | PgBossQueueOptionsError | PgBossClientOptionsError,
51
- Exclude<RegistrationRequirements<TRegistrations>, TIdentifier> | TErrorRequirements
84
+ PgBossError | PgBossQueueOptionsError,
85
+ PgBoss | Exclude<RegistrationRequirements<TRegistrations>, TIdentifier> | TErrorRequirements
52
86
  >;
53
87
  }
54
88
 
55
89
  export function makePgBoss<const TIdentifier extends string>(identifier: TIdentifier): PgBossDefinition<TIdentifier> {
56
- const Service = Context.Service<TIdentifier, PgBossService>(identifier);
57
-
90
+ function definition<R>(options: PgBossLayerOptions<readonly JobRegistration[], R>, dependencies: Context.Context<R>) {
91
+ return defineService({
92
+ id: identifier,
93
+ initialize: Effect.gen(function* () {
94
+ const acquired = yield* PgBoss;
95
+ const execute = Effect.runPromiseWith(dependencies);
96
+ const workerScope = yield* Scope.make();
97
+ const service = makeService(acquired.client, options.jobs.map(registrationName), acquired.schema);
98
+ const context = Context.add(dependencies, Service, service);
99
+ const reportError = options.onError ?? logClientError;
100
+ const unsubscribe = subscribeClientErrors(acquired.client, (error) => {
101
+ void execute(Effect.exit(Effect.provide(reportError(error), context)));
102
+ });
103
+ yield* Effect.addFinalizer(() => Scope.close(workerScope, Exit.void).pipe(Effect.ensuring(Effect.sync(unsubscribe))));
104
+ yield* registerReleasedJobs(
105
+ acquired.client,
106
+ options.jobs,
107
+ context,
108
+ acquired.reused,
109
+ (effect) => execute(effect),
110
+ acquired.schema,
111
+ acquired.stop?.graceful !== false,
112
+ ).pipe(Effect.provideService(Scope.Scope, workerScope), Effect.provide(dependencies));
113
+ return service;
114
+ }),
115
+ methods: (service) => ({
116
+ counts: service.counts,
117
+ deadLetteredJobs: genericMethod(service.deadLetteredJobs),
118
+ enqueue: genericMethod(service.enqueue),
119
+ failedJobs: genericMethod(service.failedJobs),
120
+ getJob: genericMethod(service.getJob),
121
+ health: service.health,
122
+ retry: service.retry,
123
+ }),
124
+ requires: [PgBoss],
125
+ });
126
+ }
127
+ const Service: Context.Service<TIdentifier, PgBossService> = definition({ jobs: [] }, Context.empty());
58
128
  function layer<const TRegistrations extends readonly JobRegistration[], TErrorRequirements = never>(
59
129
  options: PgBossLayerOptions<TRegistrations, TErrorRequirements>,
60
- ): Layer.Layer<
61
- TIdentifier,
62
- PgBossError | PgBossQueueOptionsError | PgBossClientOptionsError,
63
- Exclude<RegistrationRequirements<TRegistrations>, TIdentifier> | TErrorRequirements
64
- > {
130
+ ) {
65
131
  type Requirements = Exclude<RegistrationRequirements<TRegistrations>, TIdentifier> | TErrorRequirements;
66
- const names = options.jobs.map(registrationName);
67
- const { clientFactory, clientCacheKey, jobs, onError, stop, ...constructorOptions } = options;
68
-
69
- const acquire = Effect.acquireRelease(
70
- Effect.gen(function* () {
71
- const dependencies = yield* Effect.context<Requirements>();
72
- const acquired = yield* acquireClient({
73
- clientCacheKey,
74
- clientFactory,
75
- constructor: constructorOptions,
76
- });
77
- const service = makeService(acquired.client, names, constructorOptions.schema);
78
- const context = Context.add(dependencies, Service, service);
79
- const reportError: (error: Error) => Effect.Effect<unknown, never, Requirements | TIdentifier> = onError ?? logClientError;
80
- function errorListener(error: Error): void {
81
- void Effect.runPromise(Effect.exit(Effect.provide(reportError(error), context)));
82
- }
83
- const unsubscribe = subscribeClientErrors(acquired.client, errorListener);
84
- const registration = registerReleasedJobs(acquired.client, jobs, context, acquired.reused, constructorOptions.schema);
85
- return yield* registration.pipe(
86
- Effect.as({ acquired, service, unsubscribe }),
87
- Effect.onError(() => releaseClient(acquired, stop).pipe(Effect.ensuring(Effect.sync(unsubscribe)))),
88
- );
89
- }),
90
- (resource) => releaseClient(resource.acquired, stop).pipe(Effect.ensuring(Effect.sync(resource.unsubscribe))),
91
- );
92
-
93
- return Layer.effect(
94
- Service,
95
- Effect.map(acquire, ({ service }) => service),
96
- );
132
+ return Layer.unwrap(Effect.map(Effect.context<Requirements>(), (dependencies) => definition(options, dependencies).layer));
97
133
  }
98
-
99
134
  return Object.assign(Service, { layer });
100
135
  }