@shivaedev/jobs 0.25.0 → 0.26.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 (157) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +44 -19
  3. package/dist/server/adapters/clientErrorReporter.d.ts +3 -0
  4. package/dist/server/adapters/clientErrorReporter.d.ts.map +1 -0
  5. package/dist/server/adapters/clientErrorReporter.js +8 -0
  6. package/dist/server/adapters/clientErrorReporter.js.map +1 -0
  7. package/dist/server/adapters/installSchema.d.ts +5 -0
  8. package/dist/server/adapters/installSchema.d.ts.map +1 -0
  9. package/dist/server/adapters/installSchema.js +57 -0
  10. package/dist/server/adapters/installSchema.js.map +1 -0
  11. package/dist/server/adapters/registerWorker.d.ts +5 -0
  12. package/dist/server/adapters/registerWorker.d.ts.map +1 -0
  13. package/dist/server/{internal → adapters}/registerWorker.js +3 -2
  14. package/dist/server/adapters/registerWorker.js.map +1 -0
  15. package/dist/server/{internal → adapters}/runSchemaPlan.d.ts.map +1 -1
  16. package/dist/server/{internal → adapters}/runSchemaPlan.js.map +1 -1
  17. package/dist/server/{internal → adapters}/schemaIndexBuilds.d.ts.map +1 -1
  18. package/dist/server/{internal → adapters}/schemaIndexBuilds.js.map +1 -1
  19. package/dist/server/{internal → adapters}/testingExpirations.d.ts.map +1 -1
  20. package/dist/server/{internal → adapters}/testingExpirations.js.map +1 -1
  21. package/dist/server/{internal → adapters}/transactionDb.d.ts.map +1 -1
  22. package/dist/server/{internal → adapters}/transactionDb.js.map +1 -1
  23. package/dist/server/adapters/workerDiagnostics.d.ts +3 -0
  24. package/dist/server/adapters/workerDiagnostics.d.ts.map +1 -0
  25. package/dist/server/adapters/workerDiagnostics.js +24 -0
  26. package/dist/server/adapters/workerDiagnostics.js.map +1 -0
  27. package/dist/server/client.d.ts +0 -1
  28. package/dist/server/client.d.ts.map +1 -1
  29. package/dist/server/client.js +1 -2
  30. package/dist/server/client.js.map +1 -1
  31. package/dist/server/defaultClientFactory.d.ts +3 -0
  32. package/dist/server/defaultClientFactory.d.ts.map +1 -0
  33. package/dist/server/{internal/nativeClient.js → defaultClientFactory.js} +2 -2
  34. package/dist/server/defaultClientFactory.js.map +1 -0
  35. package/dist/server/defineSchedule.d.ts +3 -0
  36. package/dist/server/defineSchedule.d.ts.map +1 -0
  37. package/dist/server/{internal/scheduleDefinition.js → defineSchedule.js} +2 -2
  38. package/dist/server/defineSchedule.js.map +1 -0
  39. package/dist/server/definition.d.ts +0 -1
  40. package/dist/server/definition.d.ts.map +1 -1
  41. package/dist/server/definition.js +0 -4
  42. package/dist/server/definition.js.map +1 -1
  43. package/dist/server/error.d.ts +11 -1
  44. package/dist/server/error.d.ts.map +1 -1
  45. package/dist/server/error.js +6 -1
  46. package/dist/server/error.js.map +1 -1
  47. package/dist/server/health.d.ts +1 -0
  48. package/dist/server/health.d.ts.map +1 -1
  49. package/dist/server/internal/adapters/testingFetches.d.ts +7 -0
  50. package/dist/server/internal/adapters/testingFetches.d.ts.map +1 -0
  51. package/dist/server/internal/adapters/testingFetches.js +30 -0
  52. package/dist/server/internal/adapters/testingFetches.js.map +1 -0
  53. package/dist/server/internal/batchHandler.d.ts +1 -2
  54. package/dist/server/internal/batchHandler.d.ts.map +1 -1
  55. package/dist/server/internal/batchHandler.js +9 -1
  56. package/dist/server/internal/batchHandler.js.map +1 -1
  57. package/dist/server/internal/deadLetterResolutionPlan.d.ts +2 -0
  58. package/dist/server/internal/deadLetterResolutionPlan.d.ts.map +1 -0
  59. package/dist/server/internal/deadLetterResolutionPlan.js +28 -0
  60. package/dist/server/internal/deadLetterResolutionPlan.js.map +1 -0
  61. package/dist/server/internal/enqueueFor.d.ts.map +1 -1
  62. package/dist/server/internal/enqueueFor.js +2 -2
  63. package/dist/server/internal/enqueueFor.js.map +1 -1
  64. package/dist/server/internal/jobControl.d.ts.map +1 -1
  65. package/dist/server/internal/jobControl.js +2 -1
  66. package/dist/server/internal/jobControl.js.map +1 -1
  67. package/dist/server/internal/jobStorePlans.d.ts +2 -0
  68. package/dist/server/internal/jobStorePlans.d.ts.map +1 -0
  69. package/dist/server/internal/jobStorePlans.js +6 -0
  70. package/dist/server/internal/jobStorePlans.js.map +1 -0
  71. package/dist/server/internal/lifecycle.d.ts +1 -1
  72. package/dist/server/internal/lifecycle.d.ts.map +1 -1
  73. package/dist/server/internal/lifecycle.js +5 -3
  74. package/dist/server/internal/lifecycle.js.map +1 -1
  75. package/dist/server/internal/listJobs.d.ts.map +1 -1
  76. package/dist/server/internal/listJobs.js +3 -2
  77. package/dist/server/internal/listJobs.js.map +1 -1
  78. package/dist/server/internal/register.d.ts +1 -2
  79. package/dist/server/internal/register.d.ts.map +1 -1
  80. package/dist/server/internal/register.js +8 -8
  81. package/dist/server/internal/register.js.map +1 -1
  82. package/dist/server/internal/registerReleasedJobs.d.ts +1 -2
  83. package/dist/server/internal/registerReleasedJobs.d.ts.map +1 -1
  84. package/dist/server/internal/registerReleasedJobs.js +3 -3
  85. package/dist/server/internal/registerReleasedJobs.js.map +1 -1
  86. package/dist/server/internal/runQueueWorker.d.ts.map +1 -1
  87. package/dist/server/internal/runQueueWorker.js +3 -2
  88. package/dist/server/internal/runQueueWorker.js.map +1 -1
  89. package/dist/server/internal/storedRows.js +1 -1
  90. package/dist/server/internal/storedRows.js.map +1 -1
  91. package/dist/server/internal/testingClient.d.ts.map +1 -1
  92. package/dist/server/internal/testingClient.js +1 -1
  93. package/dist/server/internal/testingClient.js.map +1 -1
  94. package/dist/server/internal/testingWaits.d.ts +16 -0
  95. package/dist/server/internal/testingWaits.d.ts.map +1 -0
  96. package/dist/server/internal/testingWaits.js +56 -0
  97. package/dist/server/internal/testingWaits.js.map +1 -0
  98. package/dist/server/schema.d.ts.map +1 -1
  99. package/dist/server/schema.js +11 -53
  100. package/dist/server/schema.js.map +1 -1
  101. package/dist/server/service.d.ts +3 -3
  102. package/dist/server/service.d.ts.map +1 -1
  103. package/dist/server/service.js +6 -10
  104. package/dist/server/service.js.map +1 -1
  105. package/dist/server/testing.d.ts +5 -0
  106. package/dist/server/testing.d.ts.map +1 -1
  107. package/dist/server/testing.js +7 -2
  108. package/dist/server/testing.js.map +1 -1
  109. package/package.json +4 -4
  110. package/src/server/adapters/clientErrorReporter.ts +8 -0
  111. package/src/server/adapters/installSchema.ts +68 -0
  112. package/src/server/{internal → adapters}/registerWorker.ts +6 -7
  113. package/src/server/adapters/workerDiagnostics.ts +28 -0
  114. package/src/server/client.ts +0 -3
  115. package/src/server/{internal/nativeClient.ts → defaultClientFactory.ts} +1 -1
  116. package/src/server/{internal/scheduleDefinition.ts → defineSchedule.ts} +1 -1
  117. package/src/server/definition.ts +0 -5
  118. package/src/server/error.ts +11 -1
  119. package/src/server/health.ts +1 -0
  120. package/src/server/internal/adapters/testingFetches.ts +36 -0
  121. package/src/server/internal/batchHandler.ts +11 -8
  122. package/src/server/internal/deadLetterResolutionPlan.ts +28 -0
  123. package/src/server/internal/enqueueFor.ts +2 -2
  124. package/src/server/internal/jobControl.ts +2 -1
  125. package/src/server/internal/jobStorePlans.ts +6 -0
  126. package/src/server/internal/lifecycle.ts +6 -3
  127. package/src/server/internal/listJobs.ts +11 -3
  128. package/src/server/internal/register.ts +6 -21
  129. package/src/server/internal/registerReleasedJobs.ts +2 -6
  130. package/src/server/internal/runQueueWorker.ts +3 -1
  131. package/src/server/internal/storedRows.ts +1 -1
  132. package/src/server/internal/testingClient.ts +1 -1
  133. package/src/server/internal/testingWaits.ts +98 -0
  134. package/src/server/schema.ts +14 -65
  135. package/src/server/service.ts +12 -21
  136. package/src/server/testing.ts +11 -2
  137. package/dist/server/internal/nativeClient.d.ts +0 -3
  138. package/dist/server/internal/nativeClient.d.ts.map +0 -1
  139. package/dist/server/internal/nativeClient.js.map +0 -1
  140. package/dist/server/internal/registerWorker.d.ts +0 -6
  141. package/dist/server/internal/registerWorker.d.ts.map +0 -1
  142. package/dist/server/internal/registerWorker.js.map +0 -1
  143. package/dist/server/internal/scheduleDefinition.d.ts +0 -3
  144. package/dist/server/internal/scheduleDefinition.d.ts.map +0 -1
  145. package/dist/server/internal/scheduleDefinition.js.map +0 -1
  146. /package/dist/server/{internal → adapters}/runSchemaPlan.d.ts +0 -0
  147. /package/dist/server/{internal → adapters}/runSchemaPlan.js +0 -0
  148. /package/dist/server/{internal → adapters}/schemaIndexBuilds.d.ts +0 -0
  149. /package/dist/server/{internal → adapters}/schemaIndexBuilds.js +0 -0
  150. /package/dist/server/{internal → adapters}/testingExpirations.d.ts +0 -0
  151. /package/dist/server/{internal → adapters}/testingExpirations.js +0 -0
  152. /package/dist/server/{internal → adapters}/transactionDb.d.ts +0 -0
  153. /package/dist/server/{internal → adapters}/transactionDb.js +0 -0
  154. /package/src/server/{internal → adapters}/runSchemaPlan.ts +0 -0
  155. /package/src/server/{internal → adapters}/schemaIndexBuilds.ts +0 -0
  156. /package/src/server/{internal → adapters}/testingExpirations.ts +0 -0
  157. /package/src/server/{internal → adapters}/transactionDb.ts +0 -0
@@ -0,0 +1,98 @@
1
+ import { Clock, Duration, Effect, Option, Redacted } from "effect";
2
+ import type { JobWithMetadata, PgBoss } from "pg-boss";
3
+ import type { JobPayloadSchema, QueueDefinition } from "#definition.ts";
4
+ import { type PgBossPayloadError, PgBossTestingError, PgBossWaitError } from "#error.ts";
5
+ import type { TestingFetches } from "#internal/adapters/testingFetches.ts";
6
+ import { decodeStoredData } from "#internal/storedRows.ts";
7
+ import { testingOperation } from "#internal/testingReadout.ts";
8
+ import type { PgBossTestingWaitOptions } from "#testing.ts";
9
+
10
+ interface Reading<A> {
11
+ readonly arrived: Option.Option<A>;
12
+ readonly seen: string;
13
+ }
14
+
15
+ interface Signal {
16
+ readonly revision: Effect.Effect<number>;
17
+ readonly waitForChange: (after: number) => Effect.Effect<number>;
18
+ }
19
+
20
+ function waitUntil<A, E, R>(
21
+ signal: Signal,
22
+ waitingFor: string,
23
+ read: Effect.Effect<Reading<A>, E, R>,
24
+ options: PgBossTestingWaitOptions | undefined,
25
+ ) {
26
+ const within = Duration.fromInputUnsafe(options?.within ?? "20 seconds");
27
+ return Effect.suspend(() => {
28
+ let lastSeen = "nothing yet";
29
+ const wait = Effect.gen(function* () {
30
+ while (true) {
31
+ const revision = yield* signal.revision;
32
+ const { arrived, seen } = yield* read;
33
+ lastSeen = seen;
34
+ if (Option.isSome(arrived)) {
35
+ return arrived.value;
36
+ }
37
+ yield* signal.waitForChange(revision);
38
+ }
39
+ });
40
+ return wait.pipe(
41
+ Effect.timeoutOrElse({ duration: within, orElse: () => Effect.fail(new PgBossWaitError({ lastSeen, waitingFor, within })) }),
42
+ // The budget is wall time, so a story's TestClock neither ends the wait early nor holds it open.
43
+ Effect.provideService(Clock.Clock, Clock.Clock.defaultValue()),
44
+ );
45
+ });
46
+ }
47
+
48
+ function fetchesRefused(queue: Pick<QueueDefinition, "name">, original: Error) {
49
+ return new PgBossTestingError({ operation: "fetchesPass", original: Redacted.make(original), queue: queue.name });
50
+ }
51
+
52
+ export function testingWaits(client: () => PgBoss, fetches: () => TestingFetches | undefined, signal: Signal) {
53
+ return {
54
+ fetchesPass: (queue: Pick<QueueDefinition, "name">, count: number, options?: PgBossTestingWaitOptions) =>
55
+ Effect.gen(function* () {
56
+ if (!Number.isSafeInteger(count) || count < 1) {
57
+ return yield* fetchesRefused(queue, new RangeError("Fetch count must be a positive integer"));
58
+ }
59
+ const observed = fetches();
60
+ if (observed === undefined) {
61
+ return yield* fetchesRefused(queue, new Error("Provide the testing clientFactory to a jobs Layer first"));
62
+ }
63
+ const begun = observed.begun(queue.name);
64
+ const fetchesHave = count === 1 ? "fetch that began after the wait has" : "fetches that began after the wait have";
65
+ return yield* waitUntil(
66
+ signal,
67
+ `${count} ${queue.name} ${fetchesHave} finished`,
68
+ Effect.sync(() => {
69
+ const passed = observed.passedSince(queue.name, begun);
70
+ return { arrived: passed >= count ? Option.some(undefined) : Option.none(), seen: `${passed} finished` };
71
+ }),
72
+ options,
73
+ );
74
+ }),
75
+ untilJob: <TName extends string, TPayload extends JobPayloadSchema>(
76
+ queue: Pick<QueueDefinition<TName, TPayload>, "name" | "schema">,
77
+ id: string,
78
+ state: JobWithMetadata["state"],
79
+ options?: PgBossTestingWaitOptions,
80
+ ) =>
81
+ waitUntil(
82
+ signal,
83
+ `job ${id} in ${queue.name} is ${state}`,
84
+ testingOperation("untilJob", () => client().getJobById<unknown>(queue.name, id), queue.name).pipe(
85
+ Effect.flatMap((job): Effect.Effect<Reading<JobWithMetadata<TPayload["Type"]>>, PgBossPayloadError, TPayload["DecodingServices"]> => {
86
+ if (job === null) {
87
+ return Effect.succeed({ arrived: Option.none(), seen: "no such job" });
88
+ }
89
+ if (job.state !== state) {
90
+ return Effect.succeed({ arrived: Option.none(), seen: job.state });
91
+ }
92
+ return Effect.map(decodeStoredData(queue, job.data), (data) => ({ arrived: Option.some({ ...job, data }), seen: job.state }));
93
+ }),
94
+ ),
95
+ options,
96
+ ),
97
+ };
98
+ }
@@ -1,79 +1,23 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { SqlClient } from "effect/sql";
3
- import type { Connection } from "effect/sql/SqlConnection";
4
- import { getConstructionPlans, getMigrationPlans, PgBoss } from "pg-boss";
5
3
  import pgBossPackage from "pg-boss/package.json" with { type: "json" };
6
4
  import type { SchemaComponent } from "@shivaedev/sql/SchemaComponent.ts";
5
+ import { installSchema } from "#adapters/installSchema.ts";
7
6
  import { type PgBossError, toPgBossError } from "#error.ts";
8
- import { deferralStorePlan } from "#internal/deferralStorePlan.ts";
9
- import { runSchemaPlan } from "#internal/runSchemaPlan.ts";
10
- import { resumeSchemaIndexes } from "#internal/schemaIndexBuilds.ts";
11
7
 
12
- const Partitions = Schema.Array(
13
- Schema.Struct({
14
- policy: Schema.String,
15
- tableName: Schema.String,
16
- }),
17
- );
18
8
  const InstalledSchema = Schema.Array(Schema.Struct({ name: Schema.NullOr(Schema.String) }));
19
9
  const InstalledVersion = Schema.Array(Schema.Struct({ version: Schema.Number }));
10
+ const InstalledResolution = Schema.Array(Schema.Struct({ installed: Schema.Boolean }));
20
11
 
21
12
  export interface PgBossSchemaOptions {
22
13
  readonly schema?: string;
23
14
  }
24
15
 
25
- async function installSchema(
26
- connection: Connection,
27
- options: PgBossSchemaOptions,
28
- run: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>,
29
- ): Promise<void> {
30
- const schema = options.schema ?? "pgboss";
31
- const construction = getConstructionPlans(schema);
32
- const client = new PgBoss({
33
- db: { executeSql: async (text, values = []) => ({ rows: Array.from(await run(connection.executeUnprepared(text, values, undefined))) }) },
34
- schema,
35
- });
36
- if (!(await client.isInstalled())) {
37
- await runSchemaPlan(connection, construction, run);
38
- return;
39
- }
40
- const version = await client.schemaVersion();
41
- if (version === null) {
42
- throw new Error("The pg-boss version table is empty");
43
- }
44
- if (version > pgBossPackage.pgboss.schema) {
45
- return;
46
- }
47
- if (version >= 27) {
48
- await resumeSchemaIndexes(client, schema);
49
- }
50
- if (version < pgBossPackage.pgboss.schema) {
51
- const partitionTables = Schema.decodeUnknownSync(Partitions)(
52
- await run(connection.executeUnprepared(`SELECT table_name AS "tableName", policy FROM ${schema}.queue WHERE partition = true`, [], undefined)),
53
- );
54
- await runSchemaPlan(connection, getMigrationPlans(schema, version, { partitionTables: Array.from(partitionTables) }), run, {
55
- schema,
56
- version: pgBossPackage.pgboss.schema,
57
- });
58
- await resumeSchemaIndexes(client, schema);
59
- }
60
- }
61
-
62
16
  export function installPgBossSchema(options: PgBossSchemaOptions = {}): Effect.Effect<void, PgBossError, SqlClient.SqlClient> {
63
- return Effect.scoped(
64
- Effect.gen(function* () {
65
- const sql = yield* SqlClient.SqlClient;
66
- const connection = yield* sql.reserve;
67
- const run = Effect.runPromiseWith(yield* Effect.context<never>());
68
- yield* Effect.tryPromise({
69
- catch: (error) => error,
70
- try: async () => {
71
- await installSchema(connection, options, run);
72
- await run(connection.executeRaw(deferralStorePlan(options.schema ?? "pgboss"), []));
73
- },
74
- });
75
- }).pipe(Effect.mapError((error) => toPgBossError("install", error))),
76
- ).pipe(Effect.uninterruptible);
17
+ return Effect.scoped(installSchema(options)).pipe(
18
+ Effect.mapError((error) => toPgBossError("install", error)),
19
+ Effect.uninterruptible,
20
+ );
77
21
  }
78
22
 
79
23
  export function pgBossSchema(options: PgBossSchemaOptions = {}): SchemaComponent {
@@ -92,9 +36,14 @@ export function pgBossSchema(options: PgBossSchemaOptions = {}): SchemaComponent
92
36
  ? []
93
37
  : yield* sql`select version from ${table}`.withoutTransform.pipe(Effect.flatMap(Schema.decodeUnknownEffect(InstalledVersion)));
94
38
  const required = pgBossPackage.pgboss.schema;
95
- return row === undefined || row.version < required
96
- ? `schema ${schema}, installed version ${row?.version ?? "missing"}, required version ${required}`
97
- : undefined;
39
+ if (row === undefined || row.version < required) {
40
+ return `schema ${schema}, installed version ${row?.version ?? "missing"}, required version ${required}`;
41
+ }
42
+ const job = sql`${sql(schema)}.${sql("job")}`;
43
+ const [resolution] = yield* sql`SELECT EXISTS (
44
+ SELECT 1 FROM pg_trigger WHERE tgrelid = to_regclass(${job.compile()[0]}) AND tgname = 'effect_dead_letter_resolution'
45
+ ) AS installed`.withoutTransform.pipe(Effect.flatMap(Schema.decodeUnknownEffect(InstalledResolution)));
46
+ return resolution?.installed ? undefined : `schema ${schema}, missing dead-letter resolution trigger`;
98
47
  }),
99
48
  };
100
49
  }
@@ -2,6 +2,7 @@ import { Context, Effect, Exit, Layer, type Option, Redacted, type Schema, Scope
2
2
  import type { ConstructorOptions, JobWithMetadata, SendOptions, StopOptions } from "pg-boss";
3
3
  import { defineService } from "@shivaedev/service/defineService.ts";
4
4
  import { genericMethod } from "@shivaedev/service/genericMethod.ts";
5
+ import { clientErrorReporter } from "#adapters/clientErrorReporter.ts";
5
6
  import type { PgBossClientFactory } from "#client.ts";
6
7
  import type { JobPayloadSchema, JobRegistration, QueueDefinition, RegistrationRequirements } from "#definition.ts";
7
8
  import type { PgBossClientOptionsError, PgBossError, PgBossPayloadError, PgBossQueueNamesError, PgBossQueueOptionsError } from "#error.ts";
@@ -19,7 +20,7 @@ export interface PgBossService {
19
20
  readonly deadLetteredJobs: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
20
21
  queue: { readonly name: TName; readonly schema: TPayload },
21
22
  options: JobPageOptions,
22
- ) => Effect.Effect<JobPage<DeadLetteredJob<TPayload["Type"]>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
23
+ ) => Effect.Effect<JobPage<DeadLetteredJob<unknown>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
23
24
  readonly enqueue: <const TName extends string, const TPayload extends JobPayloadSchema, const TQueue extends QueueDefinition<TName, TPayload>>(
24
25
  queue: QueueDefinition<TName, TPayload> & TQueue,
25
26
  payload: NoInfer<Schema.Schema.Type<TPayload>>,
@@ -28,11 +29,11 @@ export interface PgBossService {
28
29
  readonly failedJobs: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
29
30
  queue: { readonly name: TName; readonly schema: TPayload },
30
31
  options: JobPageOptions,
31
- ) => Effect.Effect<JobPage<ListedJob<TPayload["Type"]>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
32
+ ) => Effect.Effect<JobPage<ListedJob<unknown>>, PgBossError | PgBossPayloadError | Schema.SchemaError, TPayload["DecodingServices"]>;
32
33
  readonly getJob: <const TName extends string, const TPayload extends JobPayloadSchema | typeof Schema.Null>(
33
34
  queue: { readonly name: TName; readonly schema: TPayload },
34
35
  id: string,
35
- ) => Effect.Effect<Option.Option<JobWithMetadata<TPayload["Type"]>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
36
+ ) => Effect.Effect<Option.Option<JobWithMetadata<unknown>>, PgBossError | PgBossPayloadError, TPayload["DecodingServices"]>;
36
37
  readonly health: () => Effect.Effect<JobsHealth, PgBossError>;
37
38
  readonly retry: (queue: Pick<QueueDefinition, "name">, id: string) => Effect.Effect<void, PgBossError>;
38
39
  }
@@ -49,8 +50,7 @@ export function pgBossLayer(options: PgBossOptions): Layer.Layer<PgBoss, PgBossE
49
50
  return Layer.effect(
50
51
  PgBoss,
51
52
  Effect.gen(function* () {
52
- const dependencies = yield* Effect.context<never>();
53
- const execute = Effect.runPromiseWith(dependencies);
53
+ const report = yield* clientErrorReporter(logClientError);
54
54
  const acquired = yield* Effect.acquireRelease(
55
55
  acquireClient(
56
56
  {
@@ -61,9 +61,7 @@ export function pgBossLayer(options: PgBossOptions): Layer.Layer<PgBoss, PgBossE
61
61
  ...(connectionString === undefined ? {} : { connectionString: Redacted.value(connectionString) }),
62
62
  },
63
63
  },
64
- (error) => {
65
- void execute(logClientError(error));
66
- },
64
+ report,
67
65
  ),
68
66
  (client) => releaseClient(client, stop),
69
67
  );
@@ -94,24 +92,17 @@ export function makePgBoss<const TIdentifier extends string>(identifier: TIdenti
94
92
  initialize: Effect.gen(function* () {
95
93
  yield* validateQueueNames(options.jobs, queueNames);
96
94
  const acquired = yield* PgBoss;
97
- const execute = Effect.runPromiseWith(dependencies);
98
95
  const workerScope = yield* Scope.make();
99
96
  const service = makeService(acquired.client, options.jobs.map(registrationName), acquired.schema);
100
97
  const context = Context.add(dependencies, Service, service);
101
98
  const reportError = options.onError ?? logClientError;
102
- const unsubscribe = subscribeClientErrors(acquired.client, (error) => {
103
- void execute(Effect.exit(Effect.provide(reportError(error), context)));
104
- });
99
+ const report = yield* clientErrorReporter(reportError).pipe(Effect.provide(context));
100
+ const unsubscribe = subscribeClientErrors(acquired.client, report);
105
101
  yield* Effect.addFinalizer(() => Scope.close(workerScope, Exit.void).pipe(Effect.ensuring(Effect.sync(unsubscribe))));
106
- yield* registerReleasedJobs(
107
- acquired.client,
108
- options.jobs,
109
- context,
110
- acquired.reused,
111
- (effect) => execute(effect),
112
- acquired.schema,
113
- acquired.stop?.graceful !== false,
114
- ).pipe(Effect.provideService(Scope.Scope, workerScope), Effect.provide(dependencies));
102
+ yield* registerReleasedJobs(acquired.client, options.jobs, context, acquired.reused, acquired.schema, acquired.stop?.graceful !== false).pipe(
103
+ Effect.provideService(Scope.Scope, workerScope),
104
+ Effect.provide(dependencies),
105
+ );
115
106
  return service;
116
107
  }),
117
108
  methods: (service) => ({
@@ -1,9 +1,10 @@
1
1
  import { type DateTime, Duration, Effect, Redacted } from "effect";
2
2
  import type { PgBoss } from "pg-boss";
3
3
  import type { PgBossClientFactory } from "#client.ts";
4
+ import { defaultClientFactory } from "#defaultClientFactory.ts";
4
5
  import type { JobPayloadSchema, QueueDefinition } from "#definition.ts";
5
6
  import { PgBossTestingError } from "#error.ts";
6
- import { nativeClient } from "#internal/nativeClient.ts";
7
+ import { type TestingFetches, testingFetches } from "#internal/adapters/testingFetches.ts";
7
8
  import { testingClient } from "#internal/testingClient.ts";
8
9
  import { testingDeferrals } from "#internal/testingDeferrals.ts";
9
10
  import { type TestingWork, testingIdle } from "#internal/testingIdle.ts";
@@ -11,6 +12,7 @@ import { testingOperation, testingReadout } from "#internal/testingReadout.ts";
11
12
  import { testingSchedules } from "#internal/testingSchedules.ts";
12
13
  import { testingSignal } from "#internal/testingSignal.ts";
13
14
  import { testingSnapshot } from "#internal/testingSnapshot.ts";
15
+ import { testingWaits } from "#internal/testingWaits.ts";
14
16
 
15
17
  export type SnapshotQueue = Pick<QueueDefinition<string, JobPayloadSchema>, "name" | "schema">;
16
18
 
@@ -22,6 +24,10 @@ export interface UnfinishedJob<TQueue extends SnapshotQueue> {
22
24
  readonly state: "active" | "created" | "failed" | "retry";
23
25
  }
24
26
 
27
+ export interface PgBossTestingWaitOptions {
28
+ readonly within?: Duration.Input;
29
+ }
30
+
25
31
  export const testRetryPolicy = Object.freeze({ retryBackoff: false, retryDelay: 0 });
26
32
 
27
33
  export interface PgBossTestingOptions {
@@ -30,6 +36,7 @@ export interface PgBossTestingOptions {
30
36
 
31
37
  export function makePgBossTesting(options: PgBossTestingOptions = {}) {
32
38
  let current: PgBoss | undefined;
39
+ let fetches: TestingFetches | undefined;
33
40
  let schema = "pgboss";
34
41
  const signal = testingSignal();
35
42
  const work: TestingWork = { batches: new Set(), queues: new Set() };
@@ -52,12 +59,14 @@ export function makePgBossTesting(options: PgBossTestingOptions = {}) {
52
59
  throw new Error("A testing controller owns one jobs client; create a controller per runtime");
53
60
  }
54
61
  schema = clientOptions.schema ?? "pgboss";
55
- current = nativeClient({ ...clientOptions, "__test__enableSpies": true });
62
+ current = defaultClientFactory({ ...clientOptions, "__test__enableSpies": true });
63
+ fetches = testingFetches(current.getDb(), signal.changed);
56
64
  return testingClient(current, clientOptions.schema ?? "pgboss", controls);
57
65
  };
58
66
  return {
59
67
  ...testingDeferrals(client, () => schema, signal.changed),
60
68
  ...testingSchedules(client, signal.changed),
69
+ ...testingWaits(client, () => fetches, signal),
61
70
  checkBatchWindow: (queue: Pick<QueueDefinition, "name" | "workerOptions">, millisecondsPerJob: number) =>
62
71
  testingOperation(
63
72
  "checkBatchWindow",
@@ -1,3 +0,0 @@
1
- import { type ConstructorOptions, PgBoss } from "pg-boss";
2
- export declare function nativeClient(options: ConstructorOptions): PgBoss;
3
- //# sourceMappingURL=nativeClient.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nativeClient.d.ts","sourceRoot":"","sources":["../../../src/server/internal/nativeClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1D,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,UA0BvD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"nativeClient.js","sourceRoot":"","sources":["../../../src/server/internal/nativeClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAA2B,MAAM,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,UAAU,YAAY,CAAC,OAA2B;IACvD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC9C,MAAM,MAAM,GAAG;QACd,GAAG,OAAO;QACV,MAAM,EAAE,GAAG,EAAE;YACZ,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACzB,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC5B,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CACF,CAAC;YACF,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzB,OAAO,MAAM,CAAC;QACf,CAAC;KACD,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,IAAI,GAAG,KAAK,EAAE,WAAW,EAAE,EAAE;QACnC,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,0EAA0E;QAC1E,IAAI,WAAW,EAAE,KAAK,KAAK,KAAK,EAAE,CAAC;YAClC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;QACtC,CAAC;IACF,CAAC,CAAC;IACF,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -1,6 +0,0 @@
1
- import { type Context, Effect, Scope } from "effect";
2
- import type { Job, WorkOptions } from "pg-boss";
3
- import type { PgBossClient } from "#client.ts";
4
- import { type PgBossRunner } from "#internal/batchHandler.ts";
5
- export declare const registerWorker: (client: PgBossClient, name: string, options: WorkOptions, run: (job: Job<unknown>, deadline: number) => Effect.Effect<unknown, unknown, unknown>, scope: Scope.Scope, schema: string, maxDeferrals: number, context: Context.Context<unknown>, execute: PgBossRunner, graceful: boolean) => Effect.Effect<string, import("#error.ts").PgBossError, never>;
6
- //# sourceMappingURL=registerWorker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registerWorker.d.ts","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAS,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAK5E,eAAO,MAAM,cAAc,wEAIf,GAAG,CAAC,OAAO,CAAC,YAAY,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,yMAmCrF,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"registerWorker.js","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import type { DefineScheduleOptions, ScheduleDefinition } from "#definition.ts";
2
- export declare function scheduleDefinition<const TName extends string>(options: DefineScheduleOptions<TName>): ScheduleDefinition<TName>;
3
- //# sourceMappingURL=scheduleDefinition.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scheduleDefinition.d.ts","sourceRoot":"","sources":["../../../src/server/internal/scheduleDefinition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGhF,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,OAAO,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAiB/H"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"scheduleDefinition.js","sourceRoot":"","sources":["../../../src/server/internal/scheduleDefinition.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAElF,MAAM,UAAU,kBAAkB,CAA6B,OAAqC;IACnG,MAAM,UAAU,GAA8B;QAC7C,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;QACzC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC;QAC/C,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CACf,MAAM,CAAC,MAAM,CAAC;YACb,IAAI,EAAE,iBAA0B;YAChC,MAAM;YACN,QAAQ,EAAE,UAAU;SACpB,CAAC;QACH,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAClE,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;KAC5F,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC"}