@shivaedev/effect-pg-boss 0.8.0 → 0.9.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 (72) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +51 -3
  3. package/dist/server/error.d.ts +10 -0
  4. package/dist/server/error.d.ts.map +1 -1
  5. package/dist/server/error.js +14 -0
  6. package/dist/server/error.js.map +1 -1
  7. package/dist/server/internal/clientCache.d.ts +7 -0
  8. package/dist/server/internal/clientCache.d.ts.map +1 -0
  9. package/dist/server/internal/clientCache.js +51 -0
  10. package/dist/server/internal/clientCache.js.map +1 -0
  11. package/dist/server/internal/clientErrors.d.ts +8 -0
  12. package/dist/server/internal/clientErrors.d.ts.map +1 -0
  13. package/dist/server/internal/clientErrors.js +49 -0
  14. package/dist/server/internal/clientErrors.js.map +1 -0
  15. package/dist/server/internal/ensureQueue.d.ts.map +1 -1
  16. package/dist/server/internal/ensureQueue.js +19 -2
  17. package/dist/server/internal/ensureQueue.js.map +1 -1
  18. package/dist/server/internal/expireAttempt.d.ts +3 -0
  19. package/dist/server/internal/expireAttempt.d.ts.map +1 -0
  20. package/dist/server/internal/expireAttempt.js +14 -0
  21. package/dist/server/internal/expireAttempt.js.map +1 -0
  22. package/dist/server/internal/lifecycle.d.ts +2 -2
  23. package/dist/server/internal/lifecycle.d.ts.map +1 -1
  24. package/dist/server/internal/lifecycle.js +36 -79
  25. package/dist/server/internal/lifecycle.js.map +1 -1
  26. package/dist/server/internal/observeTestingSettlements.d.ts +6 -0
  27. package/dist/server/internal/observeTestingSettlements.d.ts.map +1 -0
  28. package/dist/server/internal/observeTestingSettlements.js +35 -0
  29. package/dist/server/internal/observeTestingSettlements.js.map +1 -0
  30. package/dist/server/internal/register.d.ts +2 -2
  31. package/dist/server/internal/register.d.ts.map +1 -1
  32. package/dist/server/internal/register.js +8 -3
  33. package/dist/server/internal/register.js.map +1 -1
  34. package/dist/server/internal/testingClient.d.ts +10 -0
  35. package/dist/server/internal/testingClient.d.ts.map +1 -0
  36. package/dist/server/internal/testingClient.js +101 -0
  37. package/dist/server/internal/testingClient.js.map +1 -0
  38. package/dist/server/internal/testingExpirations.d.ts +6 -0
  39. package/dist/server/internal/testingExpirations.d.ts.map +1 -0
  40. package/dist/server/internal/testingExpirations.js +27 -0
  41. package/dist/server/internal/testingExpirations.js.map +1 -0
  42. package/dist/server/internal/testingReadout.d.ts +44 -0
  43. package/dist/server/internal/testingReadout.d.ts.map +1 -0
  44. package/dist/server/internal/testingReadout.js +13 -0
  45. package/dist/server/internal/testingReadout.js.map +1 -0
  46. package/dist/server/internal/testingSignal.d.ts +7 -0
  47. package/dist/server/internal/testingSignal.d.ts.map +1 -0
  48. package/dist/server/internal/testingSignal.js +27 -0
  49. package/dist/server/internal/testingSignal.js.map +1 -0
  50. package/dist/server/service.d.ts.map +1 -1
  51. package/dist/server/service.js +4 -12
  52. package/dist/server/service.js.map +1 -1
  53. package/dist/server/testing.d.ts +61 -0
  54. package/dist/server/testing.d.ts.map +1 -0
  55. package/dist/server/testing.js +69 -0
  56. package/dist/server/testing.js.map +1 -0
  57. package/docs/roadmap.md +6 -5
  58. package/package.json +7 -5
  59. package/src/server/error.ts +21 -1
  60. package/src/server/internal/clientCache.ts +61 -0
  61. package/src/server/internal/clientErrors.ts +64 -0
  62. package/src/server/internal/ensureQueue.ts +20 -2
  63. package/src/server/internal/expireAttempt.ts +18 -0
  64. package/src/server/internal/lifecycle.ts +37 -97
  65. package/src/server/internal/observeTestingSettlements.ts +36 -0
  66. package/src/server/internal/register.ts +8 -4
  67. package/src/server/internal/testingClient.ts +115 -0
  68. package/src/server/internal/testingExpirations.ts +31 -0
  69. package/src/server/internal/testingReadout.ts +26 -0
  70. package/src/server/internal/testingSignal.ts +28 -0
  71. package/src/server/service.ts +5 -14
  72. package/src/server/testing.ts +85 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 - 2026-10-18
4
+
5
+ ### Added
6
+
7
+ - Export `testing.ts` with `makePgBossTesting`, `testRetryPolicy`, `PgBossTesting` and `PgBossTestingOptions`, plus `PgBossTestingError` from `error.ts`. Story kits can read Schema-decoded jobs, fail the next send, expire a selected attempt, set immediate retries, check batch budgets and schema versions, and wait for local queue changes or worker idleness without application SQL or timer polling.
8
+ - Expiry rules stay with each successfully sent job, and native terminal worker timeouts wake queue-change waiters even when a handler promise stays pending.
9
+
10
+ ## 0.8.1 - 2026-10-18
11
+
12
+ ### Changed
13
+
14
+ - `PgBossError` has a message that names the operation, the queue and the original error's class and code, such as a PostgreSQL SQLSTATE, without the redacted original message. A registration failure names the queue it was preparing.
15
+
16
+ ### Fixed
17
+
18
+ - Registration creates a queue again, up to six times with a short backoff, when PostgreSQL rolls the creation back as a deadlock victim (SQLSTATE `40P01`). A partitioned queue created while other queues in the schema have traffic no longer fails Layer acquisition.
19
+ - Observe pg-boss connection errors throughout startup and shutdown, and log administrator-terminated connections at debug while preserving query failures and application error callbacks.
20
+ - Keep observing idle errors from closing sockets after shutdown, when shutdown fails, and when `stop({ close: false })` leaves the pool open.
21
+
3
22
  ## 0.8.0 - 2026-10-17
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -170,7 +170,7 @@ export const jobsLayer = (databaseUrl: string) =>
170
170
  }).pipe(Layer.provide(MailLive));
171
171
  ```
172
172
 
173
- The constructor options belong to pg-boss. `jobs` is the complete list of registrations for this Layer. The Layer captures `MailLive` for both the queue handler and the scheduled Effect. A startup or registration failure fails Layer acquisition with `PgBossError` or `PgBossQueueOptionsError` and releases its acquired client reference. A shared client stays running while another scope still owns a reference.
173
+ The constructor options belong to pg-boss. `jobs` is the complete list of registrations for this Layer. The Layer captures `MailLive` for both the queue handler and the scheduled Effect. A startup or registration failure fails Layer acquisition with `PgBossError` or `PgBossQueueOptionsError` and releases its acquired client reference. Registration creates a queue again, up to six times with a short backoff, when PostgreSQL rolls the creation back as a deadlock victim (SQLSTATE `40P01`), which a partitioned queue can meet when other queues in the schema have traffic. A shared client stays running while another scope still owns a reference.
174
174
 
175
175
  Keep the Layer in the application's worker runtime scope. A short scope is useful for an acquisition or health check; a worker process needs its scope to remain open while it handles jobs.
176
176
 
@@ -324,6 +324,53 @@ const live = Jobs.layer({
324
324
 
325
325
  Include every target queue in `jobs`, as either a worker or a producer-only definition. Startup creates all queues before starting consumers. The example registers `Receipt` for another process to consume. A follow-up target can also have a worker in the same Layer. Scheduled effects can yield `Jobs` too. Keep one jobs Layer in the process's native Layer graph; that Layer owns one client for enqueueing and handling. The PostgreSQL follow-up test completes a chain across two workers, enqueues to a producer-only queue and asserts one client acquisition and shutdown.
326
326
 
327
+ ### Test through the queue contract
328
+
329
+ `testing.ts` gives story kits a controller for one real pg-boss client. Create a fresh controller per runtime, pass its `clientFactory` to the jobs Layer, and keep that Layer scoped as usual. `jobs(queue, options?)` returns native job metadata with `data` decoded through the queue's Schema, including traced payloads. Invalid durable data fails with `PgBossPayloadError`; decoding services remain in the Effect's requirements.
330
+
331
+ This producer story uses the `SendEmail` definition and `Jobs` service from above. `databaseUrl` points to the story's PostgreSQL database:
332
+
333
+ ```ts
334
+ import { Context, Layer, Option } from "effect";
335
+ import { expect } from "vitest";
336
+ import { storyKit } from "@shivaedev/test-story/storyKit.ts";
337
+ import { makePgBossTesting, testRetryPolicy } from "@shivaedev/effect-pg-boss/testing.ts";
338
+
339
+ const emailStories = storyKit({
340
+ clock: "live",
341
+ *create() {
342
+ const testing = makePgBossTesting({ retryPolicy: testRetryPolicy });
343
+ const context = yield* Layer.build(Jobs.layer({
344
+ connectionString: databaseUrl,
345
+ clientFactory: testing.clientFactory,
346
+ jobs: [SendEmail],
347
+ }));
348
+ return { jobs: Context.get(context, Jobs), testing };
349
+ },
350
+ name: "queued email",
351
+ stages: ["ready"],
352
+ verbs: ({ jobs, testing }) => ({
353
+ enqueue: (userId: number) => jobs.enqueue(SendEmail, { userId }),
354
+ testing,
355
+ }),
356
+ });
357
+
358
+ emailStories.it("decodes the queued email", [], function* ({ enqueue, testing }) {
359
+ const id = Option.getOrThrow(yield* enqueue(42));
360
+ expect(yield* testing.jobs(SendEmail)).toMatchObject([
361
+ { id, data: { userId: 42 }, state: "created" },
362
+ ]);
363
+ });
364
+ ```
365
+
366
+ Set `retryPolicy: testRetryPolicy` through the controller to override queue and send settings with `retryDelay: 0` and `retryBackoff: false`. A failing worker can then exhaust its native retries without production backoff. The policy applies to subsequently sent jobs; it does not rewrite jobs already stored.
367
+
368
+ `failNextSend(queue)` arms one send failure for that queue. The next valid `enqueue` fails with the usual typed `PgBossError` (`operation: "enqueue"`); the following send proceeds normally. `expireAfter(queue, n)` takes a positive attempt count: configure it before enqueueing to limit subsequent jobs to `n` attempts. Each successful send keeps its own expiry rule; later changes and duplicate-ID sends do not alter pending jobs. Earlier attempts run the handler normally; on attempt `n`, the controller expires the claimed job instead of invoking its handler. PostgreSQL supervision performs the terminal failure and dead lettering. A job that succeeds earlier completes normally. This expiry hook supports pg-boss schema version 45 and keeps its SQL inside the testing module.
369
+
370
+ `checkBatchWindow(queue, millisecondsPerJob)` checks that the declared sequential batch fits strictly inside the stored queue expiry window. `schemaVersion` reads the installed version, and `checkSchemaVersion(expected)` fails when it differs. Test control, read and check failures use `PgBossTestingError` with `operation`, optional `queue` and a redacted `original`.
371
+
372
+ A kit can capture `revision` before reading jobs and yield `waitForChange(revision)` when its condition is unmet. The wait resumes after a local send, settlement, worker fetch or return to idle, including terminal native worker timeouts whose handler promises remain pending. It also resumes immediately if a change occurred between the read and the wait. This signal observes the controller's client; external clients, future scheduled work and external database writes need their own wake source. Stories should retain a bounded test timeout. The package's PostgreSQL story kit uses this signal to settle failed-job stories without timer polling or application SQL.
373
+
327
374
  ### API
328
375
 
329
376
  Import the module that defines the name; the package has no root entry.
@@ -333,8 +380,9 @@ Import the module that defines the name; the package has no root entry.
333
380
  | `@shivaedev/effect-pg-boss/definition.ts` | `defineQueue`, `defineSchedule`, `jobContext`; `DefineQueueOptions`, `DefineScheduleOptions`, `JobContext`, `JobPayloadSchema`, `JobRegistration`, `QueueDefinition`, `QueueWorker`, `RegistrationRequirements`, `ScheduleDefinition`, `ScheduledWorker` |
334
381
  | `@shivaedev/effect-pg-boss/service.ts` | `makePgBoss`; `PgBossDefinition`, `PgBossLayerOptions`, `PgBossService` |
335
382
  | `@shivaedev/effect-pg-boss/health.ts` | `deadLetterQueueName`; `JobsHealth`, `QueueCounts`, `QueueHealth` |
336
- | `@shivaedev/effect-pg-boss/error.ts` | `PgBossError`, `PgBossPayloadError`, `PgBossQueueOptionsError`, `toPgBossError`; `PgBossOperation` |
383
+ | `@shivaedev/effect-pg-boss/error.ts` | `PgBossError`, `PgBossPayloadError`, `PgBossQueueOptionsError`, `PgBossTestingError`, `toPgBossError`; `PgBossOperation` |
337
384
  | `@shivaedev/effect-pg-boss/client.ts` | `defaultClientFactory`; `PgBossClient`, `PgBossClientFactory` |
385
+ | `@shivaedev/effect-pg-boss/testing.ts` | `makePgBossTesting`, `testRetryPolicy`; `PgBossTesting`, `PgBossTestingOptions` |
338
386
 
339
387
  The definition options keep the pg-boss names: `queue` uses `Queue` except `name` and `deadLetter`, `worker` uses `WorkOptions`, and a schedule's `schedule` uses `ScheduleOptions`. A queue also requires `name` and `schema`; a schedule requires `name` and `cron`.
340
388
 
@@ -352,7 +400,7 @@ The application's service has these operations:
352
400
 
353
401
  `JobContext` has `id`, `name`, `signal`, `expireInSeconds`, `heartbeatSeconds` and `remainingBudget`. Worker registration always requests `includeMetadata: true`, including when `worker` supplies `includeMetadata: false`; custom clients must return pg-boss's `startedOn` metadata. When calling `jobContext` directly, reading `remainingBudget` requires that same metadata. `QueueHealth` has `name`, `activeCount`, `deadLetteredCount`, `failedCount`, `queuedCount` and `readyCount`. `JobsHealth` has `jobs` and the corresponding `activeTotal`, `deadLetteredTotal`, `failedTotal`, `queuedTotal` and `readyTotal` fields.
354
402
 
355
- `PgBossError` has `operation`, optional `queue` and a `Redacted` `original`. `PgBossPayloadError` has `direction`, `queue` and a `Redacted` `original`. Those fields distinguish the pg-boss operation from the payload boundary.
403
+ `PgBossError` has `operation`, optional `queue` and a `Redacted` `original`. Its message names the operation, the queue and the original error's class and `code`, such as a PostgreSQL SQLSTATE, but never the original message, which can quote payloads or connection strings. A registration failure names the queue it was preparing. `PgBossPayloadError` has `direction`, `queue` and a `Redacted` `original`. Those fields distinguish the pg-boss operation from the payload boundary.
356
404
 
357
405
  ### Install, setup and limits
358
406
 
@@ -8,6 +8,7 @@ export declare class PgBossError extends PgBossError_base<{
8
8
  readonly queue?: string | undefined;
9
9
  readonly original: Redacted.Redacted<unknown>;
10
10
  }> {
11
+ get message(): string;
11
12
  }
12
13
  declare const PgBossQueueOptionsError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
13
14
  readonly _tag: "PgBossQueueOptionsError";
@@ -27,6 +28,15 @@ export declare class PgBossPayloadError extends PgBossPayloadError_base<{
27
28
  readonly original: Redacted.Redacted<unknown>;
28
29
  }> {
29
30
  }
31
+ declare const PgBossTestingError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
32
+ readonly _tag: "PgBossTestingError";
33
+ } & Readonly<A>;
34
+ export declare class PgBossTestingError extends PgBossTestingError_base<{
35
+ readonly operation: string;
36
+ readonly original: Redacted.Redacted<unknown>;
37
+ readonly queue?: string | undefined;
38
+ }> {
39
+ }
30
40
  export declare const toPgBossError: (operation: PgBossOperation, error: unknown, queue?: string) => PgBossError;
31
41
  export {};
32
42
  //# sourceMappingURL=error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/server/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;;;;AAEnH,qBAAa,WAAY,SAAQ,iBAAgC;IAChE,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;CAAG;;;;AAEL,qBAAa,uBAAwB,SAAQ,6BAA4C;IACxF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,QAAQ,CAAC,EAAE,CAAC;CACtD,CAAC;IACD,IAAa,OAAO,IAAI,MAAM,CAE7B;CACD;;;;AAED,qBAAa,kBAAmB,SAAQ,wBAAuC;IAC9E,QAAQ,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;CAAG;AAEL,eAAO,MAAM,aAAa,cAAe,eAAe,SAAS,OAAO,UAAU,MAAM,KAAG,WAKxF,CAAC"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/server/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;;;;AAEnH,qBAAa,WAAY,SAAQ,iBAAgC;IAChE,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;IAED,IAAa,OAAO,IAAI,MAAM,CAG7B;CACD;;;;AAUD,qBAAa,uBAAwB,SAAQ,6BAA4C;IACxF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,QAAQ,CAAC,EAAE,CAAC;CACtD,CAAC;IACD,IAAa,OAAO,IAAI,MAAM,CAE7B;CACD;;;;AAED,qBAAa,kBAAmB,SAAQ,wBAAuC;IAC9E,QAAQ,CAAC,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;CAAG;;;;AAEL,qBAAa,kBAAmB,SAAQ,wBAAuC;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;CAAG;AAEL,eAAO,MAAM,aAAa,cAAe,eAAe,SAAS,OAAO,UAAU,MAAM,KAAG,WAKxF,CAAC"}
@@ -1,5 +1,17 @@
1
1
  import { Data, Redacted } from "effect";
2
2
  export class PgBossError extends Data.TaggedError("PgBossError") {
3
+ // The original's message can quote payloads or connection strings, so only its class and code are shown.
4
+ get message() {
5
+ const target = this.queue === undefined ? "" : ` for queue ${this.queue}`;
6
+ return `pg-boss ${this.operation}${target} failed: ${originalKind(Redacted.value(this.original))}`;
7
+ }
8
+ }
9
+ function originalKind(original) {
10
+ if (!(original instanceof Error)) {
11
+ return typeof original;
12
+ }
13
+ const code = Reflect.get(original, "code");
14
+ return typeof code === "string" ? `${original.constructor.name} ${code}` : original.constructor.name;
3
15
  }
4
16
  export class PgBossQueueOptionsError extends Data.TaggedError("PgBossQueueOptionsError") {
5
17
  get message() {
@@ -8,6 +20,8 @@ export class PgBossQueueOptionsError extends Data.TaggedError("PgBossQueueOption
8
20
  }
9
21
  export class PgBossPayloadError extends Data.TaggedError("PgBossPayloadError") {
10
22
  }
23
+ export class PgBossTestingError extends Data.TaggedError("PgBossTestingError") {
24
+ }
11
25
  export const toPgBossError = (operation, error, queue) => new PgBossError({
12
26
  operation,
13
27
  original: Redacted.make(error),
@@ -1 +1 @@
1
- {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/server/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIxC,MAAM,OAAO,WAAY,SAAQ,IAAI,CAAC,WAAW,CAAC,aAAa,CAI7D;CAAG;AAEL,MAAM,OAAO,uBAAwB,SAAQ,IAAI,CAAC,WAAW,CAAC,yBAAyB,CAGrF;IACD,IAAa,OAAO;QACnB,OAAO,SAAS,IAAI,CAAC,KAAK,0CAA0C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/F,CAAC;CACD;AAED,MAAM,OAAO,kBAAmB,SAAQ,IAAI,CAAC,WAAW,CAAC,oBAAoB,CAI3E;CAAG;AAEL,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAA0B,EAAE,KAAc,EAAE,KAAc,EAAe,EAAE,CACxG,IAAI,WAAW,CAAC;IACf,SAAS;IACT,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;IAC9B,KAAK;CACL,CAAC,CAAC"}
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/server/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIxC,MAAM,OAAO,WAAY,SAAQ,IAAI,CAAC,WAAW,CAAC,aAAa,CAI7D;IACD,yGAAyG;IACzG,IAAa,OAAO;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1E,OAAO,WAAW,IAAI,CAAC,SAAS,GAAG,MAAM,YAAY,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;IACpG,CAAC;CACD;AAED,SAAS,YAAY,CAAC,QAAiB;IACtC,IAAI,CAAC,CAAC,QAAQ,YAAY,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,OAAO,QAAQ,CAAC;IACxB,CAAC;IACD,MAAM,IAAI,GAAY,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACpD,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC;AACtG,CAAC;AAED,MAAM,OAAO,uBAAwB,SAAQ,IAAI,CAAC,WAAW,CAAC,yBAAyB,CAGrF;IACD,IAAa,OAAO;QACnB,OAAO,SAAS,IAAI,CAAC,KAAK,0CAA0C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/F,CAAC;CACD;AAED,MAAM,OAAO,kBAAmB,SAAQ,IAAI,CAAC,WAAW,CAAC,oBAAoB,CAI3E;CAAG;AAEL,MAAM,OAAO,kBAAmB,SAAQ,IAAI,CAAC,WAAW,CAAC,oBAAoB,CAI3E;CAAG;AAEL,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAA0B,EAAE,KAAc,EAAE,KAAc,EAAe,EAAE,CACxG,IAAI,WAAW,CAAC;IACf,SAAS;IACT,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;IAC9B,KAAK;CACL,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { PgBossClient } from "#client.ts";
2
+ export declare function acquireCachedClient(key: string | symbol, start: () => Promise<PgBossClient>): Promise<{
3
+ client: PgBossClient;
4
+ reused: boolean;
5
+ }>;
6
+ export declare function releaseCachedClient(key: string | symbol): Promise<PgBossClient> | undefined;
7
+ //# sourceMappingURL=clientCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientCache.d.ts","sourceRoot":"","sources":["../../../src/server/internal/clientCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA0B/C,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC;;;GAqBjG;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAW3F"}
@@ -0,0 +1,51 @@
1
+ const cacheSymbol = Symbol.for("@shivaedev/effect-pg-boss/client-cache");
2
+ const shared = Reflect.get(globalThis, cacheSymbol);
3
+ const clients = shared instanceof Map ? shared : new Map();
4
+ Reflect.set(globalThis, cacheSymbol, clients);
5
+ function entryFor(key) {
6
+ const value = clients.get(key);
7
+ return isCachedClient(value) ? value : undefined;
8
+ }
9
+ function isCachedClient(value) {
10
+ return (typeof value === "object"
11
+ && value !== null
12
+ && Reflect.get(value, "client") instanceof Promise
13
+ && typeof Reflect.get(value, "references") === "number");
14
+ }
15
+ export async function acquireCachedClient(key, start) {
16
+ const existing = entryFor(key);
17
+ if (existing !== undefined) {
18
+ existing.references += 1;
19
+ try {
20
+ return { client: await existing.client, reused: true };
21
+ }
22
+ catch (error) {
23
+ existing.references -= 1;
24
+ throw error;
25
+ }
26
+ }
27
+ const entry = { client: start(), references: 1 };
28
+ clients.set(key, entry);
29
+ try {
30
+ return { client: await entry.client, reused: false };
31
+ }
32
+ catch (error) {
33
+ if (clients.get(key) === entry) {
34
+ clients.delete(key);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ export function releaseCachedClient(key) {
40
+ const entry = entryFor(key);
41
+ if (entry === undefined) {
42
+ return;
43
+ }
44
+ entry.references -= 1;
45
+ if (entry.references > 0) {
46
+ return;
47
+ }
48
+ clients.delete(key);
49
+ return entry.client;
50
+ }
51
+ //# sourceMappingURL=clientCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientCache.js","sourceRoot":"","sources":["../../../src/server/internal/clientCache.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;AACzE,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAC7D,MAAM,OAAO,GAAkC,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,EAA4B,CAAC;AACpH,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;AAE9C,SAAS,QAAQ,CAAC,GAAoB;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACrC,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;WACtB,KAAK,KAAK,IAAI;WACd,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,OAAO;WAC/C,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,QAAQ,CACvD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAoB,EAAE,KAAkC;IACjG,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC;YACJ,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;YACzB,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;IACD,MAAM,KAAK,GAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxB,IAAI,CAAC;QACJ,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC;YAChC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAoB;IACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO;IACR,CAAC;IACD,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;IACtB,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO;IACR,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpB,OAAO,KAAK,CAAC,MAAM,CAAC;AACrB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Effect } from "effect";
2
+ import type { PgBossClient } from "#client.ts";
3
+ type Listener = (error: Error) => void;
4
+ export declare function logClientError(error: Error): Effect.Effect<void>;
5
+ export declare function observeClientErrors(client: PgBossClient, fallback: Listener): void;
6
+ export declare function subscribeClientErrors(client: PgBossClient, listener: Listener): () => void;
7
+ export {};
8
+ //# sourceMappingURL=clientErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientErrors.d.ts","sourceRoot":"","sources":["../../../src/server/internal/clientErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAYvC,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAGhE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAelF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAO1F"}
@@ -0,0 +1,49 @@
1
+ import { Effect } from "effect";
2
+ const key = Symbol.for("@shivaedev/effect-pg-boss/error-observers");
3
+ const shared = Reflect.get(globalThis, key);
4
+ const observers = shared instanceof WeakMap ? shared : new WeakMap();
5
+ Reflect.set(globalThis, key, observers);
6
+ export function logClientError(error) {
7
+ const message = { event: "pg_boss_error", message: error.message };
8
+ return Reflect.get(error, "code") === "57P01" ? Effect.logDebug(message) : Effect.logError(message);
9
+ }
10
+ export function observeClientErrors(client, fallback) {
11
+ closeClientErrors(client);
12
+ const listeners = new Set();
13
+ const listener = (error) => {
14
+ if (listeners.size === 0) {
15
+ fallback(error);
16
+ }
17
+ else {
18
+ for (const report of listeners) {
19
+ report(error);
20
+ }
21
+ }
22
+ };
23
+ // Pool shutdown can resolve before its clients finish disconnecting.
24
+ client.on("error", listener);
25
+ observers.set(client, { dispose: () => client.off("error", listener), listeners });
26
+ }
27
+ export function subscribeClientErrors(client, listener) {
28
+ const observer = observerFor(client);
29
+ if (observer === undefined) {
30
+ throw new Error("pg-boss client has no error observer");
31
+ }
32
+ observer.listeners.add(listener);
33
+ return () => observer.listeners.delete(listener);
34
+ }
35
+ function closeClientErrors(client) {
36
+ observerFor(client)?.dispose();
37
+ observers.delete(client);
38
+ }
39
+ function observerFor(client) {
40
+ const value = observers.get(client);
41
+ return isObserver(value) ? value : undefined;
42
+ }
43
+ function isObserver(value) {
44
+ return (typeof value === "object"
45
+ && value !== null
46
+ && typeof Reflect.get(value, "dispose") === "function"
47
+ && Reflect.get(value, "listeners") instanceof Set);
48
+ }
49
+ //# sourceMappingURL=clientErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientErrors.js","sourceRoot":"","sources":["../../../src/server/internal/clientErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAUhC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;AACpE,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACrD,MAAM,SAAS,GAA8B,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,EAAoB,CAAC;AAClH,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;AAExC,MAAM,UAAU,cAAc,CAAC,KAAY;IAC1C,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACnE,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACrG,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAoB,EAAE,QAAkB;IAC3E,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY,CAAC;IACtC,MAAM,QAAQ,GAAa,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1B,QAAQ,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACP,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;gBAChC,MAAM,CAAC,KAAK,CAAC,CAAC;YACf,CAAC;QACF,CAAC;IACF,CAAC,CAAC;IACF,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7B,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAoB,EAAE,QAAkB;IAC7E,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACzD,CAAC;IACD,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAoB;IAC9C,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IAC/B,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,WAAW,CAAC,MAAoB;IACxC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IACjC,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;WACtB,KAAK,KAAK,IAAI;WACd,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,UAAU;WACnD,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,YAAY,GAAG,CACjD,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ensureQueue.d.ts","sourceRoot":"","sources":["../../../src/server/internal/ensureQueue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhI"}
1
+ {"version":3,"file":"ensureQueue.d.ts","sourceRoot":"","sources":["../../../src/server/internal/ensureQueue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAsB/C,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhI"}
@@ -1,10 +1,27 @@
1
1
  import { PgBossQueueOptionsError } from "#error.ts";
2
2
  import { deadLetterQueueName } from "#health.ts";
3
+ const DEADLOCK_DETECTED = "40P01";
4
+ const CREATE_QUEUE_ATTEMPTS = 6;
5
+ // A partitioned queue attaches its table to pg-boss's job table, which can deadlock with job traffic on other queues.
6
+ // PostgreSQL rolls the whole creation back, so trying it again is safe.
7
+ async function createQueue(client, name, options) {
8
+ for (let attempt = 1;; attempt += 1) {
9
+ try {
10
+ return await client.createQueue(name, options);
11
+ }
12
+ catch (error) {
13
+ if (attempt === CREATE_QUEUE_ATTEMPTS || !(error instanceof Error) || Reflect.get(error, "code") !== DEADLOCK_DETECTED) {
14
+ throw error;
15
+ }
16
+ await new Promise((resolve) => setTimeout(resolve, 25 * 2 ** (attempt - 1)));
17
+ }
18
+ }
19
+ }
3
20
  export async function ensureQueue(client, name, options) {
4
21
  const deadLetter = deadLetterQueueName(name);
5
22
  const desired = { retryBackoff: true, retryLimit: 3, ...options, deadLetter };
6
- await client.createQueue(deadLetter);
7
- await client.createQueue(name, desired);
23
+ await createQueue(client, deadLetter);
24
+ await createQueue(client, name, desired);
8
25
  const existing = await client.getQueue(name);
9
26
  if (existing === null) {
10
27
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"ensureQueue.js","sourceRoot":"","sources":["../../../src/server/internal/ensureQueue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB,EAAE,IAAY,EAAE,OAA2C;IAChH,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC;IAC9E,MAAM,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO;IACR,CAAC;IACD,MAAM,SAAS,GAAI,CAAC,WAAW,EAAE,QAAQ,CAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACnI,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5H,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtC,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;IACzE,CAAC;IACD,2EAA2E;IAC3E,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"ensureQueue.js","sourceRoot":"","sources":["../../../src/server/internal/ensureQueue.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,sHAAsH;AACtH,wEAAwE;AACxE,KAAK,UAAU,WAAW,CAAC,MAAoB,EAAE,IAAY,EAAE,OAA6B;IAC3F,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC;YACJ,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,OAAO,KAAK,qBAAqB,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,iBAAiB,EAAE,CAAC;gBACxH,MAAM,KAAK,CAAC;YACb,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9E,CAAC;IACF,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB,EAAE,IAAY,EAAE,OAA2C;IAChH,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC;IAC9E,MAAM,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACtC,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO;IACR,CAAC;IACD,MAAM,SAAS,GAAI,CAAC,WAAW,EAAE,QAAQ,CAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACnI,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,uBAAuB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5H,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO;IACR,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtC,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;IACzE,CAAC;IACD,2EAA2E;IAC3E,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Job, PgBoss } from "pg-boss";
2
+ export declare function expireAttempt(client: PgBoss, schema: string, job: Job<unknown>): Promise<void>;
3
+ //# sourceMappingURL=expireAttempt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expireAttempt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/expireAttempt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAepG"}
@@ -0,0 +1,14 @@
1
+ export async function expireAttempt(client, schema, job) {
2
+ if ((await client.schemaVersion()) !== 45) {
3
+ throw new Error("Testing expiry requires pg-boss schema version 45");
4
+ }
5
+ const quoted = `"${schema.replaceAll('"', '""')}"`;
6
+ await client.getDb().executeSql(`UPDATE ${quoted}.job SET started_on = ${quoted}.job_now() - (expire_seconds + 1) * interval '1 second'
7
+ WHERE name = $1 AND id = $2 AND retry_count = $3 AND state = 'active'`, [job.name, job.id, job.retryCount]);
8
+ // Supervision owns expiry and dead lettering; make its claim due with the job's deadline.
9
+ await client
10
+ .getDb()
11
+ .executeSql(`UPDATE ${quoted}.queue SET monitor_claim_on = ${quoted}.job_now() - interval '1 week' WHERE name = $1`, [job.name]);
12
+ await client.supervise(job.name);
13
+ }
14
+ //# sourceMappingURL=expireAttempt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expireAttempt.js","sourceRoot":"","sources":["../../../src/server/internal/expireAttempt.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,MAAc,EAAE,GAAiB;IACpF,IAAI,CAAC,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;IACnD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,UAAU,CAC9B,UAAU,MAAM,yBAAyB,MAAM;wEACuB,EACtE,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,UAAU,CAAC,CAClC,CAAC;IACF,0FAA0F;IAC1F,MAAM,MAAM;SACV,KAAK,EAAE;SACP,UAAU,CAAC,UAAU,MAAM,iCAAiC,MAAM,gDAAgD,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAClI,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC"}
@@ -11,6 +11,6 @@ export interface AcquiredClient {
11
11
  readonly client: PgBossClient;
12
12
  readonly reused: boolean;
13
13
  }
14
- export declare const acquireClient: (options: AcquireClientOptions) => Effect.Effect<AcquiredClient, import("#error.ts").PgBossError>;
15
- export declare const releaseClient: (acquired: AcquiredClient, stopOptions?: StopOptions) => Effect.Effect<void>;
14
+ export declare function acquireClient(options: AcquireClientOptions): Effect.Effect<AcquiredClient, import("#error.ts").PgBossError>;
15
+ export declare function releaseClient(acquired: AcquiredClient, stopOptions?: StopOptions): Effect.Effect<void>;
16
16
  //# sourceMappingURL=lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/server/internal/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAwB,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA2B/F,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CACzB;AAiBD,eAAO,MAAM,aAAa,YAAa,oBAAoB,KAAG,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,WAAW,EAAE,WAAW,CA6CxH,CAAC;AAEJ,eAAO,MAAM,aAAa,aAAc,cAAc,gBAAgB,WAAW,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAoB7C,CAAC"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/server/internal/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAwB,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAK/F,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CACzB;AAkBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,WAAW,EAAE,WAAW,CAAC,CAgB3H;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAUtG"}
@@ -1,23 +1,16 @@
1
1
  import { Effect } from "effect";
2
2
  import { defaultClientFactory } from "#client.ts";
3
3
  import { toPgBossError } from "#error.ts";
4
- const CacheKey = Symbol.for("@shivaedev/effect-pg-boss/client-cache");
5
- const sharedCache = Reflect.get(globalThis, CacheKey);
6
- const clientCache = sharedCache instanceof Map ? sharedCache : new Map();
7
- Reflect.set(globalThis, CacheKey, clientCache);
8
- function isCachedClient(value) {
9
- return (typeof value === "object"
10
- && value !== null
11
- && Reflect.get(value, "client") instanceof Promise
12
- && typeof Reflect.get(value, "references") === "number");
13
- }
14
- function cachedClient(key) {
15
- const entry = clientCache.get(key);
16
- return isCachedClient(entry) ? entry : undefined;
17
- }
18
- function startClient(options) {
4
+ import { acquireCachedClient, releaseCachedClient } from "./clientCache.js";
5
+ import { logClientError, observeClientErrors } from "./clientErrors.js";
6
+ async function startClient(options, report) {
19
7
  const client = (options.clientFactory ?? defaultClientFactory)(options.constructor);
20
- return client.start().then(() => client, async (error) => {
8
+ observeClientErrors(client, report);
9
+ try {
10
+ await client.start();
11
+ return client;
12
+ }
13
+ catch (error) {
21
14
  try {
22
15
  await client.stop();
23
16
  }
@@ -25,70 +18,34 @@ function startClient(options) {
25
18
  // Preserve the startup failure; stop is best effort on failed acquisition.
26
19
  }
27
20
  throw error;
21
+ }
22
+ }
23
+ export function acquireClient(options) {
24
+ return Effect.contextWith((context) => {
25
+ const run = Effect.runForkWith(context);
26
+ function report(error) {
27
+ run(logClientError(error));
28
+ }
29
+ return Effect.tryPromise({
30
+ catch: (error) => toPgBossError("start", error),
31
+ try: async () => {
32
+ const cacheKey = options.clientCacheKey;
33
+ return cacheKey === undefined
34
+ ? { client: await startClient(options, report), reused: false }
35
+ : { cacheKey, ...(await acquireCachedClient(cacheKey, () => startClient(options, report))) };
36
+ },
37
+ });
28
38
  });
29
39
  }
30
- export const acquireClient = (options) => Effect.tryPromise({
31
- catch: (error) => toPgBossError("start", error),
32
- try: async () => {
33
- const cacheKey = options.clientCacheKey;
34
- if (cacheKey === undefined) {
35
- return {
36
- client: await startClient(options),
37
- reused: false,
38
- };
39
- }
40
- const existing = cachedClient(cacheKey);
41
- if (existing !== undefined) {
42
- existing.references += 1;
43
- try {
44
- return {
45
- cacheKey,
46
- client: await existing.client,
47
- reused: true,
48
- };
49
- }
50
- catch (error) {
51
- existing.references -= 1;
52
- throw error;
53
- }
54
- }
55
- const entry = {
56
- client: startClient(options),
57
- references: 1,
58
- };
59
- clientCache.set(cacheKey, entry);
60
- try {
61
- return {
62
- cacheKey,
63
- client: await entry.client,
64
- reused: false,
65
- };
66
- }
67
- catch (error) {
68
- if (clientCache.get(cacheKey) === entry) {
69
- clientCache.delete(cacheKey);
40
+ export function releaseClient(acquired, stopOptions) {
41
+ return Effect.tryPromise({
42
+ catch: (error) => toPgBossError("stop", error),
43
+ try: async () => {
44
+ const client = acquired.cacheKey === undefined ? acquired.client : await releaseCachedClient(acquired.cacheKey);
45
+ if (client !== undefined) {
46
+ await client.stop(stopOptions);
70
47
  }
71
- throw error;
72
- }
73
- },
74
- });
75
- export const releaseClient = (acquired, stopOptions) => Effect.tryPromise({
76
- catch: (error) => toPgBossError("stop", error),
77
- try: async () => {
78
- if (acquired.cacheKey === undefined) {
79
- await acquired.client.stop(stopOptions);
80
- return;
81
- }
82
- const entry = cachedClient(acquired.cacheKey);
83
- if (entry === undefined) {
84
- return;
85
- }
86
- entry.references -= 1;
87
- if (entry.references > 0) {
88
- return;
89
- }
90
- clientCache.delete(acquired.cacheKey);
91
- await acquired.client.stop(stopOptions);
92
- },
93
- }).pipe(Effect.catch((error) => Effect.logError(error)));
48
+ },
49
+ }).pipe(Effect.catch((error) => Effect.logError(error)));
50
+ }
94
51
  //# sourceMappingURL=lifecycle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../../src/server/internal/lifecycle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../../src/server/internal/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,oBAAoB,EAA+C,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAcxE,KAAK,UAAU,WAAW,CAAC,OAA6B,EAAE,MAA8B;IACvF,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,oBAAoB,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACpF,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACR,2EAA2E;QAC5E,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAA6B;IAC1D,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACxC,SAAS,MAAM,CAAC,KAAY;YAC3B,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,MAAM,CAAC,UAAU,CAAC;YACxB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC;YAC/C,GAAG,EAAE,KAAK,IAAI,EAAE;gBACf,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC;gBACxC,OAAO,QAAQ,KAAK,SAAS;oBAC5B,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE;oBAC/D,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,mBAAmB,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/F,CAAC;SACD,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAwB,EAAE,WAAyB;IAChF,OAAO,MAAM,CAAC,UAAU,CAAC;QACxB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC;QAC9C,GAAG,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAChH,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAChC,CAAC;QACF,CAAC;KACD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { PgBoss } from "pg-boss";
2
+ export declare function observeTestingSettlements(client: PgBoss, changed: () => void): {
3
+ close(): void;
4
+ watch(name: string): void;
5
+ };
6
+ //# sourceMappingURL=observeTestingSettlements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observeTestingSettlements.d.ts","sourceRoot":"","sources":["../../../src/server/internal/observeTestingSettlements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI;IAG3E,KAAK;IAML,KAAK,OAAO,MAAM;EAwBnB"}
@@ -0,0 +1,35 @@
1
+ export function observeTestingSettlements(client, changed) {
2
+ const observers = new Map();
3
+ return {
4
+ close() {
5
+ for (const close of observers.values()) {
6
+ close();
7
+ }
8
+ observers.clear();
9
+ },
10
+ watch(name) {
11
+ if (observers.has(name)) {
12
+ return;
13
+ }
14
+ const spy = client.getSpy(name);
15
+ let open = true;
16
+ async function observe() {
17
+ while (true) {
18
+ await Promise.race([spy.waitForJob(() => true, "completed"), spy.waitForJob(() => true, "failed")]);
19
+ if (!open) {
20
+ return;
21
+ }
22
+ // This controller owns the spy; clear history and the losing wait before rearming both outcomes.
23
+ spy.clear();
24
+ changed();
25
+ }
26
+ }
27
+ observers.set(name, () => {
28
+ open = false;
29
+ spy.clear();
30
+ });
31
+ void observe().catch((error) => client.emit("error", error instanceof Error ? error : new Error(String(error))));
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=observeTestingSettlements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observeTestingSettlements.js","sourceRoot":"","sources":["../../../src/server/internal/observeTestingSettlements.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,yBAAyB,CAAC,MAAc,EAAE,OAAmB;IAC5E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAChD,OAAO;QACN,KAAK;YACJ,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxC,KAAK,EAAE,CAAC;YACT,CAAC;YACD,SAAS,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QACD,KAAK,CAAC,IAAY;YACjB,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,OAAO;YACR,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,IAAI,GAAG,IAAI,CAAC;YAChB,KAAK,UAAU,OAAO;gBACrB,OAAO,IAAI,EAAE,CAAC;oBACb,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;oBACpG,IAAI,CAAC,IAAI,EAAE,CAAC;wBACX,OAAO;oBACR,CAAC;oBACD,iGAAiG;oBACjG,GAAG,CAAC,KAAK,EAAE,CAAC;oBACZ,OAAO,EAAE,CAAC;gBACX,CAAC;YACF,CAAC;YACD,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE;gBACxB,IAAI,GAAG,KAAK,CAAC;gBACb,GAAG,CAAC,KAAK,EAAE,CAAC;YACb,CAAC,CAAC,CAAC;YACH,KAAK,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3H,CAAC;KACD,CAAC;AACH,CAAC"}