@arkstack/jobs 0.5.3
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 +71 -0
- package/dist/bridge-CQ0DHM02.js +198 -0
- package/dist/commands/MakeJobCommand.d.ts +14 -0
- package/dist/commands/MakeJobCommand.js +47 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +26 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +12 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Toneflix Technologies Limited
|
|
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
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @arkstack/jobs
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@arkstack/jobs)
|
|
4
|
+
|
|
5
|
+
Jobs module for Arkstack, providing dispatchable job classes and a dispatcher on top of [`@arkstack/queue`](../queue).
|
|
6
|
+
|
|
7
|
+
`@arkstack/jobs` is the **authoring** layer; `@arkstack/queue` is the transport. This package gives you the `Job` base class, the `dispatch()` helper, and a registry so workers can reconstruct your job classes from a stored payload.
|
|
8
|
+
|
|
9
|
+
## Writing a job
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
ark make:job SendWelcomeEmail
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// src/app/jobs/SendWelcomeEmail.ts
|
|
17
|
+
import { Job } from '@arkstack/jobs'
|
|
18
|
+
|
|
19
|
+
export class SendWelcomeEmail extends Job {
|
|
20
|
+
constructor(public userId: number) {
|
|
21
|
+
super()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async handle () {
|
|
25
|
+
// ... send the email
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Dispatching
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { dispatch } from '@arkstack/jobs'
|
|
34
|
+
import { SendWelcomeEmail } from '@app/jobs/SendWelcomeEmail'
|
|
35
|
+
|
|
36
|
+
// static helper
|
|
37
|
+
await SendWelcomeEmail.dispatch(user.id)
|
|
38
|
+
await SendWelcomeEmail.dispatch(user.id).onQueue('mail').withDelay(60)
|
|
39
|
+
await SendWelcomeEmail.dispatchSync(user.id) // run inline now
|
|
40
|
+
|
|
41
|
+
// functional helper
|
|
42
|
+
await dispatch(new SendWelcomeEmail(user.id))
|
|
43
|
+
await dispatch(new SendWelcomeEmail(user.id), { queue: 'mail', delay: 60 })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
With the default `sync` queue connection the job runs immediately. Configure a
|
|
47
|
+
`database` or `redis` connection (see `@arkstack/queue`) and run a worker to
|
|
48
|
+
process jobs in the background:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
ark queue:work
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Setup
|
|
55
|
+
|
|
56
|
+
Importing `@arkstack/jobs` anywhere wires the queue (de)serialization
|
|
57
|
+
automatically. For an explicit bootstrap hook (next to `@arkstack/database/setup`):
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import '@arkstack/jobs/setup'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## How reconstruction works
|
|
64
|
+
|
|
65
|
+
Each `Job` registers itself with the `JobRegistry` when constructed. When a
|
|
66
|
+
worker pops a payload, the registry rebuilds the instance (bypassing the
|
|
67
|
+
constructor) and assigns the serialized `data` back onto it. For dedicated worker
|
|
68
|
+
**processes**, make sure your job modules are imported — or call
|
|
69
|
+
`JobRegistry.register(MyJob)` — so the names are known before processing.
|
|
70
|
+
|
|
71
|
+
Override `serialize()` on a job for custom payloads.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Queue } from "@arkstack/queue";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
//#region src/JobRegistry.ts
|
|
4
|
+
/**
|
|
5
|
+
* A registry mapping job names to their classes so a worker can reconstruct job
|
|
6
|
+
* instances from a stored payload.
|
|
7
|
+
*
|
|
8
|
+
* Job classes register themselves when instantiated, which covers same-process
|
|
9
|
+
* dispatch + work. For dedicated worker processes, ensure the job modules are
|
|
10
|
+
* imported (or call {@link JobRegistry.register} explicitly) so the names are
|
|
11
|
+
* known before jobs are processed.
|
|
12
|
+
*/
|
|
13
|
+
var JobRegistry = class {
|
|
14
|
+
static classes = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Register a job class under an explicit name (defaults to the class name).
|
|
17
|
+
*/
|
|
18
|
+
static register(jobClass, name) {
|
|
19
|
+
this.classes.set(name ?? jobClass.name, jobClass);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The registered name for a job instance, if any.
|
|
24
|
+
*/
|
|
25
|
+
static nameOf(job) {
|
|
26
|
+
const ctor = job.constructor;
|
|
27
|
+
for (const [name, jobClass] of this.classes) if (jobClass === ctor) return name;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether a job name is registered.
|
|
31
|
+
*/
|
|
32
|
+
static has(name) {
|
|
33
|
+
return this.classes.has(name);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reconstruct a job instance from a payload.
|
|
37
|
+
*
|
|
38
|
+
* The constructor is bypassed (via `Object.create`) and the serialized data
|
|
39
|
+
* is assigned onto a fresh instance, so reconstruction never re-runs
|
|
40
|
+
* constructor side effects.
|
|
41
|
+
*/
|
|
42
|
+
static resolve(payload) {
|
|
43
|
+
const jobClass = this.classes.get(payload.displayName);
|
|
44
|
+
if (!jobClass) throw new Error(`Job "${payload.displayName}" is not registered. Import the job class or call JobRegistry.register().`);
|
|
45
|
+
const instance = Object.create(jobClass.prototype);
|
|
46
|
+
Object.assign(instance, payload.data);
|
|
47
|
+
return instance;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Remove all registrations. Intended for tests.
|
|
51
|
+
*/
|
|
52
|
+
static clear() {
|
|
53
|
+
this.classes.clear();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/PendingDispatch.ts
|
|
58
|
+
/**
|
|
59
|
+
* A fluent, awaitable handle returned by `dispatch()` and `Job.dispatch()`.
|
|
60
|
+
*
|
|
61
|
+
* It collects routing overrides and, when awaited, pushes the job onto the
|
|
62
|
+
* appropriate connection. Being thenable means `await dispatch(job)` works
|
|
63
|
+
* directly, and the chained setters return `this` so they can precede the await:
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* await dispatch(new SendReport(id)).onQueue('reports').withDelay(30)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
var PendingDispatch = class {
|
|
70
|
+
job;
|
|
71
|
+
constructor(job) {
|
|
72
|
+
this.job = job;
|
|
73
|
+
}
|
|
74
|
+
/** Set the connection this job is dispatched to. */
|
|
75
|
+
onConnection(connection) {
|
|
76
|
+
this.job.connection = connection;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/** Set the queue this job is dispatched to. */
|
|
80
|
+
onQueue(queue) {
|
|
81
|
+
this.job.queue = queue;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
/** Delay the dispatch by a number of seconds (or until a Date). */
|
|
85
|
+
withDelay(delay) {
|
|
86
|
+
this.job.delay = delay instanceof Date ? Math.max(0, Math.round((delay.getTime() - Date.now()) / 1e3)) : delay;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
/** The underlying job instance. */
|
|
90
|
+
getJob() {
|
|
91
|
+
return this.job;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Perform the dispatch, returning the resulting job id.
|
|
95
|
+
*/
|
|
96
|
+
send() {
|
|
97
|
+
if (this.job.delay && this.job.delay > 0) return Queue.later(this.job.delay, this.job, this.job.queue);
|
|
98
|
+
return Queue.push(this.job, this.job.queue);
|
|
99
|
+
}
|
|
100
|
+
then(onfulfilled, onrejected) {
|
|
101
|
+
return this.send().then(onfulfilled, onrejected);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/Job.ts
|
|
106
|
+
/**
|
|
107
|
+
* The base class for dispatchable jobs.
|
|
108
|
+
*
|
|
109
|
+
* Extend it and implement {@link handle}. Instances are {@link Queueable}, so
|
|
110
|
+
* they can be pushed straight onto a queue, but the fluent dispatch API is the
|
|
111
|
+
* idiomatic entry point:
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* class SendWelcomeEmail extends Job {
|
|
115
|
+
* constructor(public userId: number) { super() }
|
|
116
|
+
* async handle() { ... }
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* await SendWelcomeEmail.dispatch(1) // default connection/queue
|
|
120
|
+
* await SendWelcomeEmail.dispatch(1).onQueue('mail') // fluent overrides
|
|
121
|
+
* await SendWelcomeEmail.dispatch(1).withDelay(60)
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
var Job = class {
|
|
125
|
+
/** The connection this job should be sent to. */
|
|
126
|
+
connection;
|
|
127
|
+
/** The queue this job should be sent to. */
|
|
128
|
+
queue;
|
|
129
|
+
/** Seconds to delay before the job becomes available. */
|
|
130
|
+
delay;
|
|
131
|
+
/** Maximum number of attempts before the job is marked failed. */
|
|
132
|
+
tries;
|
|
133
|
+
/** Seconds to wait before a released job becomes available again. */
|
|
134
|
+
backoff;
|
|
135
|
+
constructor() {
|
|
136
|
+
JobRegistry.register(this.constructor);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Serialize the job's state for storage. Defaults to a shallow copy of the
|
|
140
|
+
* instance's own properties. Override for custom serialization.
|
|
141
|
+
*/
|
|
142
|
+
serialize() {
|
|
143
|
+
return { ...this };
|
|
144
|
+
}
|
|
145
|
+
/** Send this job to the given connection. */
|
|
146
|
+
onConnection(connection) {
|
|
147
|
+
this.connection = connection;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/** Send this job to the given queue. */
|
|
151
|
+
onQueue(queue) {
|
|
152
|
+
this.queue = queue;
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
/** Delay the job by a number of seconds. */
|
|
156
|
+
withDelay(seconds) {
|
|
157
|
+
this.delay = seconds;
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Create a pending dispatch for this job class with the given constructor
|
|
162
|
+
* arguments. Await it (or chain `onQueue`/`onConnection`/`withDelay`) to send.
|
|
163
|
+
*/
|
|
164
|
+
static dispatch(...args) {
|
|
165
|
+
return new PendingDispatch(new this(...args));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Dispatch immediately on the synchronous connection, running the job inline.
|
|
169
|
+
*/
|
|
170
|
+
static dispatchSync(...args) {
|
|
171
|
+
return new PendingDispatch(new this(...args)).onConnection("sync");
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/bridge.ts
|
|
176
|
+
let registered = false;
|
|
177
|
+
/**
|
|
178
|
+
* Wire `@arkstack/jobs` into `@arkstack/queue` by registering how jobs are
|
|
179
|
+
* serialized to and resolved from payloads.
|
|
180
|
+
*
|
|
181
|
+
* Importing `@arkstack/jobs` (or `@arkstack/jobs/setup`) runs this once. It is
|
|
182
|
+
* idempotent, so calling it again is a no-op.
|
|
183
|
+
*/
|
|
184
|
+
const registerJobsWithQueue = () => {
|
|
185
|
+
if (registered) return;
|
|
186
|
+
registered = true;
|
|
187
|
+
Queue.serializeUsing((job) => ({
|
|
188
|
+
id: randomUUID(),
|
|
189
|
+
displayName: (job instanceof Job ? JobRegistry.nameOf(job) : void 0) ?? job.constructor?.name ?? "Closure",
|
|
190
|
+
attempts: 0,
|
|
191
|
+
maxTries: job.tries ?? null,
|
|
192
|
+
backoff: job.backoff ?? 0,
|
|
193
|
+
data: typeof job.serialize === "function" ? job.serialize() : { ...job }
|
|
194
|
+
}));
|
|
195
|
+
Queue.resolveJobsUsing((payload) => JobRegistry.resolve(payload));
|
|
196
|
+
};
|
|
197
|
+
//#endregion
|
|
198
|
+
export { JobRegistry as i, Job as n, PendingDispatch as r, registerJobsWithQueue as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from "@h3ravel/musket";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/MakeJobCommand.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Generate a new dispatchable job class.
|
|
6
|
+
*/
|
|
7
|
+
declare class MakeJobCommand extends Command {
|
|
8
|
+
protected signature: string;
|
|
9
|
+
protected description: string;
|
|
10
|
+
handle(): Promise<undefined>;
|
|
11
|
+
stub(name: string): string;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { MakeJobCommand };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { Arkstack } from "@arkstack/contract";
|
|
3
|
+
import { Command } from "@h3ravel/musket";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
//#region src/commands/MakeJobCommand.ts
|
|
6
|
+
/**
|
|
7
|
+
* Generate a new dispatchable job class.
|
|
8
|
+
*/
|
|
9
|
+
var MakeJobCommand = class extends Command {
|
|
10
|
+
signature = `make:job
|
|
11
|
+
{name : The name of the job class to create.}
|
|
12
|
+
`;
|
|
13
|
+
description = "Create a new queued job class.";
|
|
14
|
+
async handle() {
|
|
15
|
+
const name = String(this.argument("name")).replace(/\s+/g, "").replace(/\.ts$/, "").trim();
|
|
16
|
+
if (!name) {
|
|
17
|
+
this.error("Job name is required");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const className = name.split("/").pop();
|
|
21
|
+
const filePath = resolve(Arkstack.rootDir(), "src", `app/jobs/${name}.ts`);
|
|
22
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
23
|
+
await writeFile(filePath, this.stub(className), { flag: "wx" });
|
|
24
|
+
this.success(`Job ${className} created successfully at ${filePath}`);
|
|
25
|
+
}
|
|
26
|
+
stub(name) {
|
|
27
|
+
return [
|
|
28
|
+
"import { Job } from '@arkstack/jobs'",
|
|
29
|
+
"",
|
|
30
|
+
`export class ${name} extends Job {`,
|
|
31
|
+
" constructor() {",
|
|
32
|
+
" super()",
|
|
33
|
+
" }",
|
|
34
|
+
"",
|
|
35
|
+
" /**",
|
|
36
|
+
" * Execute the job.",
|
|
37
|
+
" */",
|
|
38
|
+
" async handle () {",
|
|
39
|
+
" // Job logic goes here",
|
|
40
|
+
" }",
|
|
41
|
+
"}",
|
|
42
|
+
""
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { MakeJobCommand };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { JobPayload, Queueable } from "@arkstack/queue";
|
|
2
|
+
|
|
3
|
+
//#region src/PendingDispatch.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A fluent, awaitable handle returned by `dispatch()` and `Job.dispatch()`.
|
|
6
|
+
*
|
|
7
|
+
* It collects routing overrides and, when awaited, pushes the job onto the
|
|
8
|
+
* appropriate connection. Being thenable means `await dispatch(job)` works
|
|
9
|
+
* directly, and the chained setters return `this` so they can precede the await:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* await dispatch(new SendReport(id)).onQueue('reports').withDelay(30)
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare class PendingDispatch<T extends Job = Job> implements PromiseLike<string> {
|
|
16
|
+
private readonly job;
|
|
17
|
+
constructor(job: T);
|
|
18
|
+
/** Set the connection this job is dispatched to. */
|
|
19
|
+
onConnection(connection: string): this;
|
|
20
|
+
/** Set the queue this job is dispatched to. */
|
|
21
|
+
onQueue(queue: string): this;
|
|
22
|
+
/** Delay the dispatch by a number of seconds (or until a Date). */
|
|
23
|
+
withDelay(delay: number | Date): this;
|
|
24
|
+
/** The underlying job instance. */
|
|
25
|
+
getJob(): T;
|
|
26
|
+
/**
|
|
27
|
+
* Perform the dispatch, returning the resulting job id.
|
|
28
|
+
*/
|
|
29
|
+
private send;
|
|
30
|
+
then<TResult1 = string, TResult2 = never>(onfulfilled?: ((value: string) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/Job.d.ts
|
|
34
|
+
/**
|
|
35
|
+
* The base class for dispatchable jobs.
|
|
36
|
+
*
|
|
37
|
+
* Extend it and implement {@link handle}. Instances are {@link Queueable}, so
|
|
38
|
+
* they can be pushed straight onto a queue, but the fluent dispatch API is the
|
|
39
|
+
* idiomatic entry point:
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* class SendWelcomeEmail extends Job {
|
|
43
|
+
* constructor(public userId: number) { super() }
|
|
44
|
+
* async handle() { ... }
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* await SendWelcomeEmail.dispatch(1) // default connection/queue
|
|
48
|
+
* await SendWelcomeEmail.dispatch(1).onQueue('mail') // fluent overrides
|
|
49
|
+
* await SendWelcomeEmail.dispatch(1).withDelay(60)
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare abstract class Job implements Queueable {
|
|
53
|
+
/** The connection this job should be sent to. */
|
|
54
|
+
connection?: string;
|
|
55
|
+
/** The queue this job should be sent to. */
|
|
56
|
+
queue?: string;
|
|
57
|
+
/** Seconds to delay before the job becomes available. */
|
|
58
|
+
delay?: number;
|
|
59
|
+
/** Maximum number of attempts before the job is marked failed. */
|
|
60
|
+
tries?: number;
|
|
61
|
+
/** Seconds to wait before a released job becomes available again. */
|
|
62
|
+
backoff?: number;
|
|
63
|
+
constructor();
|
|
64
|
+
/**
|
|
65
|
+
* Perform the work for this job. Implemented by subclasses.
|
|
66
|
+
*/
|
|
67
|
+
abstract handle(): unknown | Promise<unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* Serialize the job's state for storage. Defaults to a shallow copy of the
|
|
70
|
+
* instance's own properties. Override for custom serialization.
|
|
71
|
+
*/
|
|
72
|
+
serialize(): Record<string, unknown>;
|
|
73
|
+
/** Send this job to the given connection. */
|
|
74
|
+
onConnection(connection: string): this;
|
|
75
|
+
/** Send this job to the given queue. */
|
|
76
|
+
onQueue(queue: string): this;
|
|
77
|
+
/** Delay the job by a number of seconds. */
|
|
78
|
+
withDelay(seconds: number): this;
|
|
79
|
+
/**
|
|
80
|
+
* Create a pending dispatch for this job class with the given constructor
|
|
81
|
+
* arguments. Await it (or chain `onQueue`/`onConnection`/`withDelay`) to send.
|
|
82
|
+
*/
|
|
83
|
+
static dispatch<T extends Job>(this: new (...args: any[]) => T, ...args: any[]): PendingDispatch<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Dispatch immediately on the synchronous connection, running the job inline.
|
|
86
|
+
*/
|
|
87
|
+
static dispatchSync<T extends Job>(this: new (...args: any[]) => T, ...args: any[]): PendingDispatch<T>;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/types.d.ts
|
|
91
|
+
/**
|
|
92
|
+
* Constructor type for a dispatchable {@link Job} subclass.
|
|
93
|
+
*/
|
|
94
|
+
type JobConstructor<T extends Job = Job> = new (...args: any[]) => T;
|
|
95
|
+
/**
|
|
96
|
+
* Options that can be passed to a dispatch call to override routing/retry.
|
|
97
|
+
*/
|
|
98
|
+
interface DispatchOptions {
|
|
99
|
+
connection?: string;
|
|
100
|
+
queue?: string;
|
|
101
|
+
delay?: number | Date;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/JobRegistry.d.ts
|
|
105
|
+
/**
|
|
106
|
+
* A registry mapping job names to their classes so a worker can reconstruct job
|
|
107
|
+
* instances from a stored payload.
|
|
108
|
+
*
|
|
109
|
+
* Job classes register themselves when instantiated, which covers same-process
|
|
110
|
+
* dispatch + work. For dedicated worker processes, ensure the job modules are
|
|
111
|
+
* imported (or call {@link JobRegistry.register} explicitly) so the names are
|
|
112
|
+
* known before jobs are processed.
|
|
113
|
+
*/
|
|
114
|
+
declare class JobRegistry {
|
|
115
|
+
private static classes;
|
|
116
|
+
/**
|
|
117
|
+
* Register a job class under an explicit name (defaults to the class name).
|
|
118
|
+
*/
|
|
119
|
+
static register(jobClass: JobConstructor, name?: string): typeof JobRegistry;
|
|
120
|
+
/**
|
|
121
|
+
* The registered name for a job instance, if any.
|
|
122
|
+
*/
|
|
123
|
+
static nameOf(job: Job): string | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Whether a job name is registered.
|
|
126
|
+
*/
|
|
127
|
+
static has(name: string): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Reconstruct a job instance from a payload.
|
|
130
|
+
*
|
|
131
|
+
* The constructor is bypassed (via `Object.create`) and the serialized data
|
|
132
|
+
* is assigned onto a fresh instance, so reconstruction never re-runs
|
|
133
|
+
* constructor side effects.
|
|
134
|
+
*/
|
|
135
|
+
static resolve(payload: JobPayload): Job;
|
|
136
|
+
/**
|
|
137
|
+
* Remove all registrations. Intended for tests.
|
|
138
|
+
*/
|
|
139
|
+
static clear(): void;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/dispatch.d.ts
|
|
143
|
+
/**
|
|
144
|
+
* Dispatch a job instance onto a queue.
|
|
145
|
+
*
|
|
146
|
+
* Returns a {@link PendingDispatch} that can be awaited directly, chained with
|
|
147
|
+
* routing overrides, or passed `options` up front:
|
|
148
|
+
*
|
|
149
|
+
* ```ts
|
|
150
|
+
* await dispatch(new SendReport(id))
|
|
151
|
+
* await dispatch(new SendReport(id)).onQueue('reports')
|
|
152
|
+
* await dispatch(new SendReport(id), { queue: 'reports', delay: 30 })
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
declare const dispatch: <T extends Job>(job: T, options?: DispatchOptions) => PendingDispatch<T>;
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/bridge.d.ts
|
|
158
|
+
/**
|
|
159
|
+
* Wire `@arkstack/jobs` into `@arkstack/queue` by registering how jobs are
|
|
160
|
+
* serialized to and resolved from payloads.
|
|
161
|
+
*
|
|
162
|
+
* Importing `@arkstack/jobs` (or `@arkstack/jobs/setup`) runs this once. It is
|
|
163
|
+
* idempotent, so calling it again is a no-op.
|
|
164
|
+
*/
|
|
165
|
+
declare const registerJobsWithQueue: () => void;
|
|
166
|
+
//#endregion
|
|
167
|
+
export { DispatchOptions, Job, JobConstructor, JobRegistry, PendingDispatch, dispatch, registerJobsWithQueue };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { i as JobRegistry, n as Job, r as PendingDispatch, t as registerJobsWithQueue } from "./bridge-CQ0DHM02.js";
|
|
2
|
+
//#region src/dispatch.ts
|
|
3
|
+
/**
|
|
4
|
+
* Dispatch a job instance onto a queue.
|
|
5
|
+
*
|
|
6
|
+
* Returns a {@link PendingDispatch} that can be awaited directly, chained with
|
|
7
|
+
* routing overrides, or passed `options` up front:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* await dispatch(new SendReport(id))
|
|
11
|
+
* await dispatch(new SendReport(id)).onQueue('reports')
|
|
12
|
+
* await dispatch(new SendReport(id), { queue: 'reports', delay: 30 })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
const dispatch = (job, options = {}) => {
|
|
16
|
+
const pending = new PendingDispatch(job);
|
|
17
|
+
if (options.connection) pending.onConnection(options.connection);
|
|
18
|
+
if (options.queue) pending.onQueue(options.queue);
|
|
19
|
+
if (options.delay !== void 0) pending.withDelay(options.delay);
|
|
20
|
+
return pending;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/index.ts
|
|
24
|
+
registerJobsWithQueue();
|
|
25
|
+
//#endregion
|
|
26
|
+
export { Job, JobRegistry, PendingDispatch, dispatch, registerJobsWithQueue };
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as registerJobsWithQueue } from "./bridge-CQ0DHM02.js";
|
|
2
|
+
//#region src/setup.ts
|
|
3
|
+
/**
|
|
4
|
+
* Boot the jobs/queue integration. Import this from your application bootstrap:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import '@arkstack/jobs/setup'
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
registerJobsWithQueue();
|
|
11
|
+
//#endregion
|
|
12
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arkstack/jobs",
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Jobs module for Arkstack, providing dispatchable job classes and a dispatcher on top of @arkstack/queue.",
|
|
6
|
+
"homepage": "https://arkstack.toneflix.net/guide/jobs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/arkstack-hq/arkstack.git",
|
|
10
|
+
"directory": "packages/jobs"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"jobs",
|
|
14
|
+
"queue",
|
|
15
|
+
"dispatch",
|
|
16
|
+
"background",
|
|
17
|
+
"dispatchable",
|
|
18
|
+
"worker",
|
|
19
|
+
"arkstack"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./dist/index.js",
|
|
29
|
+
"./commands/MakeJobCommand": "./dist/commands/MakeJobCommand.js",
|
|
30
|
+
"./setup": "./dist/setup.js",
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@arkstack/common": "^0.5.3",
|
|
35
|
+
"@arkstack/queue": "^0.5.3"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@h3ravel/musket": "^2.2.1",
|
|
39
|
+
"@arkstack/contract": "^0.5.3"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsdown",
|
|
43
|
+
"test": "vitest",
|
|
44
|
+
"version:patch": "pnpm version patch"
|
|
45
|
+
}
|
|
46
|
+
}
|