@nest-boot/queue-api 0.0.0-stage → 8.1.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/LICENSE +21 -0
- package/README.md +115 -2
- package/dist/connections/job.connection-definition.d.ts +9 -0
- package/dist/connections/job.connection-definition.js +50 -0
- package/dist/connections/job.connection-definition.js.map +1 -0
- package/dist/decorators/queue-visible.decorator.d.ts +14 -0
- package/dist/decorators/queue-visible.decorator.js +20 -0
- package/dist/decorators/queue-visible.decorator.js.map +1 -0
- package/dist/entities/index.d.ts +4 -0
- package/dist/entities/index.js +5 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/job.entity.d.ts +24 -0
- package/dist/entities/job.entity.js +128 -0
- package/dist/entities/job.entity.js.map +1 -0
- package/dist/enums/job-status.enum.d.ts +19 -0
- package/dist/enums/job-status.enum.js +23 -0
- package/dist/enums/job-status.enum.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/processors/job.processor.d.ts +18 -0
- package/dist/processors/job.processor.js +111 -0
- package/dist/processors/job.processor.js.map +1 -0
- package/dist/queue-api.module.d.ts +3 -0
- package/dist/queue-api.module.js +25 -0
- package/dist/queue-api.module.js.map +1 -0
- package/dist/queues/job.queue.d.ts +32 -0
- package/dist/queues/job.queue.js +65 -0
- package/dist/queues/job.queue.js.map +1 -0
- package/dist/resolvers/job.resolver.d.ts +54 -0
- package/dist/resolvers/job.resolver.js +145 -0
- package/dist/resolvers/job.resolver.js.map +1 -0
- package/dist/services/job.service.d.ts +77 -0
- package/dist/services/job.service.js +276 -0
- package/dist/services/job.service.js.map +1 -0
- package/package.json +115 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 nest-boot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,116 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nest-boot/queue-api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Optional Job history and authenticated GraphQL queries for
|
|
4
|
+
[`@nest-boot/queue`](../queue/README.md). Redis remains responsible for queue state
|
|
5
|
+
and processing; the database stores asynchronous history snapshots.
|
|
6
|
+
|
|
7
|
+
## Setup and visibility
|
|
8
|
+
|
|
9
|
+
Configure the application's global DatabaseModule, GraphQLModule and AuthModule,
|
|
10
|
+
then import QueueApiModule. It reuses those configured modules, imports QueueModule
|
|
11
|
+
and does not depend on `@nest-boot/schedule`. Register JobEntity in both runtime
|
|
12
|
+
and MikroORM CLI entity lists and create its table through application migrations.
|
|
13
|
+
The database generates UUID IDs; `(queue, jobId)` identifies the source Redis job.
|
|
14
|
+
|
|
15
|
+
Register queue providers in the API application and mark each visible queue:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { BaseQueue, Queue } from "@nest-boot/queue";
|
|
19
|
+
import { QueueApiModule, QueueVisible } from "@nest-boot/queue-api";
|
|
20
|
+
import { Module } from "@nestjs/common";
|
|
21
|
+
|
|
22
|
+
@QueueVisible()
|
|
23
|
+
@Queue()
|
|
24
|
+
export class EmailQueue extends BaseQueue {}
|
|
25
|
+
|
|
26
|
+
@Module({
|
|
27
|
+
imports: [QueueApiModule],
|
|
28
|
+
providers: [EmailQueue],
|
|
29
|
+
})
|
|
30
|
+
export class EmailModule {}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`@QueueVisible()` takes no arguments. Each concrete class needs its own marker;
|
|
34
|
+
subclasses do not inherit visibility. Unmarked or unregistered queues are excluded
|
|
35
|
+
from single-record reads, page nodes and counts, including explicit client
|
|
36
|
+
filters. An empty visible set returns no records. Visibility does not change
|
|
37
|
+
synchronization, retention or authorization.
|
|
38
|
+
|
|
39
|
+
## Queries and authorization
|
|
40
|
+
|
|
41
|
+
Every query uses AuthGuard. JobService reads trusted request identity and applies
|
|
42
|
+
the same visibility and ownership constraints to single records, pages and counts:
|
|
43
|
+
|
|
44
|
+
| Query | Access scope |
|
|
45
|
+
| -------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
46
|
+
| `userJob` / `userJobs` | `data.userId` matches the current user |
|
|
47
|
+
| `workspaceJob` / `workspaceJobs` | `data.workspaceId` matches the selected workspace after membership and read authorization |
|
|
48
|
+
| `systemJob` / `systemJobs` | Both owner fields are absent or JSON null, with administrator authorization |
|
|
49
|
+
|
|
50
|
+
System reads use AuthModule's configured `user.adminRoles`. API keys do not inherit
|
|
51
|
+
their owner's administrator access. Empty strings count as present owner values.
|
|
52
|
+
User and workspace scopes can overlap when a task carries both IDs. Missing
|
|
53
|
+
identity or unauthorized workspace access is rejected.
|
|
54
|
+
|
|
55
|
+
JobService exposes `getUserJob`, `getWorkspaceJob`, `getSystemJob` and their
|
|
56
|
+
`get*JobConnection` variants. It injects EntityManager directly and exposes no
|
|
57
|
+
generic CRUD or unscoped query. Job history has no database RLS; direct ORM access
|
|
58
|
+
is a trusted internal operation, so request handlers use the scoped methods.
|
|
59
|
+
|
|
60
|
+
GraphQL exposes `id`, `queue`, `name`, `status`, finite numeric `progress`,
|
|
61
|
+
`createdAt`, `startedAt` and `finishedAt`. Other progress values return null.
|
|
62
|
+
Payloads, results, errors, native job IDs and event versions remain private.
|
|
63
|
+
Connections support queue, name, status and creation-time filters. CREATED_AT is
|
|
64
|
+
the only selectable sort key and defaults to descending; UUID breaks timestamp
|
|
65
|
+
ties internally. User/workspace expression indexes and a partial system index
|
|
66
|
+
support these scoped cursor reads.
|
|
67
|
+
|
|
68
|
+
## Synchronization and retention
|
|
69
|
+
|
|
70
|
+
JobService subscribes to registered queues before their event readers start,
|
|
71
|
+
including producer-only instances. It forwards snapshots to the built-in `job`
|
|
72
|
+
queue, deduplicating qualified source names and Redis Stream IDs for 24 hours.
|
|
73
|
+
The Job worker compares bigint `(lastEventMs, lastEventSeq)` pairs atomically and
|
|
74
|
+
only applies newer events. Events from its own queue are excluded to prevent
|
|
75
|
+
recursion. Database write failures retry up to 20 times with exponential backoff.
|
|
76
|
+
|
|
77
|
+
Readers use BullMQ's default starting position without startup replay. Connection
|
|
78
|
+
readiness does not guarantee that the first stream read has started. Retain source
|
|
79
|
+
jobs long enough to capture full snapshots; when a source job is already removed,
|
|
80
|
+
only metadata carried by the event remains available.
|
|
81
|
+
|
|
82
|
+
Start all registered consumers with `nb queue:start`, or use
|
|
83
|
+
`nb queue:start --queue job` for synchronization and cleanup alone. When filtering,
|
|
84
|
+
select `job` explicitly in the process intended to persist history. Shutdown drains
|
|
85
|
+
source Workers, then event readers and pending submissions, then the Job Worker.
|
|
86
|
+
|
|
87
|
+
The built-in JobQueue handles `sync` and `cleanup`. One shared BullMQ scheduler on
|
|
88
|
+
the `job` queue runs cleanup daily at 00:00 UTC. Cleanup deletes records inactive
|
|
89
|
+
for more than 30 days in guarded batches of 1000, regardless of status. Records
|
|
90
|
+
without an applied event use `createdAt`. Redis jobs remain intact; later events
|
|
91
|
+
recreate deleted records with a new UUID. Cleanup requires the Job Worker.
|
|
92
|
+
|
|
93
|
+
## Tests
|
|
94
|
+
|
|
95
|
+
Unit tests sit beside their source files. End-to-end suites live in `test/`:
|
|
96
|
+
|
|
97
|
+
- `job.resolver`: visibility, ownership, authorization, filters and cursors through HTTP.
|
|
98
|
+
- `job-queries`: synchronized Redis history through the GraphQL API.
|
|
99
|
+
- `job.processor`: atomic event ordering and retention against PostgreSQL.
|
|
100
|
+
- `job.service`: producer-only event forwarding without historical replay.
|
|
101
|
+
- `job.entity`: schema, UUID defaults, bigint columns and query indexes.
|
|
102
|
+
- `queue-api`: multiple application instances, deduplication, retries and shutdown.
|
|
103
|
+
|
|
104
|
+
Build before running end-to-end tests because they import the package output:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pnpm --filter @nest-boot/queue-api build
|
|
108
|
+
pnpm --filter @nest-boot/queue-api test
|
|
109
|
+
REDIS_URL=redis://localhost:6379 \
|
|
110
|
+
QUEUE_TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres \
|
|
111
|
+
pnpm --filter @nest-boot/queue-api test:e2e
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each database suite creates and removes its own database; the PostgreSQL test user
|
|
115
|
+
needs permission to create databases and the shared `authenticated` test role.
|
|
116
|
+
Redis suites use isolated prefixes and remove their own keys.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Job as JobEntity } from "../entities/job.entity.js";
|
|
2
|
+
declare const Connection: import("@nestjs/common").Type<import("@nest-boot/graphql-connection").ConnectionInterface<JobEntity>>, ConnectionArgs: import("@nestjs/common").Type<import("@nest-boot/graphql-connection").ConnectionArgsInterface<JobEntity>>;
|
|
3
|
+
/** Cursor pagination, filters and creation-time ordering for visible jobs. */
|
|
4
|
+
export declare class JobConnectionArgs extends ConnectionArgs {
|
|
5
|
+
}
|
|
6
|
+
/** Cross-queue Job records within the current authorized scope. */
|
|
7
|
+
export declare class JobConnection extends Connection {
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { ArgsType, ObjectType } from "@nest-boot/graphql";
|
|
8
|
+
import { ConnectionBuilder } from "@nest-boot/graphql-connection";
|
|
9
|
+
import { Job as JobEntity } from "../entities/job.entity.js";
|
|
10
|
+
const { Connection, ConnectionArgs } = new ConnectionBuilder(JobEntity)
|
|
11
|
+
.addField({ field: "id", type: "string", sortable: false })
|
|
12
|
+
.addField({
|
|
13
|
+
field: "queue",
|
|
14
|
+
type: "string",
|
|
15
|
+
filterable: true,
|
|
16
|
+
})
|
|
17
|
+
.addField({
|
|
18
|
+
field: "name",
|
|
19
|
+
type: "string",
|
|
20
|
+
filterable: true,
|
|
21
|
+
searchable: true,
|
|
22
|
+
})
|
|
23
|
+
.addField({
|
|
24
|
+
field: "status",
|
|
25
|
+
type: "string",
|
|
26
|
+
filterable: true,
|
|
27
|
+
})
|
|
28
|
+
.addField({
|
|
29
|
+
field: "created_at",
|
|
30
|
+
replacement: "createdAt",
|
|
31
|
+
type: "date",
|
|
32
|
+
filterable: true,
|
|
33
|
+
sortable: true,
|
|
34
|
+
})
|
|
35
|
+
.build();
|
|
36
|
+
/** Cursor pagination, filters and creation-time ordering for visible jobs. */
|
|
37
|
+
let JobConnectionArgs = class JobConnectionArgs extends ConnectionArgs {
|
|
38
|
+
};
|
|
39
|
+
JobConnectionArgs = __decorate([
|
|
40
|
+
ArgsType()
|
|
41
|
+
], JobConnectionArgs);
|
|
42
|
+
export { JobConnectionArgs };
|
|
43
|
+
/** Cross-queue Job records within the current authorized scope. */
|
|
44
|
+
let JobConnection = class JobConnection extends Connection {
|
|
45
|
+
};
|
|
46
|
+
JobConnection = __decorate([
|
|
47
|
+
ObjectType()
|
|
48
|
+
], JobConnection);
|
|
49
|
+
export { JobConnection };
|
|
50
|
+
//# sourceMappingURL=job.connection-definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job.connection-definition.js","sourceRoot":"","sources":["../../src/connections/job.connection-definition.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,EAAE,GAAG,IAAI,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,IAAI,iBAAiB,CAAC,SAAS,CAAC;KACpE,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;KAC1D,QAAQ,CAAC;IACR,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,IAAI;CACjB,CAAC;KACD,QAAQ,CAAC;IACR,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;CACjB,CAAC;KACD,QAAQ,CAAC;IACR,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,IAAI;CACjB,CAAC;KACD,QAAQ,CAAC;IACR,KAAK,EAAE,YAAY;IACnB,WAAW,EAAE,WAAW;IACxB,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,IAAI;IAChB,QAAQ,EAAE,IAAI;CACf,CAAC;KACD,KAAK,EAAE,CAAC;AAEX,8EAA8E;AAEvE,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,cAAc;CAAG,CAAA;AAA3C,iBAAiB;IAD7B,QAAQ,EAAE;GACE,iBAAiB,CAA0B;;AAExD,mEAAmE;AAE5D,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;CAAG,CAAA;AAAnC,aAAa;IADzB,UAAU,EAAE;GACA,aAAa,CAAsB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BaseQueue } from "@nest-boot/queue";
|
|
2
|
+
import type { Type } from "@nestjs/common";
|
|
3
|
+
/**
|
|
4
|
+
* Allows a registered queue's Job records to appear in scoped API queries.
|
|
5
|
+
* @returns Queue class decorator. Authorization still applies to every query.
|
|
6
|
+
*/
|
|
7
|
+
export declare function QueueVisible(): (target: Type<BaseQueue>) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether a queue class explicitly enables API queries.
|
|
10
|
+
* @param queue - Registered queue instance.
|
|
11
|
+
* @returns Whether the concrete queue class has its own visibility marker.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function isQueueVisible(queue: BaseQueue): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const QUEUE_VISIBLE = Symbol("QUEUE_VISIBLE");
|
|
2
|
+
/**
|
|
3
|
+
* Allows a registered queue's Job records to appear in scoped API queries.
|
|
4
|
+
* @returns Queue class decorator. Authorization still applies to every query.
|
|
5
|
+
*/
|
|
6
|
+
export function QueueVisible() {
|
|
7
|
+
return (target) => {
|
|
8
|
+
Reflect.defineMetadata(QUEUE_VISIBLE, true, target);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Checks whether a queue class explicitly enables API queries.
|
|
13
|
+
* @param queue - Registered queue instance.
|
|
14
|
+
* @returns Whether the concrete queue class has its own visibility marker.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export function isQueueVisible(queue) {
|
|
18
|
+
return Reflect.getOwnMetadata(QUEUE_VISIBLE, queue.constructor) === true;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=queue-visible.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-visible.decorator.js","sourceRoot":"","sources":["../../src/decorators/queue-visible.decorator.ts"],"names":[],"mappings":"AAGA,MAAM,aAAa,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,MAAuB,EAAQ,EAAE;QACvC,OAAO,CAAC,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAgB;IAC7C,OAAO,OAAO,CAAC,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entities/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,GAAG,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEnD,oEAAoE;AACpE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JobProgress } from "bullmq";
|
|
2
|
+
import { JobStatus } from "../enums/job-status.enum.js";
|
|
3
|
+
/** Database history synchronized from Redis QueueEvents by the Job worker. */
|
|
4
|
+
export declare class Job {
|
|
5
|
+
/** Database-generated UUID, stable across synchronization events. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Queue name of the source Redis job. */
|
|
8
|
+
queue: string;
|
|
9
|
+
/** Original BullMQ identifier, unique within its queue. */
|
|
10
|
+
jobId: string;
|
|
11
|
+
name: string;
|
|
12
|
+
data: Record<string, unknown>;
|
|
13
|
+
status: JobStatus;
|
|
14
|
+
progress: JobProgress | null;
|
|
15
|
+
returnValue: unknown;
|
|
16
|
+
failedReason: string | null;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
startedAt: Date | null;
|
|
19
|
+
finishedAt: Date | null;
|
|
20
|
+
/** Millisecond component of the latest applied Redis Stream ID. */
|
|
21
|
+
lastEventMs: bigint;
|
|
22
|
+
/** Sequence component of the latest applied Redis Stream ID. */
|
|
23
|
+
lastEventSeq: bigint;
|
|
24
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
/* eslint-disable @nest-boot/graphql-field-config-from-types -- Persist arbitrary progress but expose only numeric values via the resolver. */
|
|
11
|
+
/* eslint-disable @nest-boot/entity-property-config-from-types -- History uses explicit SQL UUID, JSON and bigint types. */
|
|
12
|
+
import { BigIntType, t } from "@mikro-orm/core";
|
|
13
|
+
import { Entity, Enum, Index, PrimaryKey, Property, Unique, } from "@mikro-orm/decorators/legacy";
|
|
14
|
+
import { Field, Float, HideField, ID, ObjectType } from "@nest-boot/graphql";
|
|
15
|
+
import { JobStatus } from "../enums/job-status.enum.js";
|
|
16
|
+
/** Database history synchronized from Redis QueueEvents by the Job worker. */
|
|
17
|
+
let Job = class Job {
|
|
18
|
+
/** Database-generated UUID, stable across synchronization events. */
|
|
19
|
+
id;
|
|
20
|
+
/** Queue name of the source Redis job. */
|
|
21
|
+
queue;
|
|
22
|
+
/** Original BullMQ identifier, unique within its queue. */
|
|
23
|
+
jobId;
|
|
24
|
+
name;
|
|
25
|
+
data;
|
|
26
|
+
status;
|
|
27
|
+
progress;
|
|
28
|
+
returnValue;
|
|
29
|
+
failedReason;
|
|
30
|
+
createdAt;
|
|
31
|
+
startedAt;
|
|
32
|
+
finishedAt;
|
|
33
|
+
/** Millisecond component of the latest applied Redis Stream ID. */
|
|
34
|
+
lastEventMs;
|
|
35
|
+
/** Sequence component of the latest applied Redis Stream ID. */
|
|
36
|
+
lastEventSeq;
|
|
37
|
+
};
|
|
38
|
+
__decorate([
|
|
39
|
+
PrimaryKey({ type: t.uuid, defaultRaw: "gen_random_uuid()" }),
|
|
40
|
+
Field(() => ID),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], Job.prototype, "id", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
Property({ type: t.text }),
|
|
45
|
+
Field(() => String),
|
|
46
|
+
__metadata("design:type", String)
|
|
47
|
+
], Job.prototype, "queue", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
Property({ type: t.text }),
|
|
50
|
+
HideField(),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], Job.prototype, "jobId", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
Property({ type: t.text }),
|
|
55
|
+
Field(() => String),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], Job.prototype, "name", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
Property({ type: t.json, defaultRaw: "'{}'::jsonb" }),
|
|
60
|
+
HideField(),
|
|
61
|
+
__metadata("design:type", Object)
|
|
62
|
+
], Job.prototype, "data", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
Enum({ items: () => JobStatus }),
|
|
65
|
+
Field(() => JobStatus),
|
|
66
|
+
__metadata("design:type", String)
|
|
67
|
+
], Job.prototype, "status", void 0);
|
|
68
|
+
__decorate([
|
|
69
|
+
Property({ type: t.json, nullable: true }),
|
|
70
|
+
Field(() => Float, { nullable: true }),
|
|
71
|
+
__metadata("design:type", Object)
|
|
72
|
+
], Job.prototype, "progress", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
Property({ type: t.json, nullable: true }),
|
|
75
|
+
HideField(),
|
|
76
|
+
__metadata("design:type", Object)
|
|
77
|
+
], Job.prototype, "returnValue", void 0);
|
|
78
|
+
__decorate([
|
|
79
|
+
Property({ type: t.text, nullable: true }),
|
|
80
|
+
HideField(),
|
|
81
|
+
__metadata("design:type", Object)
|
|
82
|
+
], Job.prototype, "failedReason", void 0);
|
|
83
|
+
__decorate([
|
|
84
|
+
Property({ type: t.datetime }),
|
|
85
|
+
Field(() => Date),
|
|
86
|
+
__metadata("design:type", Date)
|
|
87
|
+
], Job.prototype, "createdAt", void 0);
|
|
88
|
+
__decorate([
|
|
89
|
+
Property({ type: t.datetime, nullable: true }),
|
|
90
|
+
Field(() => Date, { nullable: true }),
|
|
91
|
+
__metadata("design:type", Object)
|
|
92
|
+
], Job.prototype, "startedAt", void 0);
|
|
93
|
+
__decorate([
|
|
94
|
+
Property({ type: t.datetime, nullable: true }),
|
|
95
|
+
Field(() => Date, { nullable: true }),
|
|
96
|
+
__metadata("design:type", Object)
|
|
97
|
+
], Job.prototype, "finishedAt", void 0);
|
|
98
|
+
__decorate([
|
|
99
|
+
Property({ type: new BigIntType("bigint"), default: 0 }),
|
|
100
|
+
HideField(),
|
|
101
|
+
__metadata("design:type", BigInt)
|
|
102
|
+
], Job.prototype, "lastEventMs", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
Property({ type: new BigIntType("bigint"), default: 0 }),
|
|
105
|
+
HideField(),
|
|
106
|
+
__metadata("design:type", BigInt)
|
|
107
|
+
], Job.prototype, "lastEventSeq", void 0);
|
|
108
|
+
Job = __decorate([
|
|
109
|
+
ObjectType(),
|
|
110
|
+
Entity(),
|
|
111
|
+
Unique({ properties: ["queue", "jobId"] }),
|
|
112
|
+
Index({
|
|
113
|
+
columns: [{ name: "data.userId" }, { name: "createdAt" }, { name: "id" }],
|
|
114
|
+
}),
|
|
115
|
+
Index({
|
|
116
|
+
columns: [
|
|
117
|
+
{ name: "data.workspaceId" },
|
|
118
|
+
{ name: "createdAt" },
|
|
119
|
+
{ name: "id" },
|
|
120
|
+
],
|
|
121
|
+
}),
|
|
122
|
+
Index({
|
|
123
|
+
properties: ["createdAt", "id"],
|
|
124
|
+
where: `"data"->>'userId' is null and "data"->>'workspaceId' is null`,
|
|
125
|
+
})
|
|
126
|
+
], Job);
|
|
127
|
+
export { Job };
|
|
128
|
+
//# sourceMappingURL=job.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job.entity.js","sourceRoot":"","sources":["../../src/entities/job.entity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** BullMQ job state values, shared by the entity and GraphQL API. */
|
|
2
|
+
export declare enum JobStatus {
|
|
3
|
+
/** Job is waiting in the Redis prioritized set. */
|
|
4
|
+
PRIORITIZED = "prioritized",
|
|
5
|
+
/** No source job state could be recovered from Redis. */
|
|
6
|
+
UNKNOWN = "unknown",
|
|
7
|
+
/** Job is waiting to be processed, without a priority. */
|
|
8
|
+
WAITING = "waiting",
|
|
9
|
+
/** Job is actively being processed. */
|
|
10
|
+
ACTIVE = "active",
|
|
11
|
+
/** Job has completed successfully. */
|
|
12
|
+
COMPLETED = "completed",
|
|
13
|
+
/** Job has failed; it may be retried according to its retry policy. */
|
|
14
|
+
FAILED = "failed",
|
|
15
|
+
/** Job is delayed and will be processed later. */
|
|
16
|
+
DELAYED = "delayed",
|
|
17
|
+
/** Job is waiting for its child jobs to complete. */
|
|
18
|
+
WAITING_CHILDREN = "waiting-children"
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { registerEnumType } from "@nest-boot/graphql";
|
|
2
|
+
/** BullMQ job state values, shared by the entity and GraphQL API. */
|
|
3
|
+
export var JobStatus;
|
|
4
|
+
(function (JobStatus) {
|
|
5
|
+
/** Job is waiting in the Redis prioritized set. */
|
|
6
|
+
JobStatus["PRIORITIZED"] = "prioritized";
|
|
7
|
+
/** No source job state could be recovered from Redis. */
|
|
8
|
+
JobStatus["UNKNOWN"] = "unknown";
|
|
9
|
+
/** Job is waiting to be processed, without a priority. */
|
|
10
|
+
JobStatus["WAITING"] = "waiting";
|
|
11
|
+
/** Job is actively being processed. */
|
|
12
|
+
JobStatus["ACTIVE"] = "active";
|
|
13
|
+
/** Job has completed successfully. */
|
|
14
|
+
JobStatus["COMPLETED"] = "completed";
|
|
15
|
+
/** Job has failed; it may be retried according to its retry policy. */
|
|
16
|
+
JobStatus["FAILED"] = "failed";
|
|
17
|
+
/** Job is delayed and will be processed later. */
|
|
18
|
+
JobStatus["DELAYED"] = "delayed";
|
|
19
|
+
/** Job is waiting for its child jobs to complete. */
|
|
20
|
+
JobStatus["WAITING_CHILDREN"] = "waiting-children";
|
|
21
|
+
})(JobStatus || (JobStatus = {}));
|
|
22
|
+
registerEnumType(JobStatus, { name: "JobStatus" });
|
|
23
|
+
//# sourceMappingURL=job-status.enum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-status.enum.js","sourceRoot":"","sources":["../../src/enums/job-status.enum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,qEAAqE;AACrE,MAAM,CAAN,IAAY,SAiBX;AAjBD,WAAY,SAAS;IACnB,mDAAmD;IACnD,wCAA2B,CAAA;IAC3B,yDAAyD;IACzD,gCAAmB,CAAA;IACnB,0DAA0D;IAC1D,gCAAmB,CAAA;IACnB,uCAAuC;IACvC,8BAAiB,CAAA;IACjB,sCAAsC;IACtC,oCAAuB,CAAA;IACvB,uEAAuE;IACvE,8BAAiB,CAAA;IACjB,kDAAkD;IAClD,gCAAmB,CAAA;IACnB,qDAAqD;IACrD,kDAAqC,CAAA;AACvC,CAAC,EAjBW,SAAS,KAAT,SAAS,QAiBpB;AAED,gBAAgB,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./connections/job.connection-definition.js";
|
|
2
|
+
export { QueueVisible } from "./decorators/queue-visible.decorator.js";
|
|
3
|
+
export * from "./entities/index.js";
|
|
4
|
+
export { JobStatus } from "./enums/job-status.enum.js";
|
|
5
|
+
export { QueueApiModule } from "./queue-api.module.js";
|
|
6
|
+
export { JobResolver } from "./resolvers/job.resolver.js";
|
|
7
|
+
export { JobService } from "./services/job.service.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./connections/job.connection-definition.js";
|
|
2
|
+
export { QueueVisible } from "./decorators/queue-visible.decorator.js";
|
|
3
|
+
export * from "./entities/index.js";
|
|
4
|
+
export { JobStatus } from "./enums/job-status.enum.js";
|
|
5
|
+
export { QueueApiModule } from "./queue-api.module.js";
|
|
6
|
+
export { JobResolver } from "./resolvers/job.resolver.js";
|
|
7
|
+
export { JobService } from "./services/job.service.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4CAA4C,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AACvE,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EntityManager } from "@mikro-orm/core";
|
|
2
|
+
import { BaseProcessor, type Job } from "@nest-boot/queue";
|
|
3
|
+
import { JobQueue } from "../queues/job.queue.js";
|
|
4
|
+
/** Synchronizes Job records and removes expired records. */
|
|
5
|
+
export declare class JobProcessor extends BaseProcessor {
|
|
6
|
+
private readonly em;
|
|
7
|
+
constructor(em: EntityManager);
|
|
8
|
+
/**
|
|
9
|
+
* Deletes records inactive for 30 days, falling back to their creation time.
|
|
10
|
+
* @param _job - Scheduled cleanup task.
|
|
11
|
+
*/
|
|
12
|
+
processCleanup(_job: Job<JobQueue, "cleanup">): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Inserts new history and applies only strictly newer events.
|
|
15
|
+
* @param job - Deduplicated synchronization job.
|
|
16
|
+
*/
|
|
17
|
+
processSync(job: Job<JobQueue, "sync">): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { EntityManager, } from "@mikro-orm/core";
|
|
11
|
+
import { BaseProcessor, Processor } from "@nest-boot/queue";
|
|
12
|
+
import { pick } from "lodash-es";
|
|
13
|
+
import { Job as JobEntity } from "../entities/job.entity.js";
|
|
14
|
+
import { JobStatus } from "../enums/job-status.enum.js";
|
|
15
|
+
import { JobQueue } from "../queues/job.queue.js";
|
|
16
|
+
/** Synchronizes Job records and removes expired records. */
|
|
17
|
+
let JobProcessor = class JobProcessor extends BaseProcessor {
|
|
18
|
+
em;
|
|
19
|
+
constructor(em) {
|
|
20
|
+
super();
|
|
21
|
+
this.em = em;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Deletes records inactive for 30 days, falling back to their creation time.
|
|
25
|
+
* @param _job - Scheduled cleanup task.
|
|
26
|
+
*/
|
|
27
|
+
async processCleanup(_job) {
|
|
28
|
+
const em = this.em.fork({ disableContextResolution: true });
|
|
29
|
+
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
30
|
+
const where = {
|
|
31
|
+
$or: [
|
|
32
|
+
{ lastEventMs: { $gt: 0n, $lt: BigInt(cutoff) } },
|
|
33
|
+
{ lastEventMs: 0n, createdAt: { $lt: new Date(cutoff) } },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
let lastId;
|
|
37
|
+
while (true) {
|
|
38
|
+
const rows = await em.find(JobEntity, { $and: [where, ...(lastId ? [{ id: { $gt: lastId } }] : [])] }, {
|
|
39
|
+
fields: ["id"],
|
|
40
|
+
orderBy: { id: "asc" },
|
|
41
|
+
limit: 1000,
|
|
42
|
+
disableIdentityMap: true,
|
|
43
|
+
});
|
|
44
|
+
if (!rows.length) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
lastId = rows[rows.length - 1].id;
|
|
48
|
+
// Recheck eligibility so a concurrent event cannot delete a refreshed job.
|
|
49
|
+
await em.nativeDelete(JobEntity, {
|
|
50
|
+
$and: [where, { id: { $in: rows.map(({ id }) => id) } }],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Inserts new history and applies only strictly newer events.
|
|
56
|
+
* @param job - Deduplicated synchronization job.
|
|
57
|
+
*/
|
|
58
|
+
async processSync(job) {
|
|
59
|
+
const { eventId, data: snapshot } = job.data;
|
|
60
|
+
const [ms, seq] = eventId.split("-");
|
|
61
|
+
const data = {
|
|
62
|
+
...pick(snapshot, [
|
|
63
|
+
"queue",
|
|
64
|
+
"jobId",
|
|
65
|
+
"name",
|
|
66
|
+
"data",
|
|
67
|
+
"status",
|
|
68
|
+
"progress",
|
|
69
|
+
"returnValue",
|
|
70
|
+
"failedReason",
|
|
71
|
+
]),
|
|
72
|
+
lastEventMs: BigInt(ms),
|
|
73
|
+
lastEventSeq: BigInt(seq),
|
|
74
|
+
};
|
|
75
|
+
for (const field of ["createdAt", "startedAt", "finishedAt"]) {
|
|
76
|
+
const timestamp = snapshot[field];
|
|
77
|
+
if (timestamp !== undefined) {
|
|
78
|
+
data[field] = timestamp === null ? null : new Date(timestamp);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const em = this.em.fork({ disableContextResolution: true });
|
|
82
|
+
// A concurrent insert must never overwrite an already newer version.
|
|
83
|
+
await em.upsert(JobEntity, {
|
|
84
|
+
name: "",
|
|
85
|
+
data: {},
|
|
86
|
+
status: JobStatus.UNKNOWN,
|
|
87
|
+
createdAt: new Date(Number(ms)),
|
|
88
|
+
...data,
|
|
89
|
+
}, {
|
|
90
|
+
onConflictFields: ["queue", "jobId"],
|
|
91
|
+
onConflictAction: "ignore",
|
|
92
|
+
});
|
|
93
|
+
await em.nativeUpdate(JobEntity, {
|
|
94
|
+
queue: data.queue,
|
|
95
|
+
jobId: data.jobId,
|
|
96
|
+
$or: [
|
|
97
|
+
{ lastEventMs: { $lt: data.lastEventMs } },
|
|
98
|
+
{
|
|
99
|
+
lastEventMs: data.lastEventMs,
|
|
100
|
+
lastEventSeq: { $lt: data.lastEventSeq },
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}, data);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
JobProcessor = __decorate([
|
|
107
|
+
Processor(JobQueue),
|
|
108
|
+
__metadata("design:paramtypes", [EntityManager])
|
|
109
|
+
], JobProcessor);
|
|
110
|
+
export { JobProcessor };
|
|
111
|
+
//# sourceMappingURL=job.processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job.processor.js","sourceRoot":"","sources":["../../src/processors/job.processor.ts"],"names":[],"mappings":"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"}
|