@c9up/bay 0.1.12 → 0.2.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 +172 -1
- package/dist/BayProvider.d.ts +55 -33
- package/dist/BayProvider.d.ts.map +1 -1
- package/dist/BayProvider.js +81 -10
- package/dist/BayProvider.js.map +1 -1
- package/dist/Job.d.ts +99 -0
- package/dist/Job.d.ts.map +1 -0
- package/dist/Job.js +78 -0
- package/dist/Job.js.map +1 -0
- package/dist/QueueManager.d.ts +147 -22
- package/dist/QueueManager.d.ts.map +1 -1
- package/dist/QueueManager.js +290 -52
- package/dist/QueueManager.js.map +1 -1
- package/dist/adapters.d.ts +68 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +56 -0
- package/dist/adapters.js.map +1 -0
- package/dist/augmentations.d.ts +28 -0
- package/dist/augmentations.d.ts.map +1 -0
- package/dist/augmentations.js +17 -0
- package/dist/augmentations.js.map +1 -0
- package/dist/configure.d.ts +19 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +48 -0
- package/dist/configure.js.map +1 -0
- package/dist/console/contract.d.ts +60 -0
- package/dist/console/contract.d.ts.map +1 -0
- package/dist/console/contract.js +36 -0
- package/dist/console/contract.js.map +1 -0
- package/dist/console/index.d.ts +29 -0
- package/dist/console/index.d.ts.map +1 -0
- package/dist/console/index.js +45 -0
- package/dist/console/index.js.map +1 -0
- package/dist/console/makeJob.d.ts +32 -0
- package/dist/console/makeJob.d.ts.map +1 -0
- package/dist/console/makeJob.js +118 -0
- package/dist/console/makeJob.js.map +1 -0
- package/dist/console/queueWork.d.ts +18 -0
- package/dist/console/queueWork.d.ts.map +1 -0
- package/dist/console/queueWork.js +58 -0
- package/dist/console/queueWork.js.map +1 -0
- package/dist/drivers/MemoryDriver.d.ts +14 -8
- package/dist/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/drivers/MemoryDriver.js +61 -7
- package/dist/drivers/MemoryDriver.js.map +1 -1
- package/dist/drivers/RedisDriver.d.ts +66 -8
- package/dist/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/drivers/RedisDriver.js +257 -45
- package/dist/drivers/RedisDriver.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +43 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +105 -0
- package/dist/jobs.js.map +1 -0
- package/dist/nodeEnv.d.ts +16 -0
- package/dist/nodeEnv.d.ts.map +1 -0
- package/dist/nodeEnv.js +32 -0
- package/dist/nodeEnv.js.map +1 -0
- package/dist/quasar.d.ts +1 -1
- package/dist/quasar.js +1 -1
- package/dist/services/main.d.ts +5 -0
- package/dist/services/main.d.ts.map +1 -1
- package/dist/services/main.js +7 -0
- package/dist/services/main.js.map +1 -1
- package/dist/testing/FakeQueue.d.ts +15 -9
- package/dist/testing/FakeQueue.d.ts.map +1 -1
- package/dist/testing/FakeQueue.js +13 -3
- package/dist/testing/FakeQueue.js.map +1 -1
- package/package.json +9 -3
- package/src/BayProvider.ts +143 -25
- package/src/Job.ts +137 -0
- package/src/QueueManager.ts +454 -56
- package/src/adapters.ts +75 -0
- package/src/augmentations.ts +31 -0
- package/src/configure.ts +63 -0
- package/src/console/contract.ts +94 -0
- package/src/console/index.ts +68 -0
- package/src/console/makeJob.ts +139 -0
- package/src/console/queueWork.ts +70 -0
- package/src/drivers/MemoryDriver.ts +66 -14
- package/src/drivers/RedisDriver.ts +366 -63
- package/src/index.ts +35 -5
- package/src/jobs.ts +111 -0
- package/src/nodeEnv.ts +30 -0
- package/src/quasar.ts +1 -1
- package/src/services/main.ts +8 -0
- package/src/testing/FakeQueue.ts +25 -15
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding the job classes an application wrote.
|
|
3
|
+
*
|
|
4
|
+
* A job class carries its own name, and a worker resolves a queued record by
|
|
5
|
+
* that name — so the worker process has to have imported the class. Written by
|
|
6
|
+
* hand that is a registration list to keep in step with a directory:
|
|
7
|
+
*
|
|
8
|
+
* queue.registerJob(SendEmail)
|
|
9
|
+
* queue.registerJob(SendInvoice) // …and the one nobody added
|
|
10
|
+
*
|
|
11
|
+
* `locations` in `config/queue.ts` is the directory instead. Every module under
|
|
12
|
+
* it is imported once at boot, and a default export that is a job class is
|
|
13
|
+
* registered under its own name.
|
|
14
|
+
*
|
|
15
|
+
* Directories, not globs. `'./app/jobs/**\/*.{ts,js}'` — the spelling upstream's
|
|
16
|
+
* config uses — is accepted and read as the directory it starts with, so a
|
|
17
|
+
* config copied from there works; bay ships no glob engine and adding a
|
|
18
|
+
* dependency for one path shape is not worth it.
|
|
19
|
+
*/
|
|
20
|
+
import * as fsp from "node:fs/promises";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { pathToFileURL } from "node:url";
|
|
23
|
+
import { isJobClass } from "./Job.js";
|
|
24
|
+
/** Where `make:job` writes, and where discovery looks when nothing is declared. */
|
|
25
|
+
export const DEFAULT_JOBS_DIR = "app/jobs";
|
|
26
|
+
let jobsDir = DEFAULT_JOBS_DIR;
|
|
27
|
+
/** @internal Told by the provider what the config declared. */
|
|
28
|
+
export function setJobsDir(dir) {
|
|
29
|
+
jobsDir = dir;
|
|
30
|
+
}
|
|
31
|
+
/** Where job files live — the first `locations` entry, or the default. */
|
|
32
|
+
export function getJobsDir() {
|
|
33
|
+
return jobsDir;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The directory a `locations` entry names.
|
|
37
|
+
*
|
|
38
|
+
* Everything from the first glob character on is dropped: `app/jobs/** /*.ts`
|
|
39
|
+
* and `app/jobs` name the same directory, and the walk below is recursive
|
|
40
|
+
* either way.
|
|
41
|
+
*/
|
|
42
|
+
export function directoryOf(location) {
|
|
43
|
+
const withoutGlob = location.split(/[*?[{]/)[0] ?? location;
|
|
44
|
+
const trimmed = withoutGlob.replace(/\/+$/, "");
|
|
45
|
+
return trimmed.replace(/^\.\//, "") || ".";
|
|
46
|
+
}
|
|
47
|
+
/** Every module file under `dir`, recursively. */
|
|
48
|
+
async function walk(dir, depth = 0) {
|
|
49
|
+
// A jobs directory is a flat convention with the occasional subdirectory;
|
|
50
|
+
// an unbounded walk would follow whatever happens to live under it.
|
|
51
|
+
if (depth > 8)
|
|
52
|
+
return [];
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// A declared directory that does not exist yet is not an error: a project
|
|
59
|
+
// can name where its jobs will go before writing the first one.
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const found = [];
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const full = path.join(dir, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
found.push(...(await walk(full, depth + 1)));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// `.d.ts` is a declaration, not a module with a job in it.
|
|
70
|
+
if (/\.d\.[cm]?ts$/.test(entry.name))
|
|
71
|
+
continue;
|
|
72
|
+
if (/\.[cm]?[jt]s$/.test(entry.name))
|
|
73
|
+
found.push(full);
|
|
74
|
+
}
|
|
75
|
+
return found.sort();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Import every module under `locations` and return the job classes they
|
|
79
|
+
* default-export.
|
|
80
|
+
*
|
|
81
|
+
* A module that throws on import is reported and skipped: one unfinished job
|
|
82
|
+
* file must not stop the worker from running every other job.
|
|
83
|
+
*/
|
|
84
|
+
export async function discoverJobs(locations) {
|
|
85
|
+
const found = [];
|
|
86
|
+
for (const location of locations) {
|
|
87
|
+
for (const file of await walk(directoryOf(location))) {
|
|
88
|
+
let module;
|
|
89
|
+
try {
|
|
90
|
+
module = await import(pathToFileURL(path.resolve(file)).href);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
process.stderr.write(`[bay] could not load '${file}': ${err instanceof Error ? err.message : String(err)}\n`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (typeof module !== "object" || module === null)
|
|
97
|
+
continue;
|
|
98
|
+
const exported = Reflect.get(module, "default");
|
|
99
|
+
if (isJobClass(exported))
|
|
100
|
+
found.push(exported);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return found;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=jobs.js.map
|
package/dist/jobs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../src/jobs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading `NODE_ENV`, with the aliases people actually set.
|
|
3
|
+
*
|
|
4
|
+
* `NODE_ENV=prod` is ordinary in a Dockerfile or a platform dashboard. Read
|
|
5
|
+
* verbatim it answers "not production" — and here that decides whether a queue
|
|
6
|
+
* silently accepts a delivery guarantee weaker than the one it advertises.
|
|
7
|
+
*
|
|
8
|
+
* Duplicated rather than imported: bay depends on no other package in this
|
|
9
|
+
* workspace, and a safety decision that only holds when an optional peer is
|
|
10
|
+
* installed is not a decision.
|
|
11
|
+
*/
|
|
12
|
+
/** The canonical name for whatever `NODE_ENV` holds. */
|
|
13
|
+
export declare function normalizeNodeEnv(value: string | undefined): string;
|
|
14
|
+
/** Whether this process is running in production, under any spelling. */
|
|
15
|
+
export declare function inProduction(): boolean;
|
|
16
|
+
//# sourceMappingURL=nodeEnv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeEnv.d.ts","sourceRoot":"","sources":["../src/nodeEnv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOlE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
|
package/dist/nodeEnv.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading `NODE_ENV`, with the aliases people actually set.
|
|
3
|
+
*
|
|
4
|
+
* `NODE_ENV=prod` is ordinary in a Dockerfile or a platform dashboard. Read
|
|
5
|
+
* verbatim it answers "not production" — and here that decides whether a queue
|
|
6
|
+
* silently accepts a delivery guarantee weaker than the one it advertises.
|
|
7
|
+
*
|
|
8
|
+
* Duplicated rather than imported: bay depends on no other package in this
|
|
9
|
+
* workspace, and a safety decision that only holds when an optional peer is
|
|
10
|
+
* installed is not a decision.
|
|
11
|
+
*/
|
|
12
|
+
const DEV_ENVS = ["dev", "develop", "development"];
|
|
13
|
+
const PROD_ENVS = ["prod", "production"];
|
|
14
|
+
const TEST_ENVS = ["test", "testing"];
|
|
15
|
+
/** The canonical name for whatever `NODE_ENV` holds. */
|
|
16
|
+
export function normalizeNodeEnv(value) {
|
|
17
|
+
if (!value || typeof value !== "string")
|
|
18
|
+
return "unknown";
|
|
19
|
+
const env = value.toLowerCase();
|
|
20
|
+
if (DEV_ENVS.includes(env))
|
|
21
|
+
return "development";
|
|
22
|
+
if (PROD_ENVS.includes(env))
|
|
23
|
+
return "production";
|
|
24
|
+
if (TEST_ENVS.includes(env))
|
|
25
|
+
return "test";
|
|
26
|
+
return env;
|
|
27
|
+
}
|
|
28
|
+
/** Whether this process is running in production, under any spelling. */
|
|
29
|
+
export function inProduction() {
|
|
30
|
+
return normalizeNodeEnv(process.env.NODE_ENV) === "production";
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=nodeEnv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeEnv.js","sourceRoot":"","sources":["../src/nodeEnv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AACnD,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACzC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEtC,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAAC,KAAyB;IACzD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,aAAa,CAAC;IACjD,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IACjD,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC3C,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY;IAC3B,OAAO,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,YAAY,CAAC;AAChE,CAAC"}
|
package/dist/quasar.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bay does not depend on quasar: it is an optional peer, and this module
|
|
5
5
|
* never imports it statically. The specifier is built at runtime so the
|
|
6
|
-
* TypeScript build stays free of it too — a hard type import would make
|
|
6
|
+
* TypeScript build stays free of it too — a hard type import would make bay
|
|
7
7
|
* unbuildable for anyone who queues in memory.
|
|
8
8
|
*
|
|
9
9
|
* The shape is checked before use rather than asserted, the same way bay
|
package/dist/quasar.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bay does not depend on quasar: it is an optional peer, and this module
|
|
5
5
|
* never imports it statically. The specifier is built at runtime so the
|
|
6
|
-
* TypeScript build stays free of it too — a hard type import would make
|
|
6
|
+
* TypeScript build stays free of it too — a hard type import would make bay
|
|
7
7
|
* unbuildable for anyone who queues in memory.
|
|
8
8
|
*
|
|
9
9
|
* The shape is checked before use rather than asserted, the same way bay
|
package/dist/services/main.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ import type { QueueManager } from "../QueueManager.js";
|
|
|
17
17
|
export declare function setQueue(value: QueueManager): void;
|
|
18
18
|
/** @internal Read the singleton (or `undefined` pre-boot). */
|
|
19
19
|
export declare function getQueue(): QueueManager | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* @internal Release the singleton, so a shut-down application does not leave a
|
|
22
|
+
* dead queue reachable through `services/main`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function clearQueue(): void;
|
|
20
25
|
declare const queue: QueueManager;
|
|
21
26
|
export default queue;
|
|
22
27
|
//# sourceMappingURL=main.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAElD;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,IAAI,YAAY,GAAG,SAAS,CAEnD;AAED,QAAA,MAAM,KAAK,EAAE,YAoBX,CAAC;AAEH,eAAe,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAElD;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,IAAI,YAAY,GAAG,SAAS,CAEnD;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,QAAA,MAAM,KAAK,EAAE,YAoBX,CAAC;AAEH,eAAe,KAAK,CAAC"}
|
package/dist/services/main.js
CHANGED
|
@@ -21,6 +21,13 @@ export function setQueue(value) {
|
|
|
21
21
|
export function getQueue() {
|
|
22
22
|
return instance;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* @internal Release the singleton, so a shut-down application does not leave a
|
|
26
|
+
* dead queue reachable through `services/main`.
|
|
27
|
+
*/
|
|
28
|
+
export function clearQueue() {
|
|
29
|
+
instance = undefined;
|
|
30
|
+
}
|
|
24
31
|
const queue = new Proxy({}, {
|
|
25
32
|
get(_target, prop) {
|
|
26
33
|
// A module loader inspects what it imports before anyone uses it: it reads
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,IAAI,QAAkC,CAAC;AAEvC,0EAA0E;AAC1E,MAAM,UAAU,QAAQ,CAAC,KAAmB;IAC3C,QAAQ,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ;IACvB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,KAAK,GAAiB,IAAI,KAAK,CAAC,EAAkB,EAAE;IACzD,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,2EAA2E;QAC3E,0EAA0E;QAC1E,8DAA8D;QAC9D,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACjD,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,sEAAsE;gBACrE,4DAA4D,CAC7D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,IAAI,QAAkC,CAAC;AAEvC,0EAA0E;AAC1E,MAAM,UAAU,QAAQ,CAAC,KAAmB;IAC3C,QAAQ,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ;IACvB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACzB,QAAQ,GAAG,SAAS,CAAC;AACtB,CAAC;AAED,MAAM,KAAK,GAAiB,IAAI,KAAK,CAAC,EAAkB,EAAE;IACzD,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,2EAA2E;QAC3E,0EAA0E;QAC1E,8DAA8D;QAC9D,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACjD,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,sEAAsE;gBACrE,4DAA4D,CAC7D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,KAAK,CAAC"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `@c9up/bay/testing` so production code never accidentally pulls
|
|
9
9
|
* the fake into a runtime build.
|
|
10
10
|
*/
|
|
11
|
-
import type {
|
|
11
|
+
import type { JobRecord, QueueDriver } from "../QueueManager.js";
|
|
12
12
|
export interface FakeQueuePredicate {
|
|
13
13
|
/** Custom payload predicate — receives the job's `payload` and
|
|
14
14
|
* returns `true` to match. The job name is always taken from the
|
|
@@ -18,25 +18,31 @@ export interface FakeQueuePredicate {
|
|
|
18
18
|
* matches 'b' but the error message says 'a'). */
|
|
19
19
|
payloadMatches?: (payload: unknown) => boolean;
|
|
20
20
|
}
|
|
21
|
-
export type FakeQueuePredicateArg = FakeQueuePredicate | ((job:
|
|
21
|
+
export type FakeQueuePredicateArg = FakeQueuePredicate | ((job: JobRecord) => boolean);
|
|
22
22
|
export declare class FakeQueue implements QueueDriver {
|
|
23
23
|
#private;
|
|
24
|
-
push(job:
|
|
24
|
+
push(job: JobRecord): Promise<void>;
|
|
25
25
|
/** Always returns `null` — fake queues never auto-dispatch.
|
|
26
26
|
* Tests that need handler execution should use the memory
|
|
27
27
|
* driver directly. */
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Always empty: this fake captures what was dispatched, it does not run it.
|
|
30
|
+
*
|
|
31
|
+
* The parameter is declared so the queue names a caller asks for are part of
|
|
32
|
+
* the signature a test reads, even though nothing here serves them.
|
|
33
|
+
*/
|
|
34
|
+
pop(_queues?: readonly string[]): Promise<JobRecord | null>;
|
|
35
|
+
fail(job: JobRecord, error: string): Promise<void>;
|
|
36
|
+
complete(job: JobRecord): Promise<void>;
|
|
37
|
+
retry(job: JobRecord): Promise<void>;
|
|
38
|
+
failed(): Promise<JobRecord[]>;
|
|
33
39
|
size(): Promise<number>;
|
|
34
40
|
/**
|
|
35
41
|
* Defensive snapshot of every captured job. Each entry is a
|
|
36
42
|
* shallow clone so test-side mutations can't bleed back into the
|
|
37
43
|
* internal capture store — avoids cross-test contamination.
|
|
38
44
|
*/
|
|
39
|
-
getPushed():
|
|
45
|
+
getPushed(): JobRecord[];
|
|
40
46
|
reset(): void;
|
|
41
47
|
assertPushed(name: string, predicate?: FakeQueuePredicateArg): void;
|
|
42
48
|
assertNotPushed(name: string, predicate?: FakeQueuePredicateArg): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FakeQueue.d.ts","sourceRoot":"","sources":["../../src/testing/FakeQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"FakeQueue.d.ts","sourceRoot":"","sources":["../../src/testing/FakeQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IAClC;;;;;uDAKmD;IACnD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;CAC/C;AAED,MAAM,MAAM,qBAAqB,GAC9B,kBAAkB,GAClB,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC;AAEjC,qBAAa,SAAU,YAAW,WAAW;;IAGtC,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzC;;2BAEuB;IACvB;;;;;OAKG;IACG,GAAG,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAI3D,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlD,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvC,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BpC,MAAM,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAM9B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B;;;;OAIG;IACH,SAAS,IAAI,SAAS,EAAE;IAIxB,KAAK,IAAI,IAAI;IAIb,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,qBAAqB,GAAG,IAAI;IAQnE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,qBAAqB,GAAG,IAAI;CAQtE"}
|
|
@@ -22,7 +22,13 @@ export class FakeQueue {
|
|
|
22
22
|
/** Always returns `null` — fake queues never auto-dispatch.
|
|
23
23
|
* Tests that need handler execution should use the memory
|
|
24
24
|
* driver directly. */
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Always empty: this fake captures what was dispatched, it does not run it.
|
|
27
|
+
*
|
|
28
|
+
* The parameter is declared so the queue names a caller asks for are part of
|
|
29
|
+
* the signature a test reads, even though nothing here serves them.
|
|
30
|
+
*/
|
|
31
|
+
async pop(_queues) {
|
|
26
32
|
return null;
|
|
27
33
|
}
|
|
28
34
|
async fail(job, error) {
|
|
@@ -37,7 +43,11 @@ export class FakeQueue {
|
|
|
37
43
|
}
|
|
38
44
|
async retry(job) {
|
|
39
45
|
const found = this.#requireJob(job, "retry");
|
|
40
|
-
|
|
46
|
+
// `attempts` is the WORKER's counter, not the driver's: `processOne`
|
|
47
|
+
// increments it before it calls this, and neither real driver touches it
|
|
48
|
+
// again. Incrementing here counted the same attempt twice, so a fake
|
|
49
|
+
// exhausted `maxAttempts` in half the tries the real queue takes — a
|
|
50
|
+
// test double that disagrees with what it stands in for.
|
|
41
51
|
found.status = "pending";
|
|
42
52
|
// Reset transient state from the prior failure so a retried
|
|
43
53
|
// job's invariants match a fresh push (a real driver would
|
|
@@ -48,7 +58,7 @@ export class FakeQueue {
|
|
|
48
58
|
}
|
|
49
59
|
/** Look up the captured copy of a job by id. Throws when the id
|
|
50
60
|
* isn't present — silent no-op on a missing job is the most
|
|
51
|
-
* insidious test bug (caller's local
|
|
61
|
+
* insidious test bug (caller's local job ref shows the new
|
|
52
62
|
* status while the FakeQueue's internal capture is unchanged). */
|
|
53
63
|
#requireJob(job, verb) {
|
|
54
64
|
const found = this.#pushed.find((j) => j.id === job.id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FakeQueue.js","sourceRoot":"","sources":["../../src/testing/FakeQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkBH,MAAM,OAAO,SAAS;IACrB,OAAO,
|
|
1
|
+
{"version":3,"file":"FakeQueue.js","sourceRoot":"","sources":["../../src/testing/FakeQueue.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/bay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Queue — pluggable job-queue contract with memory + Redis drivers for the Ream framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"types": "./dist/BayProvider.d.ts",
|
|
16
16
|
"import": "./dist/BayProvider.js"
|
|
17
17
|
},
|
|
18
|
+
"./configure": {
|
|
19
|
+
"types": "./dist/configure.d.ts",
|
|
20
|
+
"import": "./dist/configure.js"
|
|
21
|
+
},
|
|
18
22
|
"./services/main": {
|
|
19
23
|
"types": "./dist/services/main.d.ts",
|
|
20
24
|
"import": "./dist/services/main.js"
|
|
@@ -32,8 +36,10 @@
|
|
|
32
36
|
"@biomejs/biome": "^2.4.10",
|
|
33
37
|
"@c9up/quasar": "^0.1.0",
|
|
34
38
|
"@types/node": "^22.19.15",
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
35
40
|
"typescript": "^6.0.2",
|
|
36
|
-
"vitest": "^4.1.2"
|
|
41
|
+
"vitest": "^4.1.2",
|
|
42
|
+
"@c9up/ream": "^0.2.0"
|
|
37
43
|
},
|
|
38
44
|
"engines": {
|
|
39
45
|
"node": ">=22.0.0"
|
|
@@ -62,7 +68,7 @@
|
|
|
62
68
|
"scripts": {
|
|
63
69
|
"build": "tsc -p tsconfig.build.json",
|
|
64
70
|
"test": "vitest run",
|
|
65
|
-
"lint": "biome check src/",
|
|
71
|
+
"lint": "biome check src/ tests/",
|
|
66
72
|
"test:coverage": "vitest run --coverage",
|
|
67
73
|
"typecheck": "tsc --noEmit"
|
|
68
74
|
}
|
package/src/BayProvider.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import type { AdapterFactory } from "./adapters.js";
|
|
2
|
+
import "./augmentations.js";
|
|
1
3
|
import { MemoryDriver } from "./drivers/MemoryDriver.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_JOBS_DIR,
|
|
6
|
+
directoryOf,
|
|
7
|
+
discoverJobs,
|
|
8
|
+
setJobsDir,
|
|
9
|
+
} from "./jobs.js";
|
|
10
|
+
import type { QueueDriver } from "./QueueManager.js";
|
|
11
|
+
import { QueueManager, type WorkerOptions } from "./QueueManager.js";
|
|
12
|
+
import { clearQueue, getQueue, setQueue } from "./services/main.js";
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
15
|
* Slim, duck-typed host context — bay stays publishable without
|
|
@@ -8,11 +17,16 @@ import { setQueue } from "./services/main.js";
|
|
|
8
17
|
* `singleton(token, factory)` + `resolve(token)` and a config store
|
|
9
18
|
* with `get(key)` satisfies the contract.
|
|
10
19
|
*/
|
|
11
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The slice of a host container bay uses. Exported because `BayAppContext` is:
|
|
22
|
+
* a host — or a test — cannot describe one without being able to name its
|
|
23
|
+
* members, and the two that could not were reached for with `as any` instead.
|
|
24
|
+
*/
|
|
25
|
+
export interface BayContainer {
|
|
12
26
|
singleton(token: unknown, factory: () => unknown): void;
|
|
13
27
|
resolve<T = unknown>(token: unknown): Promise<T>;
|
|
14
28
|
}
|
|
15
|
-
interface BayConfigStore {
|
|
29
|
+
export interface BayConfigStore {
|
|
16
30
|
get<T = unknown>(key: string): T | undefined;
|
|
17
31
|
}
|
|
18
32
|
export interface BayAppContext {
|
|
@@ -22,16 +36,45 @@ export interface BayAppContext {
|
|
|
22
36
|
|
|
23
37
|
export interface BayProviderConfig {
|
|
24
38
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
39
|
+
* Which named adapter to use — a key of {@link adapters}. Read from the
|
|
40
|
+
* environment in the generated config, so a deployment picks its queue
|
|
41
|
+
* backend without editing a file.
|
|
42
|
+
*/
|
|
43
|
+
default?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The queue adapters this application can use, by name. Each is a factory
|
|
46
|
+
* from `drivers.*`, built only when it is the one selected.
|
|
47
|
+
*/
|
|
48
|
+
adapters?: Record<string, AdapterFactory>;
|
|
49
|
+
/**
|
|
50
|
+
* The name this key had before it matched upstream's. Read when `adapters`
|
|
51
|
+
* is absent, so a config written against the older spelling keeps selecting
|
|
52
|
+
* the backend it named — silently falling back to an in-process queue is the
|
|
53
|
+
* one outcome `buildDriver` exists to prevent.
|
|
54
|
+
*/
|
|
55
|
+
stores?: Record<string, AdapterFactory>;
|
|
56
|
+
/**
|
|
57
|
+
* The single-adapter form, kept for configs written against it: only
|
|
58
|
+
* `"memory"` was ever accepted. Prefer `default` + `adapters`, which is how
|
|
59
|
+
* a pluggable backend is configured everywhere else and what lets the
|
|
60
|
+
* environment choose.
|
|
33
61
|
*/
|
|
34
62
|
driver?: "memory";
|
|
63
|
+
/**
|
|
64
|
+
* Defaults for the worker `queue.work()` starts — upstream's `worker` block,
|
|
65
|
+
* by the names it gives them. An argument to `work()` still wins.
|
|
66
|
+
*/
|
|
67
|
+
worker?: WorkerOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Where the application's job classes live. Default `['app/jobs']`.
|
|
70
|
+
*
|
|
71
|
+
* Every module under them is imported at boot and a default export that is
|
|
72
|
+
* a job class is registered under its own name — which is what lets a
|
|
73
|
+
* worker process resolve a record queued by an HTTP one. Without it the
|
|
74
|
+
* registration list is a directory kept in step by hand, and the job
|
|
75
|
+
* nobody added to it fails as "no handler registered".
|
|
76
|
+
*/
|
|
77
|
+
locations?: readonly string[];
|
|
35
78
|
}
|
|
36
79
|
|
|
37
80
|
/**
|
|
@@ -56,31 +99,106 @@ export interface BayProviderConfig {
|
|
|
56
99
|
* queue.register('send-email', new SendEmailJob())
|
|
57
100
|
* await queue.dispatch('send-email', { to: 'user@example.com' })
|
|
58
101
|
*/
|
|
102
|
+
/**
|
|
103
|
+
* The driver the config asks for.
|
|
104
|
+
*
|
|
105
|
+
* `default` + `stores` first — the form an environment variable can steer. The
|
|
106
|
+
* `driver` key is the single-store form kept for configs written against it.
|
|
107
|
+
* Naming a store that does not exist throws rather than falling back to memory:
|
|
108
|
+
* an application that meant to queue in Redis and silently got an in-process
|
|
109
|
+
* queue would only find out when a restart dropped every pending job.
|
|
110
|
+
*/
|
|
111
|
+
function buildDriver(config: BayProviderConfig | undefined): QueueDriver {
|
|
112
|
+
// `adapters` first, `stores` when it is absent: the key was renamed to the
|
|
113
|
+
// one upstream reads, and a config that still says `stores` must keep
|
|
114
|
+
// selecting its backend rather than quietly landing on memory.
|
|
115
|
+
const adapters = config?.adapters ?? config?.stores;
|
|
116
|
+
const key = config?.adapters ? "adapters" : "stores";
|
|
117
|
+
const name = config?.default;
|
|
118
|
+
|
|
119
|
+
if (adapters && name !== undefined) {
|
|
120
|
+
const selected = adapters[name];
|
|
121
|
+
if (!selected) {
|
|
122
|
+
const known = Object.keys(adapters);
|
|
123
|
+
throw new Error(
|
|
124
|
+
`[bay] config.queue names the adapter '${name}', which is not in \`${key}\`. ` +
|
|
125
|
+
(known.length > 0
|
|
126
|
+
? `Declared: ${known.join(", ")}.`
|
|
127
|
+
: `\`${key}\` is empty — declare one with drivers.memory() or drivers.redis().`),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return selected();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (adapters && name === undefined) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`[bay] config.queue declares \`${key}\` but no \`default\` naming which one to use. ` +
|
|
136
|
+
`Set default to one of: ${Object.keys(adapters).join(", ")}.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const driverName = config?.driver ?? "memory";
|
|
141
|
+
if (driverName !== "memory") {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`[bay] Unsupported driver '${driverName}' — name it under \`adapters\` instead: ` +
|
|
144
|
+
"adapters: { redis: drivers.redis({ connection: 'main' }) }.",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return new MemoryDriver();
|
|
148
|
+
}
|
|
149
|
+
|
|
59
150
|
export default class BayProvider {
|
|
60
151
|
constructor(protected app: BayAppContext) {}
|
|
61
152
|
|
|
62
153
|
register(): void {
|
|
63
154
|
this.app.container.singleton(QueueManager, () => {
|
|
64
155
|
const config = this.app.config.get<BayProviderConfig>("queue");
|
|
65
|
-
|
|
66
|
-
if (driverName !== "memory") {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`[bay] Unsupported driver '${driverName}' for default provider — ` +
|
|
69
|
-
"wire QueueManager yourself in start/queue.ts for non-memory drivers.",
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return new QueueManager(new MemoryDriver());
|
|
156
|
+
return new QueueManager(buildDriver(config), config?.worker);
|
|
73
157
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
158
|
+
// Namespaced by the package that owns it, the way upstream namespaces
|
|
159
|
+
// `lucid.db`, `auth.manager` and `drive.manager` by theirs. The bare
|
|
160
|
+
// token stays bound beside it: it is what every existing
|
|
161
|
+
// `container.make(...)` asks for, and a token is not worth breaking an
|
|
162
|
+
// application over.
|
|
163
|
+
const queue = (): Promise<QueueManager> =>
|
|
164
|
+
this.app.container.resolve<QueueManager>(QueueManager);
|
|
165
|
+
this.app.container.singleton("bay.queue", queue);
|
|
166
|
+
this.app.container.singleton("queue", queue);
|
|
77
167
|
}
|
|
78
168
|
|
|
169
|
+
/** The queue THIS provider booted — not whatever the module singleton holds. */
|
|
170
|
+
#queue: QueueManager | undefined;
|
|
171
|
+
|
|
79
172
|
async boot(): Promise<void> {
|
|
80
173
|
// Populate the `@c9up/bay/services/main` singleton so apps can
|
|
81
174
|
// `import queue from '@c9up/bay/services/main'` from anywhere.
|
|
82
|
-
|
|
175
|
+
this.#queue = await this.app.container.resolve<QueueManager>(QueueManager);
|
|
176
|
+
setQueue(this.#queue);
|
|
177
|
+
|
|
178
|
+
const config = this.app.config.get<BayProviderConfig>("queue");
|
|
179
|
+
const locations = config?.locations ?? [DEFAULT_JOBS_DIR];
|
|
180
|
+
// `make:job` writes where discovery reads, so the two cannot drift.
|
|
181
|
+
const first = locations[0];
|
|
182
|
+
if (first !== undefined) setJobsDir(directoryOf(first));
|
|
183
|
+
for (const job of await discoverJobs(locations)) {
|
|
184
|
+
this.#queue.registerJob(job);
|
|
185
|
+
}
|
|
83
186
|
}
|
|
84
187
|
|
|
85
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Stop the worker the app started, and let the job in flight finish.
|
|
190
|
+
*
|
|
191
|
+
* A worker polls on a timer. Left running, it survives a dev reload, a test
|
|
192
|
+
* teardown and a SIGTERM — so the old process keeps pulling jobs the new
|
|
193
|
+
* one is also pulling, and the same job runs twice.
|
|
194
|
+
*/
|
|
195
|
+
async shutdown(): Promise<void> {
|
|
196
|
+
if (!this.#queue) return;
|
|
197
|
+
await this.#queue.stop();
|
|
198
|
+
// Two applications can share a process — parallel tests, a hot reload.
|
|
199
|
+
// The module singleton holds whichever booted last, so it is only ours
|
|
200
|
+
// to clear while it still points at the queue this provider booted.
|
|
201
|
+
if (getQueue() === this.#queue) clearQueue();
|
|
202
|
+
this.#queue = undefined;
|
|
203
|
+
}
|
|
86
204
|
}
|