@nest-native/jobs 0.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/README.md +115 -0
- package/dist/dialects/mysql/index.d.ts +2 -0
- package/dist/dialects/mysql/index.js +21 -0
- package/dist/dialects/mysql/index.js.map +1 -0
- package/dist/dialects/mysql/job-store.d.ts +35 -0
- package/dist/dialects/mysql/job-store.js +150 -0
- package/dist/dialects/mysql/job-store.js.map +1 -0
- package/dist/dialects/mysql/schema.d.ts +255 -0
- package/dist/dialects/mysql/schema.js +47 -0
- package/dist/dialects/mysql/schema.js.map +1 -0
- package/dist/dialects/postgres/index.d.ts +2 -0
- package/dist/dialects/postgres/index.js +21 -0
- package/dist/dialects/postgres/index.js.map +1 -0
- package/dist/dialects/postgres/job-store.d.ts +32 -0
- package/dist/dialects/postgres/job-store.js +145 -0
- package/dist/dialects/postgres/job-store.js.map +1 -0
- package/dist/dialects/postgres/schema.d.ts +262 -0
- package/dist/dialects/postgres/schema.js +37 -0
- package/dist/dialects/postgres/schema.js.map +1 -0
- package/dist/dialects/sqlite/index.d.ts +2 -0
- package/dist/dialects/sqlite/index.js +21 -0
- package/dist/dialects/sqlite/index.js.map +1 -0
- package/dist/dialects/sqlite/job-store.d.ts +32 -0
- package/dist/dialects/sqlite/job-store.js +155 -0
- package/dist/dialects/sqlite/job-store.js.map +1 -0
- package/dist/dialects/sqlite/schema.d.ts +279 -0
- package/dist/dialects/sqlite/schema.js +37 -0
- package/dist/dialects/sqlite/schema.js.map +1 -0
- package/dist/enqueue-input.d.ts +12 -0
- package/dist/enqueue-input.js +26 -0
- package/dist/enqueue-input.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +125 -0
- package/dist/interfaces.js +10 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/job-handler.decorator.d.ts +31 -0
- package/dist/job-handler.decorator.js +18 -0
- package/dist/job-handler.decorator.js.map +1 -0
- package/dist/jobs-claimer.service.d.ts +37 -0
- package/dist/jobs-claimer.service.js +121 -0
- package/dist/jobs-claimer.service.js.map +1 -0
- package/dist/jobs-handler.explorer.d.ts +24 -0
- package/dist/jobs-handler.explorer.js +67 -0
- package/dist/jobs-handler.explorer.js.map +1 -0
- package/dist/jobs-worker.d.ts +21 -0
- package/dist/jobs-worker.js +40 -0
- package/dist/jobs-worker.js.map +1 -0
- package/dist/jobs.module.d.ts +19 -0
- package/dist/jobs.module.js +49 -0
- package/dist/jobs.module.js.map +1 -0
- package/dist/jobs.service.d.ts +25 -0
- package/dist/jobs.service.js +55 -0
- package/dist/jobs.service.js.map +1 -0
- package/dist/testing/harness.d.ts +61 -0
- package/dist/testing/harness.js +86 -0
- package/dist/testing/harness.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +19 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tokens.d.ts +10 -0
- package/dist/tokens.js +15 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +107 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @nest-native/jobs
|
|
2
|
+
|
|
3
|
+
<p align="center">Background jobs for NestJS without Redis — a Drizzle-backed job queue (SQLite, Postgres & MySQL) with transactional enqueue, retries, delayed and unique jobs.</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@nest-native/jobs"><img src="https://img.shields.io/npm/v/@nest-native/jobs.svg" alt="NPM Version" /></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/@nest-native/jobs"><img src="https://img.shields.io/npm/dm/@nest-native/jobs.svg" alt="NPM Downloads" /></a>
|
|
8
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="Package License" /></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg" alt="Test Coverage" />
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> **v0.x — early but stable.** The producer, claimer, handler discovery, and the three Drizzle stores are implemented and tested at 100% coverage. SQLite, Postgres, and MySQL are supported.
|
|
14
|
+
|
|
15
|
+
## The problem it solves
|
|
16
|
+
|
|
17
|
+
Most NestJS apps grow a first background job long before they need a queueing *system*: send a welcome email after signup, generate a report, retry a flaky webhook. The official NestJS answer is `@nestjs/bullmq` — which means **operating Redis** for what is often a handful of jobs a minute. And because Redis is a second system, the classic dual-write bug appears on day one: the signup commits but the process crashes before `queue.add()` — the email is never sent. Or `queue.add()` succeeds and the transaction rolls back — a welcome email for a user that does not exist.
|
|
18
|
+
|
|
19
|
+
`@nest-native/jobs` stores jobs in the **same Drizzle database your app already has**:
|
|
20
|
+
|
|
21
|
+
- **Transactional enqueue** — `enqueue()` inserts the job row *inside your business transaction* (via [`@nestjs-cls/transactional`](https://www.npmjs.com/package/@nestjs-cls/transactional)). The job exists if and only if your writes committed.
|
|
22
|
+
- **Nest-native execution** — declare a class with `@JobHandler('email.welcome')`, register it as a provider, and the claimer dispatches to it with full DI. Handlers are discovered at bootstrap; duplicate names throw at startup.
|
|
23
|
+
- **Retries, delays, priorities, unique jobs** — jittered exponential backoff (or `RetryableError`'s explicit `delayMs`), `PermanentError` to fail fast, `runAt`/`delayMs` scheduling, `priority` ordering, and `uniqueKey` dedup among active jobs.
|
|
24
|
+
- **Zero runtime dependencies** — everything (Nest, Drizzle, your driver) is a peer you already installed.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @nest-native/jobs
|
|
30
|
+
# plus your driver (peer dependencies):
|
|
31
|
+
npm install drizzle-orm @nestjs-cls/transactional better-sqlite3 # or pg / mysql2
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Entry points
|
|
35
|
+
|
|
36
|
+
| Import | Contents |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `@nest-native/jobs` | core engine — `JobsService` (enqueue), `JobsClaimer` + `runWorkerLoop`, `@JobHandler` + `JobsHandlerExplorer`, `RetryableError`/`PermanentError`, the `JobStore` seam, `JobsModule` |
|
|
39
|
+
| `@nest-native/jobs/sqlite` | better-sqlite3 (synchronous) store + the `jobs` table definition |
|
|
40
|
+
| `@nest-native/jobs/postgres` | node-postgres (async) store + table definition |
|
|
41
|
+
| `@nest-native/jobs/mysql` | mysql2 (async) store + table definition |
|
|
42
|
+
| `@nest-native/jobs/testing` | `drainJobs` + `RecordingJobHandler` for hermetic tests |
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// app.module.ts — wire CLS + the dialect store once
|
|
48
|
+
JobsModule.forRoot({
|
|
49
|
+
drizzleInstanceToken: DRIZZLE,
|
|
50
|
+
store: new SqliteJobStore(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// user.service.ts — enqueue inside the business transaction
|
|
54
|
+
@Injectable()
|
|
55
|
+
export class UserService {
|
|
56
|
+
constructor(
|
|
57
|
+
@InjectTransaction() private readonly db: AppDatabase,
|
|
58
|
+
private readonly jobs: JobsService<SqliteJobStore>,
|
|
59
|
+
) {}
|
|
60
|
+
|
|
61
|
+
@Transactional()
|
|
62
|
+
register(email: string) {
|
|
63
|
+
this.db.insert(users).values({ email }).run();
|
|
64
|
+
this.jobs.enqueue({
|
|
65
|
+
name: 'email.welcome',
|
|
66
|
+
payload: { email },
|
|
67
|
+
uniqueKey: `welcome:${email}`, // dedup among active jobs
|
|
68
|
+
});
|
|
69
|
+
// both rows commit atomically; a throw rolls both back
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// welcome-email.handler.ts — executed by the claimer, with full DI
|
|
74
|
+
@JobHandler('email.welcome')
|
|
75
|
+
@Injectable()
|
|
76
|
+
export class WelcomeEmailHandler implements JobHandler {
|
|
77
|
+
async handle(payload: Record<string, unknown>, ctx: JobContext) {
|
|
78
|
+
await this.mailer.send(String(payload.email)); // throw RetryableError / PermanentError to steer retries
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// worker (same process or a dedicated one)
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
void runWorkerLoop(app.get(JobsClaimer), { signal: controller.signal });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Delivery is **at-least-once**: a worker crash mid-job means the row is reclaimed after `stuckTimeoutMs` and run again. Make handlers idempotent or key their side effects on `ctx.jobId`.
|
|
88
|
+
|
|
89
|
+
### The uniqueKey contract
|
|
90
|
+
|
|
91
|
+
`uniqueKey` means "unique among **active** jobs", identically on all three dialects: a full unique index on `(name, unique_key)`, and terminal transitions (`completed`, `failed`) clear the key. Enqueueing a duplicate `(name, uniqueKey)` while one is pending/processing is a **no-op that returns the existing row**; once that job finishes, the key is free again. Jobs without a `uniqueKey` never collide.
|
|
92
|
+
|
|
93
|
+
## Honest comparison
|
|
94
|
+
|
|
95
|
+
| | BullMQ (`@nestjs/bullmq`) | pg-boss | `@nest-native/jobs` |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| Backing store | Redis (required) | Postgres only | the Drizzle DB you already run — SQLite, Postgres, or MySQL |
|
|
98
|
+
| NestJS integration | official wrapper module | none (framework-agnostic) | native — module, DI, `@JobHandler` decorators |
|
|
99
|
+
| Enqueue in your DB transaction | no (Redis is a second system) | yes (raw SQL in your tx) | yes — first-class, via `@nestjs-cls/transactional` |
|
|
100
|
+
| Delivery | Redis push (blocking ops) | polling + LISTEN/NOTIFY | polling claimer |
|
|
101
|
+
| Throughput | very high | high | right-sized — polling batches, fine for most apps' background work |
|
|
102
|
+
| Repeatable / cron jobs | yes | yes | no (non-goal — use `@nestjs/schedule`) |
|
|
103
|
+
| Dashboards, rate limiting | yes | partial | no |
|
|
104
|
+
| Runtime dependencies | Redis server + client | `pg` | zero (peers you already have) |
|
|
105
|
+
|
|
106
|
+
If you need tens of thousands of jobs per second, sandboxed processors, or a dashboard, use BullMQ — it is excellent at that. If you run Postgres without Nest, pg-boss is battle-tested. This library is for the large middle: NestJS + Drizzle apps that want reliable background jobs **without operating another system**.
|
|
107
|
+
|
|
108
|
+
## Non-goals (v0.1)
|
|
109
|
+
|
|
110
|
+
- **Cron / repeatable jobs** — `@nestjs/schedule` already does this well; combine it with `enqueue()` if you want scheduled work to flow through the queue.
|
|
111
|
+
- **Dashboards / UI**, **rate limiting**, **concurrency groups**.
|
|
112
|
+
- **LISTEN/NOTIFY push** — the claimer polls; `pollIntervalMs` is your latency knob.
|
|
113
|
+
- **Redis-class throughput** — this is a polling claimer over your relational DB, by design.
|
|
114
|
+
|
|
115
|
+
Part of the [nest-native](https://github.com/nest-native) family. Not affiliated with the NestJS core team. MIT licensed.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// @nest-native/jobs/mysql — the mysql2 (async) dialect:
|
|
18
|
+
// the `jobs` table definition + the MySQL store.
|
|
19
|
+
__exportStar(require("./schema"), exports);
|
|
20
|
+
__exportStar(require("./job-store"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../dialects/mysql/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAwD;AACxD,iDAAiD;AACjD,2CAAyB;AACzB,8CAA4B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EnqueueJobInput, JobRow, JobStore, ResolvedRunnerConfig } from '../../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* MySQL surfaces a unique-constraint violation as error code `ER_DUP_ENTRY`
|
|
4
|
+
* (errno `1062`). mysql2 sets both `error.code === 'ER_DUP_ENTRY'` and
|
|
5
|
+
* `error.errno === 1062`; Drizzle wraps driver errors in a `DrizzleQueryError`,
|
|
6
|
+
* so the code/errno may instead sit on `error.cause`. Check the code **and**
|
|
7
|
+
* the errno, on the error and on its `cause`, so the predicate is robust to
|
|
8
|
+
* both the driver's shape and Drizzle's wrapping.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isMysqlUniqueViolation(error: unknown): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* MySQL (mysql2) job store. Every method is **asynchronous** — `enqueue`
|
|
13
|
+
* awaits the insert (call it with `await` inside an async `@Transactional`
|
|
14
|
+
* body), and the claimer's batch claim runs in an async transaction.
|
|
15
|
+
*
|
|
16
|
+
* Unlike Postgres, MySQL's `INSERT` has no `RETURNING`, so `enqueue` inserts
|
|
17
|
+
* the row (client-generated UUID id) and reads it back by id to return the
|
|
18
|
+
* canonical {@link JobRow}. A `(name, unique_key)` unique violation is the
|
|
19
|
+
* active-dedup no-op: the store returns the EXISTING active row instead.
|
|
20
|
+
*/
|
|
21
|
+
export declare class MysqlJobStore implements JobStore {
|
|
22
|
+
enqueue(db: unknown, input: EnqueueJobInput<object>): Promise<JobRow>;
|
|
23
|
+
/**
|
|
24
|
+
* On a unique violation with a `uniqueKey`, the ACTIVE row that owns the
|
|
25
|
+
* `(name, uniqueKey)` pair — terminal rows cannot own it (their key is
|
|
26
|
+
* cleared to NULL). Any other error, or a vanished row (the owner completed
|
|
27
|
+
* between the insert and this read), resolves undefined so the caller
|
|
28
|
+
* rethrows the original error.
|
|
29
|
+
*/
|
|
30
|
+
private findActiveDuplicate;
|
|
31
|
+
claimBatch(db: unknown, cfg: ResolvedRunnerConfig): Promise<JobRow[]>;
|
|
32
|
+
markCompleted(db: unknown, id: string): Promise<void>;
|
|
33
|
+
retry(db: unknown, id: string, delayMs: number, lastError?: string): Promise<void>;
|
|
34
|
+
markFailed(db: unknown, id: string, reason: string): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MysqlJobStore = void 0;
|
|
4
|
+
exports.isMysqlUniqueViolation = isMysqlUniqueViolation;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
7
|
+
const enqueue_input_1 = require("../../enqueue-input");
|
|
8
|
+
const schema_1 = require("./schema");
|
|
9
|
+
/**
|
|
10
|
+
* MySQL surfaces a unique-constraint violation as error code `ER_DUP_ENTRY`
|
|
11
|
+
* (errno `1062`). mysql2 sets both `error.code === 'ER_DUP_ENTRY'` and
|
|
12
|
+
* `error.errno === 1062`; Drizzle wraps driver errors in a `DrizzleQueryError`,
|
|
13
|
+
* so the code/errno may instead sit on `error.cause`. Check the code **and**
|
|
14
|
+
* the errno, on the error and on its `cause`, so the predicate is robust to
|
|
15
|
+
* both the driver's shape and Drizzle's wrapping.
|
|
16
|
+
*/
|
|
17
|
+
function isMysqlUniqueViolation(error) {
|
|
18
|
+
return isDuplicate(error) || isDuplicate(error?.cause);
|
|
19
|
+
}
|
|
20
|
+
function isDuplicate(error) {
|
|
21
|
+
return hasProp(error, 'code', 'ER_DUP_ENTRY') || hasProp(error, 'errno', 1062);
|
|
22
|
+
}
|
|
23
|
+
function hasProp(error, key, value) {
|
|
24
|
+
return (typeof error === 'object' &&
|
|
25
|
+
error !== null &&
|
|
26
|
+
key in error &&
|
|
27
|
+
error[key] === value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* MySQL (mysql2) job store. Every method is **asynchronous** — `enqueue`
|
|
31
|
+
* awaits the insert (call it with `await` inside an async `@Transactional`
|
|
32
|
+
* body), and the claimer's batch claim runs in an async transaction.
|
|
33
|
+
*
|
|
34
|
+
* Unlike Postgres, MySQL's `INSERT` has no `RETURNING`, so `enqueue` inserts
|
|
35
|
+
* the row (client-generated UUID id) and reads it back by id to return the
|
|
36
|
+
* canonical {@link JobRow}. A `(name, unique_key)` unique violation is the
|
|
37
|
+
* active-dedup no-op: the store returns the EXISTING active row instead.
|
|
38
|
+
*/
|
|
39
|
+
class MysqlJobStore {
|
|
40
|
+
async enqueue(db, input) {
|
|
41
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
42
|
+
const availableAt = (0, enqueue_input_1.resolveAvailableAt)(input).toISOString();
|
|
43
|
+
try {
|
|
44
|
+
await db.insert(schema_1.jobs).values({
|
|
45
|
+
id,
|
|
46
|
+
name: input.name,
|
|
47
|
+
// The one place the structural input payload widens to the stored shape.
|
|
48
|
+
payload: input.payload,
|
|
49
|
+
status: 'pending',
|
|
50
|
+
maxAttempts: input.maxAttempts ?? 10,
|
|
51
|
+
uniqueKey: input.uniqueKey ?? null,
|
|
52
|
+
priority: input.priority ?? 0,
|
|
53
|
+
availableAt,
|
|
54
|
+
createdAt: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const existing = await this.findActiveDuplicate(db, input, error);
|
|
59
|
+
if (!existing)
|
|
60
|
+
throw error;
|
|
61
|
+
return existing;
|
|
62
|
+
}
|
|
63
|
+
const [row] = await db.select().from(schema_1.jobs).where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id));
|
|
64
|
+
return row;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* On a unique violation with a `uniqueKey`, the ACTIVE row that owns the
|
|
68
|
+
* `(name, uniqueKey)` pair — terminal rows cannot own it (their key is
|
|
69
|
+
* cleared to NULL). Any other error, or a vanished row (the owner completed
|
|
70
|
+
* between the insert and this read), resolves undefined so the caller
|
|
71
|
+
* rethrows the original error.
|
|
72
|
+
*/
|
|
73
|
+
async findActiveDuplicate(db, input, error) {
|
|
74
|
+
if (!isMysqlUniqueViolation(error) || input.uniqueKey == null) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const [existing] = await db
|
|
78
|
+
.select()
|
|
79
|
+
.from(schema_1.jobs)
|
|
80
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.jobs.name, input.name), (0, drizzle_orm_1.eq)(schema_1.jobs.uniqueKey, input.uniqueKey)));
|
|
81
|
+
return existing;
|
|
82
|
+
}
|
|
83
|
+
async claimBatch(db, cfg) {
|
|
84
|
+
const now = new Date();
|
|
85
|
+
const nowIso = now.toISOString();
|
|
86
|
+
const stuckCutoff = new Date(now.getTime() - cfg.stuckTimeoutMs).toISOString();
|
|
87
|
+
return db.transaction(async (tx) => {
|
|
88
|
+
const candidates = await tx
|
|
89
|
+
.select({ id: schema_1.jobs.id })
|
|
90
|
+
.from(schema_1.jobs)
|
|
91
|
+
.where((0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.jobs.status, 'pending'), (0, drizzle_orm_1.lte)(schema_1.jobs.availableAt, nowIso)), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.jobs.status, 'processing'), (0, drizzle_orm_1.lte)(schema_1.jobs.claimedAt, stuckCutoff))))
|
|
92
|
+
.orderBy((0, drizzle_orm_1.desc)(schema_1.jobs.priority), (0, drizzle_orm_1.asc)(schema_1.jobs.availableAt))
|
|
93
|
+
.limit(cfg.batchSize);
|
|
94
|
+
if (candidates.length === 0)
|
|
95
|
+
return [];
|
|
96
|
+
const ids = candidates.map((c) => c.id);
|
|
97
|
+
await tx
|
|
98
|
+
.update(schema_1.jobs)
|
|
99
|
+
.set({ status: 'processing', claimedAt: nowIso, claimedBy: cfg.workerInstanceId })
|
|
100
|
+
.where((0, drizzle_orm_1.inArray)(schema_1.jobs.id, ids));
|
|
101
|
+
return tx
|
|
102
|
+
.select()
|
|
103
|
+
.from(schema_1.jobs)
|
|
104
|
+
.where((0, drizzle_orm_1.inArray)(schema_1.jobs.id, ids))
|
|
105
|
+
.orderBy((0, drizzle_orm_1.desc)(schema_1.jobs.priority), (0, drizzle_orm_1.asc)(schema_1.jobs.availableAt));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async markCompleted(db, id) {
|
|
109
|
+
await db
|
|
110
|
+
.update(schema_1.jobs)
|
|
111
|
+
.set({
|
|
112
|
+
status: 'completed',
|
|
113
|
+
processedAt: new Date().toISOString(),
|
|
114
|
+
lastError: null,
|
|
115
|
+
// Terminal → release the active-dedup key.
|
|
116
|
+
uniqueKey: null,
|
|
117
|
+
})
|
|
118
|
+
.where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id));
|
|
119
|
+
}
|
|
120
|
+
async retry(db, id, delayMs, lastError) {
|
|
121
|
+
const nextAvailable = new Date(Date.now() + delayMs).toISOString();
|
|
122
|
+
await db
|
|
123
|
+
.update(schema_1.jobs)
|
|
124
|
+
.set({
|
|
125
|
+
status: 'pending',
|
|
126
|
+
attempts: (0, drizzle_orm_1.sql) `${schema_1.jobs.attempts} + 1`,
|
|
127
|
+
availableAt: nextAvailable,
|
|
128
|
+
claimedAt: null,
|
|
129
|
+
claimedBy: null,
|
|
130
|
+
lastError: lastError ?? null,
|
|
131
|
+
// Still active → the uniqueKey stays claimed.
|
|
132
|
+
})
|
|
133
|
+
.where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id));
|
|
134
|
+
}
|
|
135
|
+
async markFailed(db, id, reason) {
|
|
136
|
+
await db
|
|
137
|
+
.update(schema_1.jobs)
|
|
138
|
+
.set({
|
|
139
|
+
status: 'failed',
|
|
140
|
+
attempts: (0, drizzle_orm_1.sql) `${schema_1.jobs.attempts} + 1`,
|
|
141
|
+
lastError: reason,
|
|
142
|
+
processedAt: new Date().toISOString(),
|
|
143
|
+
// Terminal → release the active-dedup key.
|
|
144
|
+
uniqueKey: null,
|
|
145
|
+
})
|
|
146
|
+
.where((0, drizzle_orm_1.eq)(schema_1.jobs.id, id));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.MysqlJobStore = MysqlJobStore;
|
|
150
|
+
//# sourceMappingURL=job-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store.js","sourceRoot":"","sources":["../../../dialects/mysql/job-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MySQL `jobs` table. Add it to your Drizzle schema and generate a migration
|
|
3
|
+
* with drizzle-kit. Timestamps are stored as ISO-8601 `varchar` so the row
|
|
4
|
+
* shape is identical across dialects (ISO-8601 compares lexicographically,
|
|
5
|
+
* which the claimer's `available_at` range query relies on).
|
|
6
|
+
*/
|
|
7
|
+
export declare const jobs: import("drizzle-orm/mysql-core").MySqlTableWithColumns<{
|
|
8
|
+
name: "jobs";
|
|
9
|
+
schema: undefined;
|
|
10
|
+
columns: {
|
|
11
|
+
id: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
12
|
+
name: "id";
|
|
13
|
+
tableName: "jobs";
|
|
14
|
+
dataType: "string";
|
|
15
|
+
columnType: "MySqlVarChar";
|
|
16
|
+
data: string;
|
|
17
|
+
driverParam: string | number;
|
|
18
|
+
notNull: true;
|
|
19
|
+
hasDefault: false;
|
|
20
|
+
isPrimaryKey: true;
|
|
21
|
+
isAutoincrement: false;
|
|
22
|
+
hasRuntimeDefault: false;
|
|
23
|
+
enumValues: [string, ...string[]];
|
|
24
|
+
baseColumn: never;
|
|
25
|
+
identity: undefined;
|
|
26
|
+
generated: undefined;
|
|
27
|
+
}, {}, {}>;
|
|
28
|
+
name: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
29
|
+
name: "name";
|
|
30
|
+
tableName: "jobs";
|
|
31
|
+
dataType: "string";
|
|
32
|
+
columnType: "MySqlVarChar";
|
|
33
|
+
data: string;
|
|
34
|
+
driverParam: string | number;
|
|
35
|
+
notNull: true;
|
|
36
|
+
hasDefault: false;
|
|
37
|
+
isPrimaryKey: false;
|
|
38
|
+
isAutoincrement: false;
|
|
39
|
+
hasRuntimeDefault: false;
|
|
40
|
+
enumValues: [string, ...string[]];
|
|
41
|
+
baseColumn: never;
|
|
42
|
+
identity: undefined;
|
|
43
|
+
generated: undefined;
|
|
44
|
+
}, {}, {}>;
|
|
45
|
+
payload: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
46
|
+
name: "payload";
|
|
47
|
+
tableName: "jobs";
|
|
48
|
+
dataType: "json";
|
|
49
|
+
columnType: "MySqlJson";
|
|
50
|
+
data: Record<string, unknown>;
|
|
51
|
+
driverParam: string;
|
|
52
|
+
notNull: true;
|
|
53
|
+
hasDefault: false;
|
|
54
|
+
isPrimaryKey: false;
|
|
55
|
+
isAutoincrement: false;
|
|
56
|
+
hasRuntimeDefault: false;
|
|
57
|
+
enumValues: undefined;
|
|
58
|
+
baseColumn: never;
|
|
59
|
+
identity: undefined;
|
|
60
|
+
generated: undefined;
|
|
61
|
+
}, {}, {
|
|
62
|
+
$type: Record<string, unknown>;
|
|
63
|
+
}>;
|
|
64
|
+
status: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
65
|
+
name: "status";
|
|
66
|
+
tableName: "jobs";
|
|
67
|
+
dataType: "string";
|
|
68
|
+
columnType: "MySqlVarChar";
|
|
69
|
+
data: "pending" | "processing" | "completed" | "failed";
|
|
70
|
+
driverParam: string | number;
|
|
71
|
+
notNull: true;
|
|
72
|
+
hasDefault: false;
|
|
73
|
+
isPrimaryKey: false;
|
|
74
|
+
isAutoincrement: false;
|
|
75
|
+
hasRuntimeDefault: false;
|
|
76
|
+
enumValues: [string, ...string[]];
|
|
77
|
+
baseColumn: never;
|
|
78
|
+
identity: undefined;
|
|
79
|
+
generated: undefined;
|
|
80
|
+
}, {}, {
|
|
81
|
+
$type: "pending" | "processing" | "completed" | "failed";
|
|
82
|
+
}>;
|
|
83
|
+
attempts: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
84
|
+
name: "attempts";
|
|
85
|
+
tableName: "jobs";
|
|
86
|
+
dataType: "number";
|
|
87
|
+
columnType: "MySqlInt";
|
|
88
|
+
data: number;
|
|
89
|
+
driverParam: string | number;
|
|
90
|
+
notNull: true;
|
|
91
|
+
hasDefault: true;
|
|
92
|
+
isPrimaryKey: false;
|
|
93
|
+
isAutoincrement: false;
|
|
94
|
+
hasRuntimeDefault: false;
|
|
95
|
+
enumValues: undefined;
|
|
96
|
+
baseColumn: never;
|
|
97
|
+
identity: undefined;
|
|
98
|
+
generated: undefined;
|
|
99
|
+
}, {}, {}>;
|
|
100
|
+
maxAttempts: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
101
|
+
name: "max_attempts";
|
|
102
|
+
tableName: "jobs";
|
|
103
|
+
dataType: "number";
|
|
104
|
+
columnType: "MySqlInt";
|
|
105
|
+
data: number;
|
|
106
|
+
driverParam: string | number;
|
|
107
|
+
notNull: true;
|
|
108
|
+
hasDefault: true;
|
|
109
|
+
isPrimaryKey: false;
|
|
110
|
+
isAutoincrement: false;
|
|
111
|
+
hasRuntimeDefault: false;
|
|
112
|
+
enumValues: undefined;
|
|
113
|
+
baseColumn: never;
|
|
114
|
+
identity: undefined;
|
|
115
|
+
generated: undefined;
|
|
116
|
+
}, {}, {}>;
|
|
117
|
+
uniqueKey: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
118
|
+
name: "unique_key";
|
|
119
|
+
tableName: "jobs";
|
|
120
|
+
dataType: "string";
|
|
121
|
+
columnType: "MySqlVarChar";
|
|
122
|
+
data: string;
|
|
123
|
+
driverParam: string | number;
|
|
124
|
+
notNull: false;
|
|
125
|
+
hasDefault: false;
|
|
126
|
+
isPrimaryKey: false;
|
|
127
|
+
isAutoincrement: false;
|
|
128
|
+
hasRuntimeDefault: false;
|
|
129
|
+
enumValues: [string, ...string[]];
|
|
130
|
+
baseColumn: never;
|
|
131
|
+
identity: undefined;
|
|
132
|
+
generated: undefined;
|
|
133
|
+
}, {}, {}>;
|
|
134
|
+
priority: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
135
|
+
name: "priority";
|
|
136
|
+
tableName: "jobs";
|
|
137
|
+
dataType: "number";
|
|
138
|
+
columnType: "MySqlInt";
|
|
139
|
+
data: number;
|
|
140
|
+
driverParam: string | number;
|
|
141
|
+
notNull: true;
|
|
142
|
+
hasDefault: true;
|
|
143
|
+
isPrimaryKey: false;
|
|
144
|
+
isAutoincrement: false;
|
|
145
|
+
hasRuntimeDefault: false;
|
|
146
|
+
enumValues: undefined;
|
|
147
|
+
baseColumn: never;
|
|
148
|
+
identity: undefined;
|
|
149
|
+
generated: undefined;
|
|
150
|
+
}, {}, {}>;
|
|
151
|
+
availableAt: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
152
|
+
name: "available_at";
|
|
153
|
+
tableName: "jobs";
|
|
154
|
+
dataType: "string";
|
|
155
|
+
columnType: "MySqlVarChar";
|
|
156
|
+
data: string;
|
|
157
|
+
driverParam: string | number;
|
|
158
|
+
notNull: true;
|
|
159
|
+
hasDefault: false;
|
|
160
|
+
isPrimaryKey: false;
|
|
161
|
+
isAutoincrement: false;
|
|
162
|
+
hasRuntimeDefault: false;
|
|
163
|
+
enumValues: [string, ...string[]];
|
|
164
|
+
baseColumn: never;
|
|
165
|
+
identity: undefined;
|
|
166
|
+
generated: undefined;
|
|
167
|
+
}, {}, {}>;
|
|
168
|
+
claimedAt: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
169
|
+
name: "claimed_at";
|
|
170
|
+
tableName: "jobs";
|
|
171
|
+
dataType: "string";
|
|
172
|
+
columnType: "MySqlVarChar";
|
|
173
|
+
data: string;
|
|
174
|
+
driverParam: string | number;
|
|
175
|
+
notNull: false;
|
|
176
|
+
hasDefault: false;
|
|
177
|
+
isPrimaryKey: false;
|
|
178
|
+
isAutoincrement: false;
|
|
179
|
+
hasRuntimeDefault: false;
|
|
180
|
+
enumValues: [string, ...string[]];
|
|
181
|
+
baseColumn: never;
|
|
182
|
+
identity: undefined;
|
|
183
|
+
generated: undefined;
|
|
184
|
+
}, {}, {}>;
|
|
185
|
+
claimedBy: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
186
|
+
name: "claimed_by";
|
|
187
|
+
tableName: "jobs";
|
|
188
|
+
dataType: "string";
|
|
189
|
+
columnType: "MySqlVarChar";
|
|
190
|
+
data: string;
|
|
191
|
+
driverParam: string | number;
|
|
192
|
+
notNull: false;
|
|
193
|
+
hasDefault: false;
|
|
194
|
+
isPrimaryKey: false;
|
|
195
|
+
isAutoincrement: false;
|
|
196
|
+
hasRuntimeDefault: false;
|
|
197
|
+
enumValues: [string, ...string[]];
|
|
198
|
+
baseColumn: never;
|
|
199
|
+
identity: undefined;
|
|
200
|
+
generated: undefined;
|
|
201
|
+
}, {}, {}>;
|
|
202
|
+
processedAt: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
203
|
+
name: "processed_at";
|
|
204
|
+
tableName: "jobs";
|
|
205
|
+
dataType: "string";
|
|
206
|
+
columnType: "MySqlVarChar";
|
|
207
|
+
data: string;
|
|
208
|
+
driverParam: string | number;
|
|
209
|
+
notNull: false;
|
|
210
|
+
hasDefault: false;
|
|
211
|
+
isPrimaryKey: false;
|
|
212
|
+
isAutoincrement: false;
|
|
213
|
+
hasRuntimeDefault: false;
|
|
214
|
+
enumValues: [string, ...string[]];
|
|
215
|
+
baseColumn: never;
|
|
216
|
+
identity: undefined;
|
|
217
|
+
generated: undefined;
|
|
218
|
+
}, {}, {}>;
|
|
219
|
+
lastError: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
220
|
+
name: "last_error";
|
|
221
|
+
tableName: "jobs";
|
|
222
|
+
dataType: "string";
|
|
223
|
+
columnType: "MySqlText";
|
|
224
|
+
data: string;
|
|
225
|
+
driverParam: string;
|
|
226
|
+
notNull: false;
|
|
227
|
+
hasDefault: false;
|
|
228
|
+
isPrimaryKey: false;
|
|
229
|
+
isAutoincrement: false;
|
|
230
|
+
hasRuntimeDefault: false;
|
|
231
|
+
enumValues: [string, ...string[]];
|
|
232
|
+
baseColumn: never;
|
|
233
|
+
identity: undefined;
|
|
234
|
+
generated: undefined;
|
|
235
|
+
}, {}, {}>;
|
|
236
|
+
createdAt: import("drizzle-orm/mysql-core").MySqlColumn<{
|
|
237
|
+
name: "created_at";
|
|
238
|
+
tableName: "jobs";
|
|
239
|
+
dataType: "string";
|
|
240
|
+
columnType: "MySqlVarChar";
|
|
241
|
+
data: string;
|
|
242
|
+
driverParam: string | number;
|
|
243
|
+
notNull: true;
|
|
244
|
+
hasDefault: false;
|
|
245
|
+
isPrimaryKey: false;
|
|
246
|
+
isAutoincrement: false;
|
|
247
|
+
hasRuntimeDefault: false;
|
|
248
|
+
enumValues: [string, ...string[]];
|
|
249
|
+
baseColumn: never;
|
|
250
|
+
identity: undefined;
|
|
251
|
+
generated: undefined;
|
|
252
|
+
}, {}, {}>;
|
|
253
|
+
};
|
|
254
|
+
dialect: "mysql";
|
|
255
|
+
}>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jobs = void 0;
|
|
4
|
+
const mysql_core_1 = require("drizzle-orm/mysql-core");
|
|
5
|
+
// MySQL-specific schema choices (documented so they are deliberate, not
|
|
6
|
+
// accidental divergence from the Postgres/SQLite definitions):
|
|
7
|
+
//
|
|
8
|
+
// - **`varchar` on every indexed column.** MySQL cannot index a `TEXT`/`BLOB`
|
|
9
|
+
// column without a prefix length, so `id`, `name`, `status`, `unique_key`,
|
|
10
|
+
// and `available_at` are `varchar(n)`. 191 is the classic utf8mb4-safe
|
|
11
|
+
// single-column index width; the composite `(name, unique_key)` index at
|
|
12
|
+
// 255 + 191 utf8mb4 chars stays well under InnoDB's 3072-byte key limit.
|
|
13
|
+
// The ISO-8601 timestamp columns fit comfortably in `varchar(32)`;
|
|
14
|
+
// free-form `last_error` stays `text`.
|
|
15
|
+
// - **The SAME full unique index on `(name, unique_key)`** as the other
|
|
16
|
+
// dialects. MySQL has no partial indexes, but none are needed: a UNIQUE
|
|
17
|
+
// index over a nullable column permits multiple `NULL`s (SQL treats `NULL`
|
|
18
|
+
// as distinct), and terminal transitions clear `unique_key` to `NULL` — so
|
|
19
|
+
// "unique among ACTIVE jobs" holds with identical semantics everywhere.
|
|
20
|
+
// - **`json` payload.** MySQL's native JSON type; mysql2 returns it already
|
|
21
|
+
// parsed, so the row shape matches `JobRow`.
|
|
22
|
+
/**
|
|
23
|
+
* MySQL `jobs` table. Add it to your Drizzle schema and generate a migration
|
|
24
|
+
* with drizzle-kit. Timestamps are stored as ISO-8601 `varchar` so the row
|
|
25
|
+
* shape is identical across dialects (ISO-8601 compares lexicographically,
|
|
26
|
+
* which the claimer's `available_at` range query relies on).
|
|
27
|
+
*/
|
|
28
|
+
exports.jobs = (0, mysql_core_1.mysqlTable)('jobs', {
|
|
29
|
+
id: (0, mysql_core_1.varchar)('id', { length: 191 }).primaryKey(),
|
|
30
|
+
name: (0, mysql_core_1.varchar)('name', { length: 255 }).notNull(),
|
|
31
|
+
payload: (0, mysql_core_1.json)('payload').$type().notNull(),
|
|
32
|
+
status: (0, mysql_core_1.varchar)('status', { length: 32 }).$type().notNull(),
|
|
33
|
+
attempts: (0, mysql_core_1.int)('attempts').notNull().default(0),
|
|
34
|
+
maxAttempts: (0, mysql_core_1.int)('max_attempts').notNull().default(10),
|
|
35
|
+
uniqueKey: (0, mysql_core_1.varchar)('unique_key', { length: 191 }),
|
|
36
|
+
priority: (0, mysql_core_1.int)('priority').notNull().default(0),
|
|
37
|
+
availableAt: (0, mysql_core_1.varchar)('available_at', { length: 32 }).notNull(),
|
|
38
|
+
claimedAt: (0, mysql_core_1.varchar)('claimed_at', { length: 32 }),
|
|
39
|
+
claimedBy: (0, mysql_core_1.varchar)('claimed_by', { length: 191 }),
|
|
40
|
+
processedAt: (0, mysql_core_1.varchar)('processed_at', { length: 32 }),
|
|
41
|
+
lastError: (0, mysql_core_1.text)('last_error'),
|
|
42
|
+
createdAt: (0, mysql_core_1.varchar)('created_at', { length: 32 }).notNull(),
|
|
43
|
+
}, (table) => [
|
|
44
|
+
(0, mysql_core_1.uniqueIndex)('jobs_name_unique_key_unique').on(table.name, table.uniqueKey),
|
|
45
|
+
(0, mysql_core_1.index)('jobs_status_available_idx').on(table.status, table.availableAt),
|
|
46
|
+
]);
|
|
47
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../dialects/mysql/schema.ts"],"names":[],"mappings":";;;AAAA,uDAQgC;AAGhC,wEAAwE;AACxE,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,qEAAqE;AACrE,yCAAyC;AACzC,wEAAwE;AACxE,0EAA0E;AAC1E,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,4EAA4E;AAC5E,+CAA+C;AAE/C;;;;;GAKG;AACU,QAAA,IAAI,GAAG,IAAA,uBAAU,EAC5B,MAAM,EACN;IACE,EAAE,EAAE,IAAA,oBAAO,EAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,UAAU,EAAE;IAC/C,IAAI,EAAE,IAAA,oBAAO,EAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE;IAChD,OAAO,EAAE,IAAA,iBAAI,EAAC,SAAS,CAAC,CAAC,KAAK,EAA2B,CAAC,OAAO,EAAE;IACnE,MAAM,EAAE,IAAA,oBAAO,EAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAa,CAAC,OAAO,EAAE;IACtE,QAAQ,EAAE,IAAA,gBAAG,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,WAAW,EAAE,IAAA,gBAAG,EAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACtD,SAAS,EAAE,IAAA,oBAAO,EAAC,YAAY,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IACjD,QAAQ,EAAE,IAAA,gBAAG,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,WAAW,EAAE,IAAA,oBAAO,EAAC,cAAc,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;IAC9D,SAAS,EAAE,IAAA,oBAAO,EAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAChD,SAAS,EAAE,IAAA,oBAAO,EAAC,YAAY,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IACjD,WAAW,EAAE,IAAA,oBAAO,EAAC,cAAc,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACpD,SAAS,EAAE,IAAA,iBAAI,EAAC,YAAY,CAAC;IAC7B,SAAS,EAAE,IAAA,oBAAO,EAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;CAC3D,EACD,CAAC,KAAK,EAAE,EAAE,CAAC;IACT,IAAA,wBAAW,EAAC,6BAA6B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1E,IAAA,kBAAK,EAAC,2BAA2B,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC;CACvE,CACF,CAAC"}
|