@shivaedev/effect-pg-boss 0.8.1 → 0.10.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 +13 -0
- package/README.md +75 -24
- package/dist/server/error.d.ts +10 -1
- package/dist/server/error.d.ts.map +1 -1
- package/dist/server/error.js +2 -0
- package/dist/server/error.js.map +1 -1
- package/dist/server/internal/expireAttempt.d.ts +3 -0
- package/dist/server/internal/expireAttempt.d.ts.map +1 -0
- package/dist/server/internal/expireAttempt.js +14 -0
- package/dist/server/internal/expireAttempt.js.map +1 -0
- package/dist/server/internal/observeTestingSettlements.d.ts +6 -0
- package/dist/server/internal/observeTestingSettlements.d.ts.map +1 -0
- package/dist/server/internal/observeTestingSettlements.js +35 -0
- package/dist/server/internal/observeTestingSettlements.js.map +1 -0
- package/dist/server/internal/runSchemaPlan.d.ts +7 -0
- package/dist/server/internal/runSchemaPlan.d.ts.map +1 -0
- package/dist/server/internal/runSchemaPlan.js +25 -0
- package/dist/server/internal/runSchemaPlan.js.map +1 -0
- package/dist/server/internal/schemaIndexBuilds.d.ts +4 -0
- package/dist/server/internal/schemaIndexBuilds.d.ts.map +1 -0
- package/dist/server/internal/schemaIndexBuilds.js +34 -0
- package/dist/server/internal/schemaIndexBuilds.js.map +1 -0
- package/dist/server/internal/testingClient.d.ts +10 -0
- package/dist/server/internal/testingClient.d.ts.map +1 -0
- package/dist/server/internal/testingClient.js +101 -0
- package/dist/server/internal/testingClient.js.map +1 -0
- package/dist/server/internal/testingExpirations.d.ts +6 -0
- package/dist/server/internal/testingExpirations.d.ts.map +1 -0
- package/dist/server/internal/testingExpirations.js +27 -0
- package/dist/server/internal/testingExpirations.js.map +1 -0
- package/dist/server/internal/testingReadout.d.ts +44 -0
- package/dist/server/internal/testingReadout.d.ts.map +1 -0
- package/dist/server/internal/testingReadout.js +13 -0
- package/dist/server/internal/testingReadout.js.map +1 -0
- package/dist/server/internal/testingSignal.d.ts +7 -0
- package/dist/server/internal/testingSignal.d.ts.map +1 -0
- package/dist/server/internal/testingSignal.js +27 -0
- package/dist/server/internal/testingSignal.js.map +1 -0
- package/dist/server/schema.d.ts +8 -0
- package/dist/server/schema.d.ts.map +1 -0
- package/dist/server/schema.js +43 -0
- package/dist/server/schema.js.map +1 -0
- package/dist/server/testing.d.ts +61 -0
- package/dist/server/testing.d.ts.map +1 -0
- package/dist/server/testing.js +69 -0
- package/dist/server/testing.js.map +1 -0
- package/package.json +7 -8
- package/src/server/error.ts +7 -1
- package/src/server/internal/expireAttempt.ts +18 -0
- package/src/server/internal/observeTestingSettlements.ts +36 -0
- package/src/server/internal/runSchemaPlan.ts +32 -0
- package/src/server/internal/schemaIndexBuilds.ts +44 -0
- package/src/server/internal/testingClient.ts +115 -0
- package/src/server/internal/testingExpirations.ts +31 -0
- package/src/server/internal/testingReadout.ts +26 -0
- package/src/server/internal/testingSignal.ts +28 -0
- package/src/server/schema.ts +57 -0
- package/src/server/testing.ts +85 -0
- package/docs/north-star.md +0 -48
- package/docs/roadmap.md +0 -35
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `installPgBossSchema(options?: PgBossSchemaOptions)` in `schema.ts` installs or upgrades the pg-boss schema through an Effect SQL client without starting workers or schedules. Use it in a test template fixture or release step, then start jobs Layers with `migrate: false`. Failures use `PgBossError` with the new `"install"` operation. Retrying a failed concurrent upgrade rebuilds missing and invalid managed indexes even when the schema version has already committed.
|
|
8
|
+
|
|
9
|
+
## 0.9.0 - 2026-10-18
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
- Expiry rules stay with each successfully sent job, and native terminal worker timeouts wake queue-change waiters even when a handler promise stays pending.
|
|
15
|
+
|
|
3
16
|
## 0.8.1 - 2026-10-18
|
|
4
17
|
|
|
5
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -37,6 +37,20 @@ Use a distinct literal identifier for each jobs service. The service and its Lay
|
|
|
37
37
|
|
|
38
38
|
The work separates naturally: declare the application services once, define a queue and its worker for each feature, provide the complete registration list at the runtime boundary, and enqueue from ordinary application Effects.
|
|
39
39
|
|
|
40
|
+
### Once per database: install the schema
|
|
41
|
+
|
|
42
|
+
`installPgBossSchema` installs or upgrades pg-boss through the provided Effect SQL client without starting workers or schedules. Run it outside an application transaction, in a release step or the `fixtures` Effect of `makePostgresTestLayer`, so stories inherit the schema from the migrated template:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { installPgBossSchema } from "@shivaedev/effect-pg-boss/schema.ts";
|
|
46
|
+
|
|
47
|
+
const jobsFixture = installPgBossSchema({ schema: "app_jobs" });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The Effect requires `SqlClient` and fails with `PgBossError` whose `operation` is `"install"`. The schema defaults to `"pgboss"`. Repeating installation preserves existing jobs. Upgrades apply pg-boss's migration SQL and concurrent index builds, including partition indexes, before returning. Pending index commands are recorded in pg-boss's BAM table in the same transaction as the schema version. A retry resumes unfinished commands and repairs invalid indexes left by failed builds, even when the schema version has already committed.
|
|
51
|
+
|
|
52
|
+
After installation, pass the same schema and `migrate: false` to `Jobs.layer` to start producers and workers without schema installation or migration. Startup fails if the schema is missing. Ordinary Layer startup retains pg-boss's default migration behavior when `migrate` is omitted.
|
|
53
|
+
|
|
40
54
|
### Once per application: the jobs service
|
|
41
55
|
|
|
42
56
|
Define the application's jobs service and worker dependencies in `services.ts`:
|
|
@@ -114,7 +128,7 @@ const Compare = defineQueue({
|
|
|
114
128
|
|
|
115
129
|
This worker runs up to five handlers at once. `worker` accepts pg-boss 12's `WorkOptions`, including `batchSize`, `localConcurrency`, `pollingIntervalSeconds`, `notifyPollingIntervalSeconds` and the burst options. Explicit options override the adapter defaults. pg-boss ignores `burstWhenBatchFull` at `batchSize: 1`; keep a batch size of at least two for immediate backlog draining. Each job is settled separately through pg-boss: success completes that job, and a typed failure, payload decoding failure or defect fails only that job with its own error. A failed job does not spend the retries of other jobs in its batch or rerun their successful handlers. Interruption stops the batch and leaves the interrupted and remaining jobs to pg-boss expiry; handlers must still tolerate repeated side effects across retries and interrupted attempts.
|
|
116
130
|
|
|
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.
|
|
131
|
+
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.
|
|
118
132
|
|
|
119
133
|
### Size batches against expiry
|
|
120
134
|
|
|
@@ -206,7 +220,7 @@ export const enqueueEmail = (userId: number) =>
|
|
|
206
220
|
|
|
207
221
|
Provide `Jobs` through the application's existing runtime. `enqueue` takes the decoded payload, so passing a string `userId` is a type error. It encodes the number to a string before calling pg-boss. The result type is `Option.Option<string>`: a job identifier becomes `Some`, and a `null` send result becomes `None`.
|
|
208
222
|
|
|
209
|
-
`PgBossPayloadError` names a payload boundary failure; malformed durable input is rejected before the worker's domain code runs.
|
|
223
|
+
`PgBossPayloadError` names a payload boundary failure; malformed durable input is rejected before the worker's domain code runs.
|
|
210
224
|
|
|
211
225
|
### Trace jobs
|
|
212
226
|
|
|
@@ -214,7 +228,7 @@ Each queue or scheduled job attempt runs in one root consumer span named for its
|
|
|
214
228
|
|
|
215
229
|
`enqueue` captures the current Effect span, including an external parent span, when present. It stores that context in a typed envelope in pg-boss's existing job `data`; the adapter unwraps the envelope before Schema decoding in the handler and `getJob`, so the application's payload Schema stays unchanged. Jobs sent without a span keep their ordinary encoded payload, and workers also accept those unwrapped jobs. Direct pg-boss data readers see the envelope for traced sends.
|
|
216
230
|
|
|
217
|
-
The consumer span links to the sender rather than sharing its parent trace, following OpenTelemetry messaging conventions for asynchronous processing. Each retry gets its own job trace. Provide the application's Effect tracer when building the worker Layer; the worker uses that captured tracer.
|
|
231
|
+
The consumer span links to the sender rather than sharing its parent trace, following OpenTelemetry messaging conventions for asynchronous processing. Each retry gets its own job trace. Provide the application's Effect tracer when building the worker Layer; the worker uses that captured tracer.
|
|
218
232
|
|
|
219
233
|
### Read health
|
|
220
234
|
|
|
@@ -261,7 +275,7 @@ export const inspectEmail = (id: string) => Effect.gen(function* () {
|
|
|
261
275
|
|
|
262
276
|
### Queue settings on startup
|
|
263
277
|
|
|
264
|
-
`queue` accepts pg-boss `Queue` options except `name` and `deadLetter`, which the definition and adapter own. For prompt delivery, set `queue: { notify: true }` and enable `useListenNotify: true` in the worker Layer's constructor options.
|
|
278
|
+
`queue` accepts pg-boss `Queue` options except `name` and `deadLetter`, which the definition and adapter own. For prompt delivery, set `queue: { notify: true }` and enable `useListenNotify: true` in the worker Layer's constructor options.
|
|
265
279
|
|
|
266
280
|
Startup creates missing queues and reconciles declared settings on existing queues through `updateQueue`. Unspecified settings keep their stored values; the adapter always supplies its default retry settings and dead-letter destination. Changing an explicitly declared `policy` or `partition` on an existing queue fails startup with `PgBossQueueOptionsError`; its `queue`, `options` and message identify the conflict. Queue setting changes apply to subsequently enqueued jobs. Schedule queues follow the same registration path.
|
|
267
281
|
|
|
@@ -281,7 +295,7 @@ const sharedJobsLayer = (databaseUrl: string) =>
|
|
|
281
295
|
}).pipe(Layer.provide(MailLive));
|
|
282
296
|
```
|
|
283
297
|
|
|
284
|
-
Overlapping Layer builds with the same `clientCacheKey` share one started client. The later build replaces the registered worker, and the final scope release stops the shared client. A later build after that release starts a new client. Without a key, each build creates its own client. The application chooses whether to pass a key.
|
|
298
|
+
Overlapping Layer builds with the same `clientCacheKey` share one started client. The later build replaces the registered worker, and the final scope release stops the shared client. A later build after that release starts a new client. Without a key, each build creates its own client. The application chooses whether to pass a key; give each distinct constructor configuration its own key.
|
|
285
299
|
|
|
286
300
|
### Separate producers and workers
|
|
287
301
|
|
|
@@ -295,7 +309,7 @@ export const producerLayer = (databaseUrl: string) =>
|
|
|
295
309
|
Jobs.layer({ connectionString: databaseUrl, jobs: [SendEmail] });
|
|
296
310
|
```
|
|
297
311
|
|
|
298
|
-
Provide this Layer to the request runtime and call `jobs.enqueue(SendEmail, { userId })` as above. A separate worker process registers `SendEmail.handle(...)` against the same database. Producer queues use the same retry options and appear in `health`; only worker registrations capture handler services.
|
|
312
|
+
Provide this Layer to the request runtime and call `jobs.enqueue(SendEmail, { userId })` as above. A separate worker process registers `SendEmail.handle(...)` against the same database. Producer queues use the same retry options and appear in `health`; only worker registrations capture handler services.
|
|
299
313
|
|
|
300
314
|
### Enqueue follow-ups from a handler
|
|
301
315
|
|
|
@@ -322,7 +336,54 @@ const live = Jobs.layer({
|
|
|
322
336
|
}).pipe(Layer.provide(MailLive));
|
|
323
337
|
```
|
|
324
338
|
|
|
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.
|
|
339
|
+
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.
|
|
340
|
+
|
|
341
|
+
### Test through the queue contract
|
|
342
|
+
|
|
343
|
+
`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.
|
|
344
|
+
|
|
345
|
+
This producer story uses the `SendEmail` definition and `Jobs` service from above. `databaseUrl` points to the story's PostgreSQL database:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { Context, Layer, Option } from "effect";
|
|
349
|
+
import { expect } from "vitest";
|
|
350
|
+
import { storyKit } from "@shivaedev/test-story/storyKit.ts";
|
|
351
|
+
import { makePgBossTesting, testRetryPolicy } from "@shivaedev/effect-pg-boss/testing.ts";
|
|
352
|
+
|
|
353
|
+
const emailStories = storyKit({
|
|
354
|
+
clock: "live",
|
|
355
|
+
*create() {
|
|
356
|
+
const testing = makePgBossTesting({ retryPolicy: testRetryPolicy });
|
|
357
|
+
const context = yield* Layer.build(Jobs.layer({
|
|
358
|
+
connectionString: databaseUrl,
|
|
359
|
+
clientFactory: testing.clientFactory,
|
|
360
|
+
jobs: [SendEmail],
|
|
361
|
+
}));
|
|
362
|
+
return { jobs: Context.get(context, Jobs), testing };
|
|
363
|
+
},
|
|
364
|
+
name: "queued email",
|
|
365
|
+
stages: ["ready"],
|
|
366
|
+
verbs: ({ jobs, testing }) => ({
|
|
367
|
+
enqueue: (userId: number) => jobs.enqueue(SendEmail, { userId }),
|
|
368
|
+
testing,
|
|
369
|
+
}),
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
emailStories.it("decodes the queued email", [], function* ({ enqueue, testing }) {
|
|
373
|
+
const id = Option.getOrThrow(yield* enqueue(42));
|
|
374
|
+
expect(yield* testing.jobs(SendEmail)).toMatchObject([
|
|
375
|
+
{ id, data: { userId: 42 }, state: "created" },
|
|
376
|
+
]);
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
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.
|
|
381
|
+
|
|
382
|
+
`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.
|
|
383
|
+
|
|
384
|
+
`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`.
|
|
385
|
+
|
|
386
|
+
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.
|
|
326
387
|
|
|
327
388
|
### API
|
|
328
389
|
|
|
@@ -332,25 +393,19 @@ Import the module that defines the name; the package has no root entry.
|
|
|
332
393
|
| --- | --- |
|
|
333
394
|
| `@shivaedev/effect-pg-boss/definition.ts` | `defineQueue`, `defineSchedule`, `jobContext`; `DefineQueueOptions`, `DefineScheduleOptions`, `JobContext`, `JobPayloadSchema`, `JobRegistration`, `QueueDefinition`, `QueueWorker`, `RegistrationRequirements`, `ScheduleDefinition`, `ScheduledWorker` |
|
|
334
395
|
| `@shivaedev/effect-pg-boss/service.ts` | `makePgBoss`; `PgBossDefinition`, `PgBossLayerOptions`, `PgBossService` |
|
|
396
|
+
| `@shivaedev/effect-pg-boss/schema.ts` | `installPgBossSchema`; `PgBossSchemaOptions` |
|
|
335
397
|
| `@shivaedev/effect-pg-boss/health.ts` | `deadLetterQueueName`; `JobsHealth`, `QueueCounts`, `QueueHealth` |
|
|
336
|
-
| `@shivaedev/effect-pg-boss/error.ts` | `PgBossError`, `PgBossPayloadError`, `PgBossQueueOptionsError`, `toPgBossError`; `PgBossOperation` |
|
|
398
|
+
| `@shivaedev/effect-pg-boss/error.ts` | `PgBossError`, `PgBossPayloadError`, `PgBossQueueOptionsError`, `PgBossTestingError`, `toPgBossError`; `PgBossOperation` |
|
|
337
399
|
| `@shivaedev/effect-pg-boss/client.ts` | `defaultClientFactory`; `PgBossClient`, `PgBossClientFactory` |
|
|
400
|
+
| `@shivaedev/effect-pg-boss/testing.ts` | `makePgBossTesting`, `testRetryPolicy`; `PgBossTesting`, `PgBossTestingOptions` |
|
|
338
401
|
|
|
339
402
|
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
403
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
- `enqueue(queue, payload, options?)`: an Effect returning `Option.Option<string>`, with `PgBossError | PgBossPayloadError` in its error type. Its options use pg-boss `SendOptions`.
|
|
344
|
-
- `health`: an Effect returning cached `JobsHealth`, with `PgBossError` in its error type.
|
|
345
|
-
- `counts(queue)`: an Effect returning live `QueueCounts`, with `PgBossError` in its error type.
|
|
346
|
-
- `getJob(queue, id)`: an Effect returning `Option.Option<JobWithMetadata<Payload>>` with decoded payload data, `PgBossError | PgBossPayloadError` in its error type and the Schema's decoding services in its requirements.
|
|
347
|
-
- `retry(queue, id)`: an Effect returning `void`, with `PgBossError` in its error type.
|
|
404
|
+
`Jobs.layer(options)` accepts pg-boss `ConstructorOptions` plus the adapter's own options. Custom clients must supply native `complete` and `fail` capabilities to register queue or schedule workers; missing settlement capabilities fail Layer acquisition with a registration `PgBossError`. Producer-only clients do not need them. Custom clients can also supply the optional native capabilities `findJobs`, `retry`, `update`, `getDb` and `updateQueue` for lookup, retry, live counts and reconciliation; a requested operation fails with `PgBossError` if its required capability is absent.
|
|
348
405
|
|
|
349
|
-
`
|
|
406
|
+
`onError` handles pg-boss client `error` events using the services captured by the Layer. Only the owning jobs service is supplied automatically; a handler using another jobs definition still requires that definition's Layer.
|
|
350
407
|
|
|
351
|
-
|
|
352
|
-
|
|
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.
|
|
408
|
+
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.
|
|
354
409
|
|
|
355
410
|
`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
411
|
|
|
@@ -362,8 +417,4 @@ pnpm add @shivaedev/effect-pg-boss effect@4.0.4 pg-boss@12.37.0
|
|
|
362
417
|
|
|
363
418
|
The package requires Node.js 24 or later and PostgreSQL. `effect` and `pg-boss` are peers; use versions that satisfy the package's published peer ranges.
|
|
364
419
|
|
|
365
|
-
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
|
|
366
|
-
|
|
367
|
-
For package development, the real PostgreSQL test uses `PLATFORM_EFFECT_PG_BOSS_TEST_DATABASE_URL` and skips when the variable is absent. A skipped test does not establish durable queue behavior. Fake-client tests cover registration and lifecycle calls; they do not establish real cron execution, retry timing or deployment behavior. The package is built for ShivaeDev applications and may change without a deprecation period.
|
|
368
|
-
|
|
369
|
-
The [north star](./docs/north-star.md) explains the design trade-offs; the [roadmap](./docs/roadmap.md) owns package work and open choices.
|
|
420
|
+
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.
|
package/dist/server/error.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Redacted } from "effect";
|
|
2
|
-
export type PgBossOperation = "counts" | "enqueue" | "getJob" | "health" | "register" | "retry" | "start" | "stop";
|
|
2
|
+
export type PgBossOperation = "counts" | "enqueue" | "getJob" | "health" | "install" | "register" | "retry" | "start" | "stop";
|
|
3
3
|
declare const PgBossError_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 & {
|
|
4
4
|
readonly _tag: "PgBossError";
|
|
5
5
|
} & Readonly<A>;
|
|
@@ -28,6 +28,15 @@ export declare class PgBossPayloadError extends PgBossPayloadError_base<{
|
|
|
28
28
|
readonly original: Redacted.Redacted<unknown>;
|
|
29
29
|
}> {
|
|
30
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
|
+
}
|
|
31
40
|
export declare const toPgBossError: (operation: PgBossOperation, error: unknown, queue?: string) => PgBossError;
|
|
32
41
|
export {};
|
|
33
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;;;;
|
|
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,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;;;;AAE/H,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"}
|
package/dist/server/error.js
CHANGED
|
@@ -20,6 +20,8 @@ export class PgBossQueueOptionsError extends Data.TaggedError("PgBossQueueOption
|
|
|
20
20
|
}
|
|
21
21
|
export class PgBossPayloadError extends Data.TaggedError("PgBossPayloadError") {
|
|
22
22
|
}
|
|
23
|
+
export class PgBossTestingError extends Data.TaggedError("PgBossTestingError") {
|
|
24
|
+
}
|
|
23
25
|
export const toPgBossError = (operation, error, queue) => new PgBossError({
|
|
24
26
|
operation,
|
|
25
27
|
original: Redacted.make(error),
|
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;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
|
+
{"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 @@
|
|
|
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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
import type { Connection } from "effect/sql/SqlConnection";
|
|
3
|
+
export declare function runSchemaPlan(connection: Connection, plan: string, run: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>, upgrade?: {
|
|
4
|
+
readonly schema: string;
|
|
5
|
+
readonly version: number;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=runSchemaPlan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runSchemaPlan.d.ts","sourceRoot":"","sources":["../../../src/server/internal/runSchemaPlan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAG3D,wBAAsB,aAAa,CAClC,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EACtD,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { pendingIndexInserts } from "./schemaIndexBuilds.js";
|
|
2
|
+
export async function runSchemaPlan(connection, plan, run, upgrade) {
|
|
3
|
+
// Native exported plans append concurrent index DDL after the migration transaction.
|
|
4
|
+
const parts = /^\s*BEGIN;(?<body>[\s\S]*)\n {4}COMMIT;(?<concurrent>[\s\S]*)$/u.exec(plan);
|
|
5
|
+
if (parts?.groups === undefined) {
|
|
6
|
+
throw new Error("Unrecognized pg-boss schema plan transaction");
|
|
7
|
+
}
|
|
8
|
+
const { body = "", concurrent = "" } = parts.groups;
|
|
9
|
+
function quote(text) {
|
|
10
|
+
return `'${text.replaceAll("'", "''")}'`;
|
|
11
|
+
}
|
|
12
|
+
const commands = concurrent
|
|
13
|
+
.trim()
|
|
14
|
+
.split(/;\s*(?:\n|$)/u)
|
|
15
|
+
.filter((statement) => statement.trim() !== "");
|
|
16
|
+
const pending = upgrade === undefined ? "" : pendingIndexInserts(upgrade.schema, upgrade.version, commands, quote);
|
|
17
|
+
// Effect SQL uses the extended protocol, which accepts one statement per command.
|
|
18
|
+
await run(connection.executeRaw(`DO ${quote(`BEGIN EXECUTE ${quote(`${body}\n${pending}`)}; END`)}`, []));
|
|
19
|
+
if (upgrade === undefined) {
|
|
20
|
+
for (const statement of commands) {
|
|
21
|
+
await run(connection.executeRaw(statement, []));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=runSchemaPlan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runSchemaPlan.js","sourceRoot":"","sources":["../../../src/server/internal/runSchemaPlan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,UAAsB,EACtB,IAAY,EACZ,GAAsD,EACtD,OAA+D;IAE/D,qFAAqF;IACrF,MAAM,KAAK,GAAG,iEAAiE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3F,IAAI,KAAK,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;IACpD,SAAS,KAAK,CAAC,IAAY;QAC1B,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;IAC1C,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU;SACzB,IAAI,EAAE;SACN,KAAK,CAAC,eAAe,CAAC;SACtB,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IACnH,kFAAkF;IAClF,MAAM,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,iBAAiB,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1G,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,KAAK,MAAM,SAAS,IAAI,QAAQ,EAAE,CAAC;YAClC,MAAM,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QACjD,CAAC;IACF,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PgBoss } from "pg-boss";
|
|
2
|
+
export declare function pendingIndexInserts(schema: string, version: number, commands: readonly string[], quote: (text: string) => string): string;
|
|
3
|
+
export declare function resumeSchemaIndexes(client: PgBoss, schema: string): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=schemaIndexBuilds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaIndexBuilds.d.ts","sourceRoot":"","sources":["../../../src/server/internal/schemaIndexBuilds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAgBtC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAQzI;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBvF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const BUILD_PREFIX = "effect-pg-boss-schema:";
|
|
2
|
+
function indexName(command) {
|
|
3
|
+
const match = /^\s*(?:CREATE (?:UNIQUE )?INDEX CONCURRENTLY IF NOT EXISTS |DROP INDEX CONCURRENTLY IF EXISTS (?:(?:"[^"]+"|\w+)\.))(?<name>\w+)/mu.exec(command);
|
|
4
|
+
const name = match?.groups?.name;
|
|
5
|
+
if (name === undefined) {
|
|
6
|
+
throw new Error("Unrecognized pg-boss concurrent index command");
|
|
7
|
+
}
|
|
8
|
+
return name;
|
|
9
|
+
}
|
|
10
|
+
export function pendingIndexInserts(schema, version, commands, quote) {
|
|
11
|
+
return commands
|
|
12
|
+
.map((command, position) => {
|
|
13
|
+
const table = indexName(command).replace(/_i\d+$/u, "");
|
|
14
|
+
const name = `${BUILD_PREFIX}${String(position).padStart(6, "0")}`;
|
|
15
|
+
return `INSERT INTO ${schema}.bam (name, version, table_name, command) VALUES (${quote(name)}, ${version}, ${quote(table)}, ${quote(command)});`;
|
|
16
|
+
})
|
|
17
|
+
.join("\n");
|
|
18
|
+
}
|
|
19
|
+
export async function resumeSchemaIndexes(client, schema) {
|
|
20
|
+
const db = client.getDb();
|
|
21
|
+
const { rows } = await db.executeSql(`SELECT id, command FROM ${schema}.bam WHERE starts_with(name, $1) AND status <> 'completed' ORDER BY created_on, name`, [BUILD_PREFIX]);
|
|
22
|
+
for (const row of rows) {
|
|
23
|
+
const name = indexName(row.command);
|
|
24
|
+
if (/^\s*CREATE /mu.test(row.command)) {
|
|
25
|
+
const probe = await db.executeSql("SELECT NOT indisvalid AS invalid FROM pg_index WHERE indexrelid = to_regclass($1)", [`${schema}.${name}`]);
|
|
26
|
+
if (probe.rows[0]?.invalid === true) {
|
|
27
|
+
await db.executeSql(`DROP INDEX CONCURRENTLY IF EXISTS ${schema}."${name}"`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await db.executeSql(row.command);
|
|
31
|
+
await db.executeSql(`UPDATE ${schema}.bam SET status = 'completed', completed_on = clock_timestamp() WHERE id = $1`, [row.id]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=schemaIndexBuilds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaIndexBuilds.js","sourceRoot":"","sources":["../../../src/server/internal/schemaIndexBuilds.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAE9C,SAAS,SAAS,CAAC,OAAe;IACjC,MAAM,KAAK,GACV,oIAAoI,CAAC,IAAI,CACxI,OAAO,CACP,CAAC;IACH,MAAM,IAAI,GAAG,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC;IACjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,OAAe,EAAE,QAA2B,EAAE,KAA+B;IAChI,OAAO,QAAQ;SACb,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,GAAG,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACnE,OAAO,eAAe,MAAM,qDAAqD,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,KAAK,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;IAClJ,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,MAAc;IACvE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;IAC1B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,UAAU,CACnC,2BAA2B,MAAM,sFAAsF,EACvH,CAAC,YAAY,CAAC,CACd,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,UAAU,CAAC,mFAAmF,EAAE,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9I,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrC,MAAM,EAAE,CAAC,UAAU,CAAC,qCAAqC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;YAC9E,CAAC;QACF,CAAC;QACD,MAAM,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,EAAE,CAAC,UAAU,CAAC,UAAU,MAAM,+EAA+E,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAChI,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PgBoss, SendOptions } from "pg-boss";
|
|
2
|
+
import type { PgBossClient } from "#client.ts";
|
|
3
|
+
export interface TestingControls {
|
|
4
|
+
readonly changed: () => void;
|
|
5
|
+
readonly expirations: Map<string, number>;
|
|
6
|
+
readonly failures: Set<string>;
|
|
7
|
+
readonly retryPolicy: Pick<SendOptions, "retryBackoff" | "retryDelay">;
|
|
8
|
+
}
|
|
9
|
+
export declare function testingClient(client: PgBoss, schema: string, controls: TestingControls): PgBossClient;
|
|
10
|
+
//# sourceMappingURL=testingClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testingClient.d.ts","sourceRoot":"","sources":["../../../src/server/internal/testingClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAO,MAAM,EAAE,WAAW,EAAgD,MAAM,SAAS,CAAC;AACtG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,GAAG,YAAY,CAAC,CAAC;CACvE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,YAAY,CAqGrG"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { expireAttempt } from "#internal/expireAttempt.ts";
|
|
2
|
+
import { observeTestingSettlements } from "#internal/observeTestingSettlements.ts";
|
|
3
|
+
import { testingExpirations } from "#internal/testingExpirations.ts";
|
|
4
|
+
export function testingClient(client, schema, controls) {
|
|
5
|
+
const settlements = observeTestingSettlements(client, controls.changed);
|
|
6
|
+
const expirations = testingExpirations(client);
|
|
7
|
+
async function update(request, data, options) {
|
|
8
|
+
const result = typeof request === "string" ? await client.update(request, data, options) : await client.update(request);
|
|
9
|
+
controls.changed();
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
const overrides = {
|
|
13
|
+
complete: async (...args) => {
|
|
14
|
+
const result = await client.complete(...args);
|
|
15
|
+
controls.changed();
|
|
16
|
+
return result;
|
|
17
|
+
},
|
|
18
|
+
createQueue: (name, options) => {
|
|
19
|
+
const { retryDelayMax, ...rest } = options ?? {};
|
|
20
|
+
return client.createQueue(name, {
|
|
21
|
+
...rest,
|
|
22
|
+
...(controls.retryPolicy.retryBackoff === false || retryDelayMax === undefined ? {} : { retryDelayMax }),
|
|
23
|
+
...controls.retryPolicy,
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
fail: async (...args) => {
|
|
27
|
+
const result = await client.fail(...args);
|
|
28
|
+
controls.changed();
|
|
29
|
+
return result;
|
|
30
|
+
},
|
|
31
|
+
retry: async (...args) => {
|
|
32
|
+
const result = await client.retry(...args);
|
|
33
|
+
controls.changed();
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
send: async (name, data, options) => {
|
|
37
|
+
if (controls.failures.delete(name)) {
|
|
38
|
+
throw new Error("Injected pg-boss send failure");
|
|
39
|
+
}
|
|
40
|
+
const attempts = controls.expirations.get(name);
|
|
41
|
+
const { retryDelayMax, ...rest } = options ?? {};
|
|
42
|
+
const id = await expirations.send(name, data, {
|
|
43
|
+
...rest,
|
|
44
|
+
...(controls.retryPolicy.retryBackoff === false || retryDelayMax === undefined ? {} : { retryDelayMax }),
|
|
45
|
+
...controls.retryPolicy,
|
|
46
|
+
...(attempts === undefined ? {} : { retryLimit: attempts - 1 }),
|
|
47
|
+
}, attempts);
|
|
48
|
+
controls.changed();
|
|
49
|
+
return id;
|
|
50
|
+
},
|
|
51
|
+
stop: async (options) => {
|
|
52
|
+
try {
|
|
53
|
+
await client.stop(options);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
settlements.close();
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
update,
|
|
60
|
+
updateQueue: (name, options) => client.updateQueue(name, {
|
|
61
|
+
...options,
|
|
62
|
+
...controls.retryPolicy,
|
|
63
|
+
...(controls.retryPolicy.retryBackoff === false ? { retryDelayMax: null } : {}),
|
|
64
|
+
}),
|
|
65
|
+
work: (name, options, handler) => {
|
|
66
|
+
settlements.watch(name);
|
|
67
|
+
return client.work(name, options, async (jobs) => {
|
|
68
|
+
controls.changed();
|
|
69
|
+
try {
|
|
70
|
+
const ready = [];
|
|
71
|
+
for (const job of jobs) {
|
|
72
|
+
const attempts = await expirations.attemptsFor(job);
|
|
73
|
+
if (attempts !== undefined && job.retryCount + 1 >= attempts) {
|
|
74
|
+
await expireAttempt(client, schema, job);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
ready.push(job);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (ready.length > 0) {
|
|
81
|
+
await handler(ready);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
controls.changed();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
return new Proxy(client, {
|
|
91
|
+
get(target, property) {
|
|
92
|
+
if (Object.hasOwn(overrides, property)) {
|
|
93
|
+
const override = Reflect.get(overrides, property);
|
|
94
|
+
return override;
|
|
95
|
+
}
|
|
96
|
+
const value = Reflect.get(target, property);
|
|
97
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=testingClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testingClient.js","sourceRoot":"","sources":["../../../src/server/internal/testingClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Job, PgBoss, SendOptions } from "pg-boss";
|
|
2
|
+
export declare function testingExpirations(client: PgBoss): {
|
|
3
|
+
attemptsFor: (job: Job<unknown>) => Promise<number | undefined> | undefined;
|
|
4
|
+
send(name: string, data: object | null | undefined, options: SendOptions, attempts: number | undefined): Promise<string | null>;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=testingExpirations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testingExpirations.d.ts","sourceRoot":"","sources":["../../../src/server/internal/testingExpirations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAExD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAG/C,WAAW,QAAQ,GAAG,CAAC,OAAO,CAAC;IAC/B,IAAI,OAAO,MAAM,QAAQ,MAAM,GAAG,IAAI,GAAG,SAAS,WAAW,WAAW,YAAY,MAAM,GAAG,SAAS;EAwBvG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function testingExpirations(client) {
|
|
2
|
+
const snapshots = new Map();
|
|
3
|
+
return {
|
|
4
|
+
attemptsFor: (job) => snapshots.get(`${job.name}:${job.id}`),
|
|
5
|
+
send(name, data, options, attempts) {
|
|
6
|
+
if (attempts === undefined && options.id === undefined) {
|
|
7
|
+
return client.send(name, data, options);
|
|
8
|
+
}
|
|
9
|
+
const id = options.id ?? crypto.randomUUID();
|
|
10
|
+
const key = `${name}:${id}`;
|
|
11
|
+
const previous = snapshots.get(key);
|
|
12
|
+
const sending = client.send(name, data, { ...options, id });
|
|
13
|
+
// A worker may claim the row before send returns. Wait for its result before applying the snapshot.
|
|
14
|
+
const snapshot = sending
|
|
15
|
+
.then((sent) => (sent === id ? attempts : previous), () => previous)
|
|
16
|
+
.then((value) => {
|
|
17
|
+
if (value === undefined && snapshots.get(key) === snapshot) {
|
|
18
|
+
snapshots.delete(key);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
});
|
|
22
|
+
snapshots.set(key, snapshot);
|
|
23
|
+
return sending;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=testingExpirations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testingExpirations.js","sourceRoot":"","sources":["../../../src/server/internal/testingExpirations.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAChD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAuC,CAAC;IACjE,OAAO;QACN,WAAW,EAAE,CAAC,GAAiB,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;QAC1E,IAAI,CAAC,IAAY,EAAE,IAA+B,EAAE,OAAoB,EAAE,QAA4B;YACrG,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;gBACxD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YACzC,CAAC;YACD,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;YAC5D,oGAAoG;YACpG,MAAM,QAAQ,GAAG,OAAO;iBACtB,IAAI,CACJ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,EAC7C,GAAG,EAAE,CAAC,QAAQ,CACd;iBACA,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,IAAI,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;oBAC5D,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC;gBACD,OAAO,KAAK,CAAC;YACd,CAAC,CAAC,CAAC;YACJ,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC7B,OAAO,OAAO,CAAC;QAChB,CAAC;KACD,CAAC;AACH,CAAC"}
|