@shivaedev/jobs 0.36.0 → 0.37.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 +6 -0
- package/README.md +2 -2
- package/dist/server/adapters/listJobs.d.ts.map +1 -1
- package/dist/server/adapters/listJobs.js +17 -7
- package/dist/server/adapters/listJobs.js.map +1 -1
- package/dist/server/internal/engine.d.ts +2 -1
- package/dist/server/internal/engine.d.ts.map +1 -1
- package/dist/server/internal/engine.js.map +1 -1
- package/dist/server/internal/jobListings.d.ts.map +1 -1
- package/dist/server/internal/jobListings.js +13 -7
- package/dist/server/internal/jobListings.js.map +1 -1
- package/package.json +8 -6
- package/src/server/adapters/listJobs.ts +18 -10
- package/src/server/internal/engine.ts +2 -6
- package/src/server/internal/jobListings.ts +20 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.37.0 - 2026-11-04
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Requires the catalog-compatible `@effect/sql-pg` peer to compile shared keyset fragments through the existing pg-boss connection. Consumers must install `@effect/sql-pg` alongside `@shivaedev/jobs`, `pg-boss` and `effect`.
|
|
8
|
+
|
|
3
9
|
## 0.36.0 - 2026-11-04
|
|
4
10
|
|
|
5
11
|
### Added
|
package/README.md
CHANGED
|
@@ -595,10 +595,10 @@ Worker registration always requests `includeMetadata: true`, including when `wor
|
|
|
595
595
|
### Install, setup and limits
|
|
596
596
|
|
|
597
597
|
```sh
|
|
598
|
-
pnpm add @shivaedev/jobs effect
|
|
598
|
+
pnpm add @shivaedev/jobs @effect/sql-pg effect
|
|
599
599
|
```
|
|
600
600
|
|
|
601
|
-
The package requires Node.js 24 or later and PostgreSQL. `effect
|
|
601
|
+
The package requires Node.js 24 or later and PostgreSQL. `effect`, `pg-boss` and `@effect/sql-pg` are required peers. The PostgreSQL compiler builds paged listing queries for the existing pg-boss connection; it opens no additional pool. pnpm installs `pg-boss` as the jobs package’s peer; apps do not list `pg-boss` themselves.
|
|
602
602
|
|
|
603
603
|
`connectionString` configures pg-boss's own `pg` Pool, separately from the application's Effect SQL pool, and pg-boss's driver decides how its `sslmode` behaves. Set the TLS mode in that URL; the [framework database connection security guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/quickstart.md#database-connection-security) compares the drivers and the application's connection paths.
|
|
604
604
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listJobs.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/listJobs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"listJobs.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/listJobs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,QAAQ,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAK5D,wBAAgB,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC,UAAU,CAAC,CAuClH"}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import { makeCompiler } from "@effect/sql-pg/PgClient";
|
|
1
2
|
import { Effect } from "effect";
|
|
3
|
+
import * as Statement from "effect/sql/Statement";
|
|
2
4
|
import { toPgBossError } from "#server/error.ts";
|
|
3
5
|
import { deadLetterQueueName } from "#server/health.ts";
|
|
4
6
|
import { quotedSchema } from "#server/internal/storedRows.ts";
|
|
7
|
+
const compiler = makeCompiler();
|
|
5
8
|
export function listJobs(client, schema) {
|
|
6
|
-
return (listing, queue,
|
|
9
|
+
return (listing, queue, { limit, orderBy, seek }) => Effect.tryPromise({
|
|
7
10
|
catch: (error) => toPgBossError(listing, error, queue),
|
|
8
11
|
try: async () => {
|
|
9
12
|
if (client.getDb === undefined) {
|
|
10
13
|
throw new TypeError("This pg-boss client does not support getDb");
|
|
11
14
|
}
|
|
12
15
|
const quoted = quotedSchema(schema);
|
|
13
|
-
const
|
|
16
|
+
const query = Statement.fragment([
|
|
17
|
+
Statement.literal(`SELECT
|
|
14
18
|
id,
|
|
15
19
|
name AS queue,
|
|
16
20
|
state,
|
|
@@ -25,11 +29,17 @@ export function listJobs(client, schema) {
|
|
|
25
29
|
source_output AS "sourceOutput",
|
|
26
30
|
to_json(created_on) #>> '{}' AS "cursorAt"
|
|
27
31
|
FROM ${quoted}.job
|
|
28
|
-
WHERE name =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
WHERE name = `),
|
|
33
|
+
Statement.parameter(listing === "failedJobs" ? queue : deadLetterQueueName(queue)),
|
|
34
|
+
Statement.literal(listing === "failedJobs" ? " AND state = 'failed' AND " : " AND "),
|
|
35
|
+
...seek.segments,
|
|
36
|
+
Statement.literal(" ORDER BY "),
|
|
37
|
+
...orderBy.segments,
|
|
38
|
+
Statement.literal(" LIMIT "),
|
|
39
|
+
Statement.parameter(limit),
|
|
40
|
+
]);
|
|
41
|
+
const [text, parameters] = compiler.compile(query, false);
|
|
42
|
+
const { rows: storedRows } = await client.getDb().executeSql(text, [...parameters]);
|
|
33
43
|
return storedRows;
|
|
34
44
|
},
|
|
35
45
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listJobs.js","sourceRoot":"","sources":["../../../src/server/adapters/listJobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"listJobs.js","sourceRoot":"","sources":["../../../src/server/adapters/listJobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAgB,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAE9D,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;AAEhC,MAAM,UAAU,QAAQ,CAAC,MAAoB,EAAE,MAAc;IAC5D,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CACnD,MAAM,CAAC,UAAU,CAAC;QACjB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC;QACtD,GAAG,EAAE,KAAK,IAAI,EAAE;YACf,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAChC,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;YACnE,CAAC;YACD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC;gBAChC,SAAS,CAAC,OAAO,CAAC;;;;;;;;;;;;;;WAcZ,MAAM;kBACC,CAAC;gBACd,SAAS,CAAC,SAAS,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAClF,SAAS,CAAC,OAAO,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,OAAO,CAAC;gBACpF,GAAG,IAAI,CAAC,QAAQ;gBAChB,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC;gBAC/B,GAAG,OAAO,CAAC,QAAQ;gBACnB,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;gBAC5B,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;aAC1B,CAAC,CAAC;YACH,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC1D,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;YACpF,OAAO,UAAU,CAAC;QACnB,CAAC;KACD,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Context, type Effect } from "effect";
|
|
2
|
+
import type { KeysetPageQuery } from "@shivaedev/sql/keysetPage.ts";
|
|
2
3
|
import type { PgBossClient } from "#server/client.ts";
|
|
3
4
|
import type { PgBossError } from "#server/error.ts";
|
|
4
5
|
import type { PgBossService } from "#server/service.ts";
|
|
@@ -8,7 +9,7 @@ export type Listing = "deadLetteredJobs" | "failedJobs";
|
|
|
8
9
|
declare const JobEngine_base: Context.ServiceClass<JobEngine, "@shivaedev/jobs/JobEngine", Pick<PgBossService, "deleteQueue" | "refreshQueueStats" | "unschedule"> & {
|
|
9
10
|
readonly getJob: (queue: string, id: string) => Effect.Effect<StoredJob | undefined, PgBossError>;
|
|
10
11
|
readonly retry: (queue: string, id: string) => Effect.Effect<void, PgBossError>;
|
|
11
|
-
readonly listJobs: (listing: Listing, queue: string,
|
|
12
|
+
readonly listJobs: (listing: Listing, queue: string, query: KeysetPageQuery) => Effect.Effect<readonly unknown[], PgBossError>;
|
|
12
13
|
readonly settleDeferral: (job: JobAttempt, at: string, reason: string, maxDeferrals: number) => Effect.Effect<{
|
|
13
14
|
readonly claimed: boolean;
|
|
14
15
|
readonly deferred: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/server/internal/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3F,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,CAAC,CAAC;AACvE,MAAM,MAAM,OAAO,GAAG,kBAAkB,GAAG,YAAY,CAAC;;qBAKrC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,GAAG,SAAS,EAAE,WAAW,CAAC;oBACjF,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC;uBAC5D,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/server/internal/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3F,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,CAAC,CAAC;AACvE,MAAM,MAAM,OAAO,GAAG,kBAAkB,GAAG,YAAY,CAAC;;qBAKrC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,GAAG,SAAS,EAAE,WAAW,CAAC;oBACjF,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC;uBAC5D,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,OAAO,EAAE,EAAE,WAAW,CAAC;6BACrG,CACxB,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,EAAE,WAAW,CAAC;;AAX5F,qBAAa,SAAU,SAAQ,cAaC;CAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/server/internal/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/server/internal/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,QAAQ,CAAC;AAU9C,MAAM,OAAO,SAAU,SAAQ,OAAO,CAAC,OAAO,EAa3C,CAAC,2BAA2B,CAAC;CAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jobListings.d.ts","sourceRoot":"","sources":["../../../src/server/internal/jobListings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAoB,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"jobListings.d.ts","sourceRoot":"","sources":["../../../src/server/internal/jobListings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAoB,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,KAAK,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,SAAS,EAAgB,MAAM,4BAA4B,CAAC;AAsCrE,eAAO,MAAM,WAAW;;;;;;;;;oBAWtB,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effect, Option, Redacted, Schema } from "effect";
|
|
2
2
|
import { redact } from "@shivaedev/errors/redact.ts";
|
|
3
|
+
import { keysetPage } from "@shivaedev/sql/keysetPage.ts";
|
|
3
4
|
import { PgBossPayloadError } from "#server/error.ts";
|
|
4
5
|
import { JobEngine } from "#server/internal/engine.ts";
|
|
5
6
|
import { decodeStoredData } from "#server/internal/storedRows.ts";
|
|
@@ -36,17 +37,22 @@ export const jobListings = Effect.gen(function* () {
|
|
|
36
37
|
});
|
|
37
38
|
const listJobs = Effect.fnUntraced(function* (engine, listing, queue, options) {
|
|
38
39
|
const { cursor: after, limit } = yield* Schema.decodeUnknownEffect(PageOptions)(options);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const page = yield* keysetPage({
|
|
41
|
+
after,
|
|
42
|
+
cursorFor: (row) => [row.cursorAt, row.id],
|
|
43
|
+
limit,
|
|
44
|
+
orderBy: [{ column: "created_on", direction: "desc", value: (cursor) => cursor[0] }],
|
|
45
|
+
read: (query) => engine
|
|
46
|
+
.listJobs(listing, queue.name, query)
|
|
47
|
+
.pipe(Effect.flatMap((rows) => Schema.decodeUnknownEffect(Rows)(rows).pipe(Effect.mapError((error) => new PgBossPayloadError({ direction: "decode", original: Redacted.make(error), queue: queue.name }))))),
|
|
48
|
+
tieBreaker: { column: "id", direction: "desc", value: (cursor) => cursor[1] },
|
|
49
|
+
});
|
|
50
|
+
const jobs = yield* Effect.forEach(page.rows, Effect.fnUntraced(function* ({ cursorAt: _, data, ...row }) {
|
|
45
51
|
const decoded = yield* decodeStoredData(queue, data);
|
|
46
52
|
const output = yield* readJobFailure(queue.name, row.output);
|
|
47
53
|
const sourceOutput = yield* readJobFailure(queue.name, row.sourceOutput);
|
|
48
54
|
return { ...row, attempt: Option.isSome(row.startedOn) ? row.retryCount + 1 : 0, data: redact(decoded), output, sourceOutput };
|
|
49
55
|
}));
|
|
50
|
-
return { jobs, next };
|
|
56
|
+
return { jobs, next: Option.map(page.next, Schema.encodeSync(Cursor)) };
|
|
51
57
|
});
|
|
52
58
|
//# sourceMappingURL=jobListings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jobListings.js","sourceRoot":"","sources":["../../../src/server/internal/jobListings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"jobListings.js","sourceRoot":"","sources":["../../../src/server/internal/jobListings.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/jobs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Effect-native jobs and lifecycle management for pg-boss",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -45,14 +45,15 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@shivaedev/errors": "0.9.1",
|
|
47
47
|
"@shivaedev/service": "0.7.1",
|
|
48
|
-
"@shivaedev/sql": "0.
|
|
48
|
+
"@shivaedev/sql": "0.40.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
+
"@effect/sql-pg": "4.0.7",
|
|
51
52
|
"@shivaedev/http": "0.6.1",
|
|
52
|
-
"@shivaedev/quality": "0.
|
|
53
|
-
"@shivaedev/test-kits": "0.
|
|
54
|
-
"@shivaedev/test-sql": "0.21.
|
|
55
|
-
"@shivaedev/test-story": "0.39.
|
|
53
|
+
"@shivaedev/quality": "0.62.0",
|
|
54
|
+
"@shivaedev/test-kits": "0.5.0",
|
|
55
|
+
"@shivaedev/test-sql": "0.21.2",
|
|
56
|
+
"@shivaedev/test-story": "0.39.2",
|
|
56
57
|
"@types/node": "24.19.2",
|
|
57
58
|
"effect": "4.0.7",
|
|
58
59
|
"pg-boss": "12.38.0",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"vitest": "5.0.5"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
64
|
+
"@effect/sql-pg": "4.0.7",
|
|
63
65
|
"effect": "4.0.7",
|
|
64
66
|
"pg-boss": "12.38.0"
|
|
65
67
|
},
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { makeCompiler } from "@effect/sql-pg/PgClient";
|
|
1
2
|
import { type Context, Effect } from "effect";
|
|
3
|
+
import * as Statement from "effect/sql/Statement";
|
|
2
4
|
import type { PgBossClient } from "#server/client.ts";
|
|
3
5
|
import { toPgBossError } from "#server/error.ts";
|
|
4
6
|
import { deadLetterQueueName } from "#server/health.ts";
|
|
5
7
|
import type { JobEngine } from "#server/internal/engine.ts";
|
|
6
8
|
import { quotedSchema } from "#server/internal/storedRows.ts";
|
|
7
9
|
|
|
10
|
+
const compiler = makeCompiler();
|
|
11
|
+
|
|
8
12
|
export function listJobs(client: PgBossClient, schema: string): Context.Service.Shape<typeof JobEngine>["listJobs"] {
|
|
9
|
-
return (listing, queue,
|
|
13
|
+
return (listing, queue, { limit, orderBy, seek }) =>
|
|
10
14
|
Effect.tryPromise({
|
|
11
15
|
catch: (error) => toPgBossError(listing, error, queue),
|
|
12
16
|
try: async () => {
|
|
@@ -14,8 +18,8 @@ export function listJobs(client: PgBossClient, schema: string): Context.Service.
|
|
|
14
18
|
throw new TypeError("This pg-boss client does not support getDb");
|
|
15
19
|
}
|
|
16
20
|
const quoted = quotedSchema(schema);
|
|
17
|
-
const
|
|
18
|
-
`SELECT
|
|
21
|
+
const query = Statement.fragment([
|
|
22
|
+
Statement.literal(`SELECT
|
|
19
23
|
id,
|
|
20
24
|
name AS queue,
|
|
21
25
|
state,
|
|
@@ -30,13 +34,17 @@ export function listJobs(client: PgBossClient, schema: string): Context.Service.
|
|
|
30
34
|
source_output AS "sourceOutput",
|
|
31
35
|
to_json(created_on) #>> '{}' AS "cursorAt"
|
|
32
36
|
FROM ${quoted}.job
|
|
33
|
-
WHERE name =
|
|
34
|
-
|
|
35
|
-
AND
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
WHERE name = `),
|
|
38
|
+
Statement.parameter(listing === "failedJobs" ? queue : deadLetterQueueName(queue)),
|
|
39
|
+
Statement.literal(listing === "failedJobs" ? " AND state = 'failed' AND " : " AND "),
|
|
40
|
+
...seek.segments,
|
|
41
|
+
Statement.literal(" ORDER BY "),
|
|
42
|
+
...orderBy.segments,
|
|
43
|
+
Statement.literal(" LIMIT "),
|
|
44
|
+
Statement.parameter(limit),
|
|
45
|
+
]);
|
|
46
|
+
const [text, parameters] = compiler.compile(query, false);
|
|
47
|
+
const { rows: storedRows } = await client.getDb().executeSql(text, [...parameters]);
|
|
40
48
|
return storedRows;
|
|
41
49
|
},
|
|
42
50
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Context, type Effect } from "effect";
|
|
2
|
+
import type { KeysetPageQuery } from "@shivaedev/sql/keysetPage.ts";
|
|
2
3
|
import type { PgBossClient } from "#server/client.ts";
|
|
3
4
|
import type { PgBossError } from "#server/error.ts";
|
|
4
5
|
import type { PgBossService } from "#server/service.ts";
|
|
@@ -12,12 +13,7 @@ export class JobEngine extends Context.Service<
|
|
|
12
13
|
Pick<PgBossService, "deleteQueue" | "refreshQueueStats" | "unschedule"> & {
|
|
13
14
|
readonly getJob: (queue: string, id: string) => Effect.Effect<StoredJob | undefined, PgBossError>;
|
|
14
15
|
readonly retry: (queue: string, id: string) => Effect.Effect<void, PgBossError>;
|
|
15
|
-
readonly listJobs: (
|
|
16
|
-
listing: Listing,
|
|
17
|
-
queue: string,
|
|
18
|
-
after: readonly [string, string] | undefined,
|
|
19
|
-
limit: number,
|
|
20
|
-
) => Effect.Effect<readonly unknown[], PgBossError>;
|
|
16
|
+
readonly listJobs: (listing: Listing, queue: string, query: KeysetPageQuery) => Effect.Effect<readonly unknown[], PgBossError>;
|
|
21
17
|
readonly settleDeferral: (
|
|
22
18
|
job: JobAttempt,
|
|
23
19
|
at: string,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Context, Effect, Option, Redacted, Schema } from "effect";
|
|
2
2
|
import { redact } from "@shivaedev/errors/redact.ts";
|
|
3
|
+
import { keysetPage } from "@shivaedev/sql/keysetPage.ts";
|
|
3
4
|
import type { JobPayloadSchema } from "#server/definition.ts";
|
|
4
5
|
import { type PgBossError, PgBossPayloadError } from "#server/error.ts";
|
|
5
6
|
import type { DeadLetteredJob, JobPage, JobPageOptions } from "#server/health.ts";
|
|
@@ -65,15 +66,25 @@ const listJobs = Effect.fnUntraced(function* <const TName extends string, const
|
|
|
65
66
|
TPayload["DecodingServices"]
|
|
66
67
|
> {
|
|
67
68
|
const { cursor: after, limit } = yield* Schema.decodeUnknownEffect(PageOptions)(options);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
const page = yield* keysetPage({
|
|
70
|
+
after,
|
|
71
|
+
cursorFor: (row: typeof Row.Type) => [row.cursorAt, row.id] as const,
|
|
72
|
+
limit,
|
|
73
|
+
orderBy: [{ column: "created_on", direction: "desc", value: (cursor) => cursor[0] }],
|
|
74
|
+
read: (query) =>
|
|
75
|
+
engine
|
|
76
|
+
.listJobs(listing, queue.name, query)
|
|
77
|
+
.pipe(
|
|
78
|
+
Effect.flatMap((rows) =>
|
|
79
|
+
Schema.decodeUnknownEffect(Rows)(rows).pipe(
|
|
80
|
+
Effect.mapError((error) => new PgBossPayloadError({ direction: "decode", original: Redacted.make(error), queue: queue.name })),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
tieBreaker: { column: "id", direction: "desc", value: (cursor) => cursor[1] },
|
|
85
|
+
});
|
|
75
86
|
const jobs = yield* Effect.forEach(
|
|
76
|
-
page,
|
|
87
|
+
page.rows,
|
|
77
88
|
Effect.fnUntraced(function* ({ cursorAt: _, data, ...row }) {
|
|
78
89
|
const decoded = yield* decodeStoredData(queue, data);
|
|
79
90
|
const output = yield* readJobFailure(queue.name, row.output);
|
|
@@ -81,5 +92,5 @@ const listJobs = Effect.fnUntraced(function* <const TName extends string, const
|
|
|
81
92
|
return { ...row, attempt: Option.isSome(row.startedOn) ? row.retryCount + 1 : 0, data: redact(decoded), output, sourceOutput };
|
|
82
93
|
}),
|
|
83
94
|
);
|
|
84
|
-
return { jobs, next };
|
|
95
|
+
return { jobs, next: Option.map(page.next, Schema.encodeSync(Cursor)) };
|
|
85
96
|
});
|