@shivaedev/jobs 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.0 - 2026-10-27
4
+
5
+ ### Added
6
+
7
+ - List registered schedules with `makePgBossTesting().schedules()` and enqueue an immediate run of a registered schedule with `trigger(schedule)`.
8
+
9
+ ### Fixed
10
+
11
+ - Retry wakes wait until the persisted backoff deadline even when a host timer fires early.
12
+
13
+ ### Breaking
14
+
15
+ - Enqueue inside a provided SQL client's transaction now commits and rolls back with that transaction through pg-boss's per-call database adapter. To retain independent enqueueing, call enqueue after the application transaction returns and handle its failure separately. Outside a transaction, behavior is unchanged.
16
+
3
17
  ## 0.20.0 - 2026-10-27
4
18
 
5
19
  ### Breaking
package/README.md CHANGED
@@ -132,6 +132,8 @@ The queue takes `{ userId: number }` in application code and stores `{ userId: s
132
132
 
133
133
  The schedule runs a payload-free Effect. This schedule registers its cron expression with `tz: "UTC"`. The package creates a `<name>-dlq` dead-letter queue for both kinds of worker and supplies `retryLimit: 3` and `retryBackoff: true` by default. The email queue above overrides the retry limit to five. These are pg-boss registration settings; pg-boss runs the retry and scheduling engine.
134
134
 
135
+ Only a Layer whose `jobs` list contains a schedule registration such as `cleanupWorker` registers that cron. A request runtime that declares only queues never runs scheduled Effects. A development process that starts the worker runs its schedules locally too; leave schedule registrations out of that worker when the app should not run them in development.
136
+
135
137
  The Layer requires the workers' Effect services. Leaving `Mail` unprovided leaves that requirement in the resulting Effect's type. A primitive Schema such as `Schema.String` is rejected as a queue definition because the durable contract must be an object.
136
138
 
137
139
  ### Backlog throughput and concurrency
@@ -476,6 +478,8 @@ emailStories.it("decodes the queued email", [], function* ({ enqueue, testing })
476
478
 
477
479
  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.
478
480
 
481
+ `schedules()` lists registered schedules as `{ name, cron, timezone }` entries from PostgreSQL. `trigger(schedule)` takes a schedule definition, such as `Cleanup`, and enqueues one run now using its stored schedule data and options. Register `Cleanup.run(effect)` in the testing Layer first, then yield `testing.trigger(Cleanup)` and `testing.waitForIdle` before observing the Effect's result. An unregistered schedule fails with `PgBossTestingError` (`operation: "trigger"`).
482
+
479
483
  `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.
480
484
 
481
485
  `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.
@@ -526,4 +530,4 @@ The package requires Node.js 24 or later and PostgreSQL. `effect` and `pg-boss`
526
530
 
527
531
  `connectionString` configures pg-boss's own `pg` Pool, separately from the application's Effect SQL pool. In the default configuration a URL without `sslmode` uses plaintext. Outside a private network, add `sslmode=verify-full` (or `require`) to require TLS. The installed `pg` 8.23 driver verifies certificates for both modes and refuses servers without TLS. For private CAs, start Node with `NODE_EXTRA_CA_CERTS=/path/to/ca.pem`. See the [framework database connection security guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/quickstart.md#database-connection-security) for all three connection paths and the driver's differences from libpq.
528
532
 
529
- Provide the database connection and job registrations at the application runtime boundary. Application services own the domain work and provider calls; pg-boss owns queue execution. Treat enqueueing and an application database transaction as separate operations. Enqueue after the application write commits, and handle queue failures in the application so they cannot roll back the write. The package supplies no application transaction adapter.
533
+ Provide the database connection and job registrations at the application runtime boundary. Application services own the domain work and provider calls; pg-boss owns queue execution. When `SqlClient` is provided and a SQL transaction is active, `enqueue` uses pg-boss’s per-call `db` option on that transaction’s connection. The application write and queued jobs commit or roll back together. The jobs client and SQL client must use the same PostgreSQL database. Outside a transaction, enqueueing keeps using the jobs client as usual. To keep enqueueing independent of an application transaction, move it after the transaction returns and handle its failure separately.
@@ -1 +1 @@
1
- {"version":3,"file":"enqueueFor.d.ts","sourceRoot":"","sources":["../../../src/server/internal/enqueueFor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,SAAS,CAAC,CAgCzE"}
1
+ {"version":3,"file":"enqueueFor.d.ts","sourceRoot":"","sources":["../../../src/server/internal/enqueueFor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,SAAS,CAAC,CAuBzE"}
@@ -1,23 +1,26 @@
1
1
  import { Effect, Option, Redacted, Schema } from "effect";
2
2
  import { PgBossPayloadError, toPgBossError } from "#error.ts";
3
3
  import { capturePayload } from "#internal/tracing.ts";
4
+ import { transactionDb } from "#internal/transactionDb.ts";
4
5
  export function enqueueFor(client) {
5
- return (queue, payload, options) => Schema.encodeUnknownEffect(queue.schema)(payload).pipe(Effect.mapError((error) => new PgBossPayloadError({
6
- direction: "encode",
7
- original: Redacted.make(error),
8
- queue: queue.name,
9
- })), Effect.flatMap((encoded) => {
6
+ return (queue, payload, options) => Effect.gen(function* () {
7
+ const encoded = yield* Schema.encodeUnknownEffect(queue.schema)(payload).pipe(Effect.mapError((error) => new PgBossPayloadError({ direction: "encode", original: Redacted.make(error), queue: queue.name })));
10
8
  if (typeof encoded !== "object" || encoded === null) {
11
- return Effect.fail(new PgBossPayloadError({
9
+ return yield* new PgBossPayloadError({
12
10
  direction: "encode",
13
11
  original: Redacted.make(new TypeError("pg-boss payloads must encode to objects")),
14
12
  queue: queue.name,
15
- }));
13
+ });
16
14
  }
17
- return capturePayload(encoded).pipe(Effect.flatMap((data) => Effect.tryPromise({
15
+ const data = yield* capturePayload(encoded);
16
+ const db = yield* transactionDb;
17
+ const sendOptions = queue.key === undefined ? options : { ...options, singletonKey: queue.key(payload) };
18
+ const send = Effect.tryPromise({
18
19
  catch: (error) => toPgBossError("enqueue", error, queue.name),
19
- try: () => client.send(queue.name, data, queue.key === undefined ? options : { ...options, singletonKey: queue.key(payload) }),
20
- })), Effect.map(Option.fromNullishOr));
21
- }));
20
+ try: () => client.send(queue.name, data, db === undefined ? sendOptions : { ...sendOptions, db }),
21
+ });
22
+ const id = yield* db === undefined ? send : Effect.uninterruptible(send);
23
+ return Option.fromNullishOr(id);
24
+ });
22
25
  }
23
26
  //# sourceMappingURL=enqueueFor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"enqueueFor.js","sourceRoot":"","sources":["../../../src/server/internal/enqueueFor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAoB,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC9C,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAClC,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,QAAQ,CACd,CAAC,KAAK,EAAE,EAAE,CACT,IAAI,kBAAkB,CAAC;QACtB,SAAS,EAAE,QAAQ;QACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QAC9B,KAAK,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CACH,EACD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAA0E,EAAE;QAClG,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrD,OAAO,MAAM,CAAC,IAAI,CACjB,IAAI,kBAAkB,CAAC;gBACtB,SAAS,EAAE,QAAQ;gBACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;gBACjF,KAAK,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC,CACF,CAAC;QACH,CAAC;QACD,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,IAAI,CAClC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACvB,MAAM,CAAC,UAAU,CAAC;YACjB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC;YAC7D,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;SAC9H,CAAC,CACF,EACD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAChC,CAAC;IACH,CAAC,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"enqueueFor.js","sourceRoot":"","sources":["../../../src/server/internal/enqueueFor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG3D,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC9C,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAClC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAC5E,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAC9H,CAAC;QACF,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrD,OAAO,KAAK,CAAC,CAAC,IAAI,kBAAkB,CAAC;gBACpC,SAAS,EAAE,QAAQ;gBACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;gBACjF,KAAK,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC;QAChC,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACzG,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;YAC9B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC;YAC7D,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,EAAE,EAAE,EAAE,CAAC;SACjG,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACzE,OAAO,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"registerWorker.d.ts","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjE,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,wBAAgB,cAAc,CAC7B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EACpE,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,CAuCjB"}
1
+ {"version":3,"file":"registerWorker.d.ts","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjE,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,wBAAgB,cAAc,CAC7B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EACpE,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
@@ -16,9 +16,13 @@ export function registerWorker(client, name, options, run, scope, schema, maxDef
16
16
  }
17
17
  const workId = await registration;
18
18
  await Effect.runPromise(Effect.gen(function* () {
19
- const now = yield* Clock.currentTimeMillis;
20
- // PostgreSQL timestamps retain sub-millisecond precision that Date drops.
21
- yield* Effect.sleep(Math.max(0, job.startAfter.getTime() + 1 - now));
19
+ // Host timers can fire early; PostgreSQL also retains precision that Date drops.
20
+ const deadline = job.startAfter.getTime() + 1;
21
+ let remaining = deadline - (yield* Clock.currentTimeMillis);
22
+ while (remaining > 0) {
23
+ yield* Effect.sleep(remaining);
24
+ remaining = deadline - (yield* Clock.currentTimeMillis);
25
+ }
22
26
  const workerIds = client
23
27
  .getWipData?.()
24
28
  .filter((worker) => worker.workId === workId)
@@ -1 +1 @@
1
- {"version":3,"file":"registerWorker.js","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgB,MAAM,EAAc,MAAM,QAAQ,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,UAAU,cAAc,CAC7B,MAAoB,EACpB,IAAY,EACZ,OAAoB,EACpB,GAAoE,EACpE,KAAkB,EAClB,MAAc,EACd,YAAoB,EACpB,OAAiC;IAEjC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC/B,IAAI,EACJ,EAAE,GAAG,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,EACrC,YAAY,CACX,MAAM,EACN,IAAI,EACJ,CAAC,GAAG,EAAE,EAAE;QACP,MAAM,OAAO,GAAG,QAAQ,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QACpG,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC,EACD,KAAK,EAAE,OAAO,EAAE,EAAE;QACjB,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACxE,OAAO;QACR,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,GAAG,EAAE,KAAK,KAAK,OAAO,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;QAClC,MAAM,MAAM,CAAC,UAAU,CACtB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAC3C,0EAA0E;YAC1E,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACrE,MAAM,SAAS,GAAG,MAAM;iBACtB,UAAU,EAAE,EAAE;iBACd,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC;iBAC5C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBAClC,MAAM,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;QACF,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC7B,CAAC;IACH,CAAC,EACD,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAC9B,CACD,CAAC;IACF,OAAO,YAAY,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"registerWorker.js","sourceRoot":"","sources":["../../../src/server/internal/registerWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgB,MAAM,EAAc,MAAM,QAAQ,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,UAAU,cAAc,CAC7B,MAAoB,EACpB,IAAY,EACZ,OAAoB,EACpB,GAAoE,EACpE,KAAkB,EAClB,MAAc,EACd,YAAoB,EACpB,OAAiC;IAEjC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC/B,IAAI,EACJ,EAAE,GAAG,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,EACrC,YAAY,CACX,MAAM,EACN,IAAI,EACJ,CAAC,GAAG,EAAE,EAAE;QACP,MAAM,OAAO,GAAG,QAAQ,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QACpG,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC,EACD,KAAK,EAAE,OAAO,EAAE,EAAE;QACjB,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACxE,OAAO;QACR,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,GAAG,EAAE,KAAK,KAAK,OAAO,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;QAClC,MAAM,MAAM,CAAC,UAAU,CACtB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,iFAAiF;YACjF,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC9C,IAAI,SAAS,GAAG,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YAC5D,OAAO,SAAS,GAAG,CAAC,EAAE,CAAC;gBACtB,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBAC/B,SAAS,GAAG,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,SAAS,GAAG,MAAM;iBACtB,UAAU,EAAE,EAAE;iBACd,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC;iBAC5C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBAClC,MAAM,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;QACF,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC7B,CAAC;IACH,CAAC,EACD,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAC9B,CACD,CAAC;IACF,OAAO,YAAY,CAAC;AACrB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { PgBoss } from "pg-boss";
2
+ import type { ScheduleDefinition } from "#definition.ts";
3
+ export declare function testingSchedules(client: () => PgBoss, changed: () => void): {
4
+ schedules: () => import("effect/Effect").Effect<{
5
+ cron: string;
6
+ name: string;
7
+ timezone: string;
8
+ }[], import("../error.ts").PgBossTestingError, never>;
9
+ trigger: (schedule: Pick<ScheduleDefinition, "name" | "scheduleOptions">) => import("effect/Effect").Effect<void, import("../error.ts").PgBossTestingError, never>;
10
+ };
11
+ //# sourceMappingURL=testingSchedules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testingSchedules.d.ts","sourceRoot":"","sources":["../../../src/server/internal/testingSchedules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI;IAExE,SAAS;;;;;IAET,OAAO,aAAa,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,iBAAiB,CAAC;EAczE"}
@@ -0,0 +1,15 @@
1
+ import { testingOperation } from "#internal/testingReadout.ts";
2
+ export function testingSchedules(client, changed) {
3
+ return {
4
+ schedules: () => testingOperation("schedules", async () => (await client().getSchedules()).map(({ name, cron, timezone }) => ({ cron, name, timezone }))),
5
+ trigger: (schedule) => testingOperation("trigger", async () => {
6
+ const [registered] = await client().getSchedules(schedule.name, schedule.scheduleOptions.key ?? "");
7
+ if (registered === undefined) {
8
+ throw new Error("Schedule is not registered");
9
+ }
10
+ await client().send(registered.name, registered.data, { ...registered.options, startAfter: 0 });
11
+ changed();
12
+ }, schedule.name),
13
+ };
14
+ }
15
+ //# sourceMappingURL=testingSchedules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testingSchedules.js","sourceRoot":"","sources":["../../../src/server/internal/testingSchedules.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE/D,MAAM,UAAU,gBAAgB,CAAC,MAAoB,EAAE,OAAmB;IACzE,OAAO;QACN,SAAS,EAAE,GAAG,EAAE,CACf,gBAAgB,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QACzI,OAAO,EAAE,CAAC,QAA8D,EAAE,EAAE,CAC3E,gBAAgB,CACf,SAAS,EACT,KAAK,IAAI,EAAE;YACV,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,MAAM,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;YACpG,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC/C,CAAC;YACD,MAAM,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;YAChG,OAAO,EAAE,CAAC;QACX,CAAC,EACD,QAAQ,CAAC,IAAI,CACb;KACF,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import type { SendOptions } from "pg-boss";
3
+ export declare const transactionDb: Effect.Effect<SendOptions["db"]>;
4
+ //# sourceMappingURL=transactionDb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactionDb.d.ts","sourceRoot":"","sources":["../../../src/server/internal/transactionDb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAczD,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { Effect, Option } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ export const transactionDb = Effect.gen(function* () {
4
+ const sql = yield* Effect.serviceOption(SqlClient.SqlClient);
5
+ if (Option.isNone(sql)) {
6
+ return undefined;
7
+ }
8
+ const transaction = yield* Effect.serviceOption(sql.value.transactionService);
9
+ if (Option.isNone(transaction)) {
10
+ return undefined;
11
+ }
12
+ const [connection] = transaction.value;
13
+ const run = Effect.runPromiseWith(yield* Effect.context());
14
+ return {
15
+ executeSql: async (text, values = []) => ({ rows: Array.from(await run(connection.executeUnprepared(text, values, undefined))) }),
16
+ };
17
+ });
18
+ //# sourceMappingURL=transactionDb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactionDb.js","sourceRoot":"","sources":["../../../src/server/internal/transactionDb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,CAAC,MAAM,aAAa,GAAqC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClF,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC9E,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC;IACvC,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAS,CAAC,CAAC;IAClE,OAAO;QACN,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;KACjI,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -23,6 +23,12 @@ export declare function makePgBossTesting(options?: PgBossTestingOptions): {
23
23
  reason: string;
24
24
  }[], PgBossTestingError, never>;
25
25
  runDeferredNow: (queue: Pick<QueueDefinition, "name">) => Effect.Effect<void, PgBossTestingError, never>;
26
+ schedules: () => Effect.Effect<{
27
+ cron: string;
28
+ name: string;
29
+ timezone: string;
30
+ }[], PgBossTestingError, never>;
31
+ trigger: (schedule: Pick<import("#definition.ts").ScheduleDefinition, "name" | "scheduleOptions">) => Effect.Effect<void, PgBossTestingError, never>;
26
32
  checkBatchWindow: (queue: Pick<QueueDefinition, "name" | "workerOptions">, millisecondsPerJob: number) => Effect.Effect<void, PgBossTestingError, never>;
27
33
  checkSchemaVersion: (expected: number) => Effect.Effect<void, PgBossTestingError, never>;
28
34
  clientFactory: PgBossClientFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/server/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAY,MAAM,QAAQ,CAAC;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAS/C,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC;AAE/F,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,aAAa;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;CAC1D;AAED,eAAO,MAAM,eAAe;;;EAAwD,CAAC;AAErF,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,eAAe,CAAC;CAC9C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,oBAAyB;;;;;;8BA6BxC,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CAAC,sBAAsB,MAAM;mCAqBtE,MAAM;;yBAOhB,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,MAAM;sBAUlD,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,QAAQ,CAAC,KAAK;0BAWnD,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQpD;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/server/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAY,MAAM,QAAQ,CAAC;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAU/C,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC;AAE/F,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,aAAa;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;CAC1D;AAED,eAAO,MAAM,eAAe;;;EAAwD,CAAC;AAErF,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,eAAe,CAAC;CAC9C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,oBAAyB;;;;;;;;;;;;8BA8BxC,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CAAC,sBAAsB,MAAM;mCAqBtE,MAAM;;yBAOhB,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,MAAM;sBAUlD,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,YAAY,QAAQ,CAAC,KAAK;0BAWnD,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQpD;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -5,6 +5,7 @@ import { testingClient } from "#internal/testingClient.ts";
5
5
  import { testingDeferrals } from "#internal/testingDeferrals.ts";
6
6
  import { testingIdle } from "#internal/testingIdle.ts";
7
7
  import { testingOperation, testingReadout } from "#internal/testingReadout.ts";
8
+ import { testingSchedules } from "#internal/testingSchedules.ts";
8
9
  import { testingSignal } from "#internal/testingSignal.ts";
9
10
  import { testingSnapshot } from "#internal/testingSnapshot.ts";
10
11
  export const testRetryPolicy = Object.freeze({ retryBackoff: false, retryDelay: 0 });
@@ -37,6 +38,7 @@ export function makePgBossTesting(options = {}) {
37
38
  };
38
39
  return {
39
40
  ...testingDeferrals(client, () => schema, signal.changed),
41
+ ...testingSchedules(client, signal.changed),
40
42
  checkBatchWindow: (queue, millisecondsPerJob) => testingOperation("checkBatchWindow", async () => {
41
43
  const stored = await client().getQueue(queue.name);
42
44
  if (stored === null) {
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/server/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/server/testing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/jobs",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Effect-native jobs and lifecycle management for pg-boss",
5
5
  "keywords": [
6
6
  "effect"
@@ -44,13 +44,13 @@
44
44
  "README.md"
45
45
  ],
46
46
  "dependencies": {
47
- "@shivaedev/sql": "0.22.0"
47
+ "@shivaedev/sql": "0.23.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@shivaedev/quality": "0.42.0",
51
51
  "@shivaedev/test": "0.10.0",
52
- "@shivaedev/test-sql": "0.10.0",
53
- "@shivaedev/test-story": "0.21.0",
52
+ "@shivaedev/test-sql": "0.10.1",
53
+ "@shivaedev/test-story": "0.21.1",
54
54
  "@types/node": "24.19.2",
55
55
  "effect": "4.0.5",
56
56
  "pg-boss": "12.38.0",
@@ -1,39 +1,31 @@
1
1
  import { Effect, Option, Redacted, Schema } from "effect";
2
2
  import type { PgBossClient } from "#client.ts";
3
- import { type PgBossError, PgBossPayloadError, toPgBossError } from "#error.ts";
3
+ import { PgBossPayloadError, toPgBossError } from "#error.ts";
4
4
  import { capturePayload } from "#internal/tracing.ts";
5
+ import { transactionDb } from "#internal/transactionDb.ts";
5
6
  import type { PgBossService } from "#service.ts";
6
7
 
7
8
  export function enqueueFor(client: PgBossClient): PgBossService["enqueue"] {
8
9
  return (queue, payload, options) =>
9
- Schema.encodeUnknownEffect(queue.schema)(payload).pipe(
10
- Effect.mapError(
11
- (error) =>
12
- new PgBossPayloadError({
13
- direction: "encode",
14
- original: Redacted.make(error),
15
- queue: queue.name,
16
- }),
17
- ),
18
- Effect.flatMap((encoded): Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError> => {
19
- if (typeof encoded !== "object" || encoded === null) {
20
- return Effect.fail(
21
- new PgBossPayloadError({
22
- direction: "encode",
23
- original: Redacted.make(new TypeError("pg-boss payloads must encode to objects")),
24
- queue: queue.name,
25
- }),
26
- );
27
- }
28
- return capturePayload(encoded).pipe(
29
- Effect.flatMap((data) =>
30
- Effect.tryPromise({
31
- catch: (error) => toPgBossError("enqueue", error, queue.name),
32
- try: () => client.send(queue.name, data, queue.key === undefined ? options : { ...options, singletonKey: queue.key(payload) }),
33
- }),
34
- ),
35
- Effect.map(Option.fromNullishOr),
36
- );
37
- }),
38
- );
10
+ Effect.gen(function* () {
11
+ const encoded = yield* Schema.encodeUnknownEffect(queue.schema)(payload).pipe(
12
+ Effect.mapError((error) => new PgBossPayloadError({ direction: "encode", original: Redacted.make(error), queue: queue.name })),
13
+ );
14
+ if (typeof encoded !== "object" || encoded === null) {
15
+ return yield* new PgBossPayloadError({
16
+ direction: "encode",
17
+ original: Redacted.make(new TypeError("pg-boss payloads must encode to objects")),
18
+ queue: queue.name,
19
+ });
20
+ }
21
+ const data = yield* capturePayload(encoded);
22
+ const db = yield* transactionDb;
23
+ const sendOptions = queue.key === undefined ? options : { ...options, singletonKey: queue.key(payload) };
24
+ const send = Effect.tryPromise({
25
+ catch: (error) => toPgBossError("enqueue", error, queue.name),
26
+ try: () => client.send(queue.name, data, db === undefined ? sendOptions : { ...sendOptions, db }),
27
+ });
28
+ const id = yield* db === undefined ? send : Effect.uninterruptible(send);
29
+ return Option.fromNullishOr(id);
30
+ });
39
31
  }
@@ -36,9 +36,13 @@ export function registerWorker(
36
36
  const workId = await registration;
37
37
  await Effect.runPromise(
38
38
  Effect.gen(function* () {
39
- const now = yield* Clock.currentTimeMillis;
40
- // PostgreSQL timestamps retain sub-millisecond precision that Date drops.
41
- yield* Effect.sleep(Math.max(0, job.startAfter.getTime() + 1 - now));
39
+ // Host timers can fire early; PostgreSQL also retains precision that Date drops.
40
+ const deadline = job.startAfter.getTime() + 1;
41
+ let remaining = deadline - (yield* Clock.currentTimeMillis);
42
+ while (remaining > 0) {
43
+ yield* Effect.sleep(remaining);
44
+ remaining = deadline - (yield* Clock.currentTimeMillis);
45
+ }
42
46
  const workerIds = client
43
47
  .getWipData?.()
44
48
  .filter((worker) => worker.workId === workId)
@@ -0,0 +1,23 @@
1
+ import type { PgBoss } from "pg-boss";
2
+ import type { ScheduleDefinition } from "#definition.ts";
3
+ import { testingOperation } from "#internal/testingReadout.ts";
4
+
5
+ export function testingSchedules(client: () => PgBoss, changed: () => void) {
6
+ return {
7
+ schedules: () =>
8
+ testingOperation("schedules", async () => (await client().getSchedules()).map(({ name, cron, timezone }) => ({ cron, name, timezone }))),
9
+ trigger: (schedule: Pick<ScheduleDefinition, "name" | "scheduleOptions">) =>
10
+ testingOperation(
11
+ "trigger",
12
+ async () => {
13
+ const [registered] = await client().getSchedules(schedule.name, schedule.scheduleOptions.key ?? "");
14
+ if (registered === undefined) {
15
+ throw new Error("Schedule is not registered");
16
+ }
17
+ await client().send(registered.name, registered.data, { ...registered.options, startAfter: 0 });
18
+ changed();
19
+ },
20
+ schedule.name,
21
+ ),
22
+ };
23
+ }
@@ -0,0 +1,19 @@
1
+ import { Effect, Option } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import type { SendOptions } from "pg-boss";
4
+
5
+ export const transactionDb: Effect.Effect<SendOptions["db"]> = Effect.gen(function* () {
6
+ const sql = yield* Effect.serviceOption(SqlClient.SqlClient);
7
+ if (Option.isNone(sql)) {
8
+ return undefined;
9
+ }
10
+ const transaction = yield* Effect.serviceOption(sql.value.transactionService);
11
+ if (Option.isNone(transaction)) {
12
+ return undefined;
13
+ }
14
+ const [connection] = transaction.value;
15
+ const run = Effect.runPromiseWith(yield* Effect.context<never>());
16
+ return {
17
+ executeSql: async (text, values = []) => ({ rows: Array.from(await run(connection.executeUnprepared(text, values, undefined))) }),
18
+ };
19
+ });
@@ -8,6 +8,7 @@ import { testingClient } from "#internal/testingClient.ts";
8
8
  import { testingDeferrals } from "#internal/testingDeferrals.ts";
9
9
  import { type TestingWork, testingIdle } from "#internal/testingIdle.ts";
10
10
  import { testingOperation, testingReadout } from "#internal/testingReadout.ts";
11
+ import { testingSchedules } from "#internal/testingSchedules.ts";
11
12
  import { testingSignal } from "#internal/testingSignal.ts";
12
13
  import { testingSnapshot } from "#internal/testingSnapshot.ts";
13
14
 
@@ -56,6 +57,7 @@ export function makePgBossTesting(options: PgBossTestingOptions = {}) {
56
57
  };
57
58
  return {
58
59
  ...testingDeferrals(client, () => schema, signal.changed),
60
+ ...testingSchedules(client, signal.changed),
59
61
  checkBatchWindow: (queue: Pick<QueueDefinition, "name" | "workerOptions">, millisecondsPerJob: number) =>
60
62
  testingOperation(
61
63
  "checkBatchWindow",