@zerotal/queue 1.13.0 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/api-surface.md +1 -0
- package/package.json +3 -3
- package/src/Job.ts +32 -3
- package/src/JobRegistry.ts +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,26 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.13.1] — 2026-08-31
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`Job.jobName`** — a declared name for the queue payload, so renaming a job class is free.
|
|
16
|
+
|
|
17
|
+
A queued job is a persisted reference to a class: the payload carries a string and the
|
|
18
|
+
worker resolves the class by it, so a job enqueued yesterday is looked up by today's
|
|
19
|
+
process. The class name was therefore a compatibility surface that nothing said was one —
|
|
20
|
+
renaming a job class silently invalidated every job already enqueued under the old name,
|
|
21
|
+
and the failure landed on the deploy rather than on the change. A test never sees it,
|
|
22
|
+
because a test enqueues and runs in the same process.
|
|
23
|
+
|
|
24
|
+
`static jobName` decouples the stored identity from the class, exactly as `Migration.id`
|
|
25
|
+
does for a migration's filename, and for the same reason. It defaults to the class name, so
|
|
26
|
+
a job that declares nothing behaves as before.
|
|
27
|
+
|
|
28
|
+
Found while sizing a suite-wide rename of `@internal` exports: three `Job` classes were on
|
|
29
|
+
that list, and renaming them would have been a data migration wearing a refactor's clothes.
|
|
30
|
+
|
|
11
31
|
## [1.9.0] — 2026-08-29
|
|
12
32
|
|
|
13
33
|
### Documented
|
package/api-surface.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/queue",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@zerotal/core": "1.13.
|
|
35
|
-
"@zerotal/orm": "1.13.
|
|
34
|
+
"@zerotal/core": "1.13.1",
|
|
35
|
+
"@zerotal/orm": "1.13.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^5.8.0"
|
package/src/Job.ts
CHANGED
|
@@ -81,10 +81,39 @@ export abstract class Job {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* The
|
|
85
|
-
*
|
|
84
|
+
* The name this job is stored and resolved under, when it should not be the
|
|
85
|
+
* class name.
|
|
86
|
+
*
|
|
87
|
+
* A queued job is a *persisted* reference to a class: the payload in the
|
|
88
|
+
* database or in Redis carries a string, and the worker looks the class up by
|
|
89
|
+
* it. So the class name is a wire identifier, not just a source symbol — and
|
|
90
|
+
* renaming the class silently invalidates every job already enqueued under the
|
|
91
|
+
* old name. The failure lands on the deploy rather than on the change, and a
|
|
92
|
+
* test never sees it, because a test enqueues and runs in the same process.
|
|
93
|
+
*
|
|
94
|
+
* Declaring the name decouples the two, exactly as
|
|
95
|
+
* {@link @zerotal/orm!Migration.id} does for a migration's filename:
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* export class SendWelcomeEmail extends Job {
|
|
99
|
+
* static override jobName = "SendWelcomeEmail"; // survives a class rename
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* It also survives a build that mangles names. `zt compile` does not minify
|
|
104
|
+
* today, so this is not a live hazard — but nothing about the registry said it
|
|
105
|
+
* depended on that, which is the kind of assumption worth writing down before
|
|
106
|
+
* it is discovered.
|
|
107
|
+
*
|
|
108
|
+
* Defaults to the class name, so nothing changes for a job that does not set it.
|
|
109
|
+
*/
|
|
110
|
+
static jobName?: string;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The name used as the serialization key. Matches what `JobRegistry.register()`
|
|
114
|
+
* stores: {@link Job.jobName} when declared, the class name otherwise.
|
|
86
115
|
*/
|
|
87
116
|
get className(): string {
|
|
88
|
-
return this.constructor.name;
|
|
117
|
+
return (this.constructor as typeof Job).jobName ?? this.constructor.name;
|
|
89
118
|
}
|
|
90
119
|
}
|
package/src/JobRegistry.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import type { Job } from "./Job.ts";
|
|
2
2
|
|
|
3
|
-
type JobClass = new (...args: never[]) => Job;
|
|
3
|
+
type JobClass = (new (...args: never[]) => Job) & { jobName?: string };
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Global registry mapping job
|
|
6
|
+
* Global registry mapping a job's stored name to its constructor.
|
|
7
7
|
* Required for deserialization — when a job is popped from the queue,
|
|
8
8
|
* the worker looks up the class here to instantiate it.
|
|
9
|
+
*
|
|
10
|
+
* The key is {@link Job.jobName} when a job declares one, and the class name
|
|
11
|
+
* otherwise. That distinction exists because the key is written into a persisted
|
|
12
|
+
* payload: a job enqueued yesterday is resolved by today's process, so the name
|
|
13
|
+
* is a compatibility surface and renaming the class is a migration.
|
|
9
14
|
*/
|
|
10
15
|
export const JobRegistry = {
|
|
11
16
|
_map: new Map<string, JobClass>(),
|
|
12
17
|
|
|
13
18
|
register(JobClass: JobClass): void {
|
|
14
|
-
JobRegistry._map.set(JobClass.name, JobClass);
|
|
19
|
+
JobRegistry._map.set(JobClass.jobName ?? JobClass.name, JobClass);
|
|
15
20
|
},
|
|
16
21
|
|
|
17
22
|
resolve(className: string): JobClass | undefined {
|