@shivaedev/effect-pg-boss 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +40 -3
- package/dist/server/definition.d.ts +2 -1
- package/dist/server/definition.d.ts.map +1 -1
- package/dist/server/definition.js +3 -0
- package/dist/server/definition.js.map +1 -1
- package/dist/server/error.d.ts +1 -0
- package/dist/server/error.d.ts.map +1 -1
- package/dist/server/error.js +12 -0
- package/dist/server/error.js.map +1 -1
- package/dist/server/internal/batchHandler.d.ts.map +1 -1
- package/dist/server/internal/batchHandler.js +15 -2
- package/dist/server/internal/batchHandler.js.map +1 -1
- package/dist/server/internal/clientCache.d.ts +7 -0
- package/dist/server/internal/clientCache.d.ts.map +1 -0
- package/dist/server/internal/clientCache.js +51 -0
- package/dist/server/internal/clientCache.js.map +1 -0
- package/dist/server/internal/clientErrors.d.ts +8 -0
- package/dist/server/internal/clientErrors.d.ts.map +1 -0
- package/dist/server/internal/clientErrors.js +49 -0
- package/dist/server/internal/clientErrors.js.map +1 -0
- package/dist/server/internal/deadlineOf.d.ts +3 -0
- package/dist/server/internal/deadlineOf.d.ts.map +1 -0
- package/dist/server/internal/deadlineOf.js +7 -0
- package/dist/server/internal/deadlineOf.js.map +1 -0
- package/dist/server/internal/ensureQueue.d.ts.map +1 -1
- package/dist/server/internal/ensureQueue.js +19 -2
- package/dist/server/internal/ensureQueue.js.map +1 -1
- package/dist/server/internal/lifecycle.d.ts +2 -2
- package/dist/server/internal/lifecycle.d.ts.map +1 -1
- package/dist/server/internal/lifecycle.js +36 -79
- package/dist/server/internal/lifecycle.js.map +1 -1
- package/dist/server/internal/register.d.ts +2 -2
- package/dist/server/internal/register.d.ts.map +1 -1
- package/dist/server/internal/register.js +10 -5
- package/dist/server/internal/register.js.map +1 -1
- package/dist/server/service.d.ts.map +1 -1
- package/dist/server/service.js +4 -12
- package/dist/server/service.js.map +1 -1
- package/docs/roadmap.md +6 -5
- package/package.json +6 -5
- package/src/server/definition.ts +4 -1
- package/src/server/error.ts +15 -1
- package/src/server/internal/batchHandler.ts +15 -2
- package/src/server/internal/clientCache.ts +61 -0
- package/src/server/internal/clientErrors.ts +64 -0
- package/src/server/internal/deadlineOf.ts +8 -0
- package/src/server/internal/ensureQueue.ts +20 -2
- package/src/server/internal/lifecycle.ts +37 -97
- package/src/server/internal/register.ts +10 -6
- package/src/server/service.ts +5 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `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.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- Observe pg-boss connection errors throughout startup and shutdown, and log administrator-terminated connections at debug while preserving query failures and application error callbacks.
|
|
13
|
+
- Keep observing idle errors from closing sockets after shutdown, when shutdown fails, and when `stop({ close: false })` leaves the pool open.
|
|
14
|
+
|
|
15
|
+
## 0.8.0 - 2026-10-17
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `JobContext.remainingBudget` is an `Effect.Effect<Duration.Duration>` that reads the time left until the fetched job's expiry deadline, clamped to zero.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Bound queue and scheduled handlers by each job's pg-boss `startedOn` plus `expireInSeconds`, including time spent waiting in a batch. Skip jobs whose window has passed and interrupt running Effects at their deadline, leaving interrupted and unstarted jobs for pg-boss expiry and retry. Worker registration requests start-time metadata even when `includeMetadata: false` is supplied.
|
|
24
|
+
|
|
3
25
|
## 0.7.1 - 2026-10-17
|
|
4
26
|
|
|
5
27
|
### Added
|
package/README.md
CHANGED
|
@@ -116,6 +116,43 @@ This worker runs up to five handlers at once. `worker` accepts pg-boss 12's `Wor
|
|
|
116
116
|
|
|
117
117
|
Apps do not need to shorten the polling backstop to drain a backlog. With `notify: true` and an active LISTEN/NOTIFY listener, pg-boss wakes workers on notifications and defaults to a 30-second backstop (`notifyPollingIntervalSeconds`). Without active notifications its base poll defaults to two seconds (`pollingIntervalSeconds`). Bursting takes precedence over both intervals. Override the backstop only when the application needs a different bound for work that becomes runnable without a notification, such as delayed jobs or retries. The PostgreSQL backlog tests insert 30 jobs at once and verify completion within 2.5 seconds with both defaults and configured batches and concurrency.
|
|
118
118
|
|
|
119
|
+
### Size batches against expiry
|
|
120
|
+
|
|
121
|
+
pg-boss marks every job in a fetched batch active together. Each job's expiry window starts at its stored `startedOn`, including time waiting for earlier handlers. Size each queue's batch so its sequential work fits within that window; use a small batch for long-running work and keep the default for short work:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const Ingest = defineQueue({
|
|
125
|
+
name: "ingest",
|
|
126
|
+
schema: Schema.Struct({ sourceId: Schema.String }),
|
|
127
|
+
queue: { expireInSeconds: 300 },
|
|
128
|
+
worker: { batchSize: 1 },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const IndexTitle = defineQueue({
|
|
132
|
+
name: "index-title",
|
|
133
|
+
schema: Schema.Struct({ titleId: Schema.String }),
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`Ingest` fetches one job at a time; `IndexTitle` keeps the default batch of ten. Configure the expiry for the work's expected duration. Increasing `localConcurrency` adds batches; it does not extend any job's window.
|
|
138
|
+
|
|
139
|
+
The adapter enforces each queue or scheduled job's deadline using pg-boss's `startedOn` and `expireInSeconds`. It skips jobs whose window has already passed and interrupts a running Effect at its deadline. Expired jobs remain for pg-boss's expiry and retry engine. Interruption stops the rest of that batch, as described above.
|
|
140
|
+
|
|
141
|
+
Queue handlers can yield `job.remainingBudget`, an `Effect.Effect<Duration.Duration>` that recomputes the time left at each read and clamps it to zero. Use it to size a downstream operation's timeout:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { Duration, Effect } from "effect";
|
|
145
|
+
|
|
146
|
+
const ingestWorker = Ingest.handle(({ sourceId }, job) =>
|
|
147
|
+
Effect.gen(function* () {
|
|
148
|
+
const budget = yield* job.remainingBudget;
|
|
149
|
+
yield* Effect.log({ sourceId, remainingMilliseconds: Duration.toMillis(budget) });
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Keep handler work interruptible and use cancellation-aware Effect integrations for external operations. A deadline interrupts the Effect fiber; it cannot stop an uninterruptible region or an external side effect that ignores cancellation. Handlers must tolerate retries.
|
|
155
|
+
|
|
119
156
|
### Once per runtime: provide the workers
|
|
120
157
|
|
|
121
158
|
Build the jobs Layer in `runtime.ts`:
|
|
@@ -133,7 +170,7 @@ export const jobsLayer = (databaseUrl: string) =>
|
|
|
133
170
|
}).pipe(Layer.provide(MailLive));
|
|
134
171
|
```
|
|
135
172
|
|
|
136
|
-
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.
|
|
137
174
|
|
|
138
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.
|
|
139
176
|
|
|
@@ -313,9 +350,9 @@ The application's service has these operations:
|
|
|
313
350
|
|
|
314
351
|
`onError` handles pg-boss client `error` events using the services captured by the Layer. The Layer's requirement type includes the workers' services and their payload Schemas' `DecodingServices`, excluding its own jobs identifier, plus the callback's services. Only the owning jobs service is supplied automatically; a handler using another jobs definition still requires that definition's Layer. `enqueue` includes the payload Schema's `EncodingServices` in its requirement type.
|
|
315
352
|
|
|
316
|
-
`JobContext` has `id`, `name`, `signal`, `expireInSeconds` and `
|
|
353
|
+
`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.
|
|
317
354
|
|
|
318
|
-
`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.
|
|
355
|
+
`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.
|
|
319
356
|
|
|
320
357
|
### Install, setup and limits
|
|
321
358
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Duration, Effect, type Schema } from "effect";
|
|
2
2
|
import type { Job, Queue, ScheduleOptions, WorkOptions } from "pg-boss";
|
|
3
3
|
export interface JobPayloadSchema extends Schema.Top {
|
|
4
4
|
readonly Encoded: object;
|
|
@@ -9,6 +9,7 @@ export interface JobContext {
|
|
|
9
9
|
readonly heartbeatSeconds: number | null;
|
|
10
10
|
readonly id: string;
|
|
11
11
|
readonly name: string;
|
|
12
|
+
readonly remainingBudget: Effect.Effect<Duration.Duration>;
|
|
12
13
|
readonly signal: AbortSignal;
|
|
13
14
|
}
|
|
14
15
|
export interface QueueDefinition<Name extends string = string, Payload extends JobPayloadSchema = JobPayloadSchema> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/server/definition.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/server/definition.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Clock, Duration, Effect } from "effect";
|
|
2
|
+
import { deadlineOf } from "#internal/deadlineOf.ts";
|
|
1
3
|
export const defineQueue = (options) => {
|
|
2
4
|
const definition = {
|
|
3
5
|
_tag: "QueueDefinition",
|
|
@@ -34,6 +36,7 @@ export const jobContext = (job) => ({
|
|
|
34
36
|
heartbeatSeconds: job.heartbeatSeconds,
|
|
35
37
|
id: job.id,
|
|
36
38
|
name: job.name,
|
|
39
|
+
remainingBudget: Effect.map(Clock.currentTimeMillis, (now) => Duration.millis(Math.max(0, deadlineOf(job) - now))),
|
|
37
40
|
signal: job.signal,
|
|
38
41
|
});
|
|
39
42
|
//# sourceMappingURL=definition.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/server/definition.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/server/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAoErD,MAAM,CAAC,MAAM,WAAW,GAAG,CAC1B,OAA0C,EACT,EAAE;IACnC,MAAM,UAAU,GAAmC;QAClD,IAAI,EAAE,iBAAiB;QACvB,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CACnB,MAAM,CAAC,MAAM,CAAC;YACb,IAAI,EAAE,aAAsB;YAC5B,OAAO;YACP,KAAK,EAAE,UAAU;SACjB,CAAC;QACH,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QACjD,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,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,CAAC;AAUF,MAAM,CAAC,MAAM,cAAc,GAAG,CAA4B,OAAoC,EAA4B,EAAE;IAC3H,MAAM,UAAU,GAA6B;QAC5C,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QACjD,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,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAiB,EAAc,EAAE,CAAC,CAAC;IAC7D,eAAe,EAAE,GAAG,CAAC,eAAe;IACpC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;IACtC,EAAE,EAAE,GAAG,CAAC,EAAE;IACV,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAClH,MAAM,EAAE,GAAG,CAAC,MAAM;CAClB,CAAC,CAAC"}
|
package/dist/server/error.d.ts
CHANGED
|
@@ -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";
|
|
@@ -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;
|
|
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,eAAO,MAAM,aAAa,cAAe,eAAe,SAAS,OAAO,UAAU,MAAM,KAAG,WAKxF,CAAC"}
|
package/dist/server/error.js
CHANGED
|
@@ -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() {
|
package/dist/server/error.js.map
CHANGED
|
@@ -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;
|
|
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,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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batchHandler.d.ts","sourceRoot":"","sources":["../../../src/server/internal/batchHandler.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"batchHandler.d.ts","sourceRoot":"","sources":["../../../src/server/internal/batchHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAQ,MAAM,QAAQ,CAAC;AACpD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ/C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,UAMvG,SAAS,GAAG,CAAC,OAAO,CAAC,EAAE,KAAG,OAAO,CAAC,IAAI,CAAC,CA6B3D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Cause, Effect, Exit } from "effect";
|
|
1
|
+
import { Cause, Clock, Effect, Exit } from "effect";
|
|
2
|
+
import { deadlineOf } from "#internal/deadlineOf.ts";
|
|
2
3
|
function failureOutput(cause) {
|
|
3
4
|
const error = Cause.squash(cause);
|
|
4
5
|
return typeof error === "object" && error !== null ? error : { message: String(error) };
|
|
@@ -10,8 +11,17 @@ export function batchHandler(client, name, run) {
|
|
|
10
11
|
throw new TypeError("This pg-boss client does not support complete and fail");
|
|
11
12
|
}
|
|
12
13
|
return async (jobs) => {
|
|
14
|
+
let expired = false;
|
|
13
15
|
for (const job of jobs) {
|
|
14
|
-
const
|
|
16
|
+
const deadline = deadlineOf(job);
|
|
17
|
+
if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
|
|
18
|
+
expired = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const exit = await Effect.runPromiseExit(Effect.flatMap(Clock.currentTimeMillis, (now) => {
|
|
22
|
+
const remaining = deadline - now;
|
|
23
|
+
return remaining <= 0 ? Effect.interrupt : run(job).pipe(Effect.timeoutOrElse({ duration: remaining, orElse: () => Effect.interrupt }));
|
|
24
|
+
}), { signal: job.signal });
|
|
15
25
|
if (job.signal.aborted || (Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause))) {
|
|
16
26
|
// Returning would let pg-boss complete the unprocessed jobs; its expiry owns them instead.
|
|
17
27
|
return new Promise(() => undefined);
|
|
@@ -23,6 +33,9 @@ export function batchHandler(client, name, run) {
|
|
|
23
33
|
await fail(name, job, failureOutput(exit.cause));
|
|
24
34
|
}
|
|
25
35
|
}
|
|
36
|
+
if (expired) {
|
|
37
|
+
return new Promise(() => undefined);
|
|
38
|
+
}
|
|
26
39
|
};
|
|
27
40
|
}
|
|
28
41
|
//# sourceMappingURL=batchHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batchHandler.js","sourceRoot":"","sources":["../../../src/server/internal/batchHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"batchHandler.js","sourceRoot":"","sources":["../../../src/server/internal/batchHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,SAAS,aAAa,CAAC,KAA2B;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAoB,EAAE,IAAY,EAAE,GAA2D;IAC3H,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,QAAQ,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAClD,MAAM,IAAI,SAAS,CAAC,wDAAwD,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,KAAK,EAAE,IAA6B,EAAiB,EAAE;QAC7D,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACpE,OAAO,GAAG,IAAI,CAAC;gBACf,SAAS;YACV,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,cAAc,CACvC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC/C,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,CAAC;gBACjC,OAAO,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;YACzI,CAAC,CAAC,EACF,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CACtB,CAAC;YACF,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBACrF,2FAA2F;gBAC3F,OAAO,IAAI,OAAO,CAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC;YACD,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,MAAM,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAClD,CAAC;QACF,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,OAAO,CAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5C,CAAC;IACF,CAAC,CAAC;AACH,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deadlineOf.d.ts","sourceRoot":"","sources":["../../../src/server/internal/deadlineOf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAEnC,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,CAKpD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function deadlineOf(job) {
|
|
2
|
+
if (!("startedOn" in job && job.startedOn instanceof Date && Number.isFinite(job.startedOn.getTime()))) {
|
|
3
|
+
throw new TypeError("Worker jobs must include pg-boss startedOn metadata");
|
|
4
|
+
}
|
|
5
|
+
return job.startedOn.getTime() + job.expireInSeconds * 1000;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=deadlineOf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deadlineOf.js","sourceRoot":"","sources":["../../../src/server/internal/deadlineOf.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,GAAiB;IAC3C,IAAI,CAAC,CAAC,WAAW,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,YAAY,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;QACxG,MAAM,IAAI,SAAS,CAAC,qDAAqD,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC;AAC7D,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;
|
|
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
|
|
7
|
-
await
|
|
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,
|
|
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"}
|
|
@@ -11,6 +11,6 @@ export interface AcquiredClient {
|
|
|
11
11
|
readonly client: PgBossClient;
|
|
12
12
|
readonly reused: boolean;
|
|
13
13
|
}
|
|
14
|
-
export declare
|
|
15
|
-
export declare
|
|
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;
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|