@manablox/jobs 0.2.0 → 0.3.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.
@@ -0,0 +1,41 @@
1
+ import { Database, Repositories } from "@manablox/db";
2
+ import { JobsOptions } from "bullmq";
3
+ import { Manablox } from "@manablox/core";
4
+ //#region src/index.d.ts
5
+ export interface JobPayloads {
6
+ 'webhook:deliver': {
7
+ webhookId: string;
8
+ event: string;
9
+ payload: Record<string, unknown>;
10
+ };
11
+ 'media:derive': {
12
+ assetId: string;
13
+ preset: string;
14
+ format: string;
15
+ };
16
+ 'content:scheduledPublish': {
17
+ contentId: string;
18
+ };
19
+ 'workflow:run': {
20
+ runId: string;
21
+ };
22
+ 'maintenance:pruneApiKeys': Record<string, never>;
23
+ }
24
+ export type JobName = keyof JobPayloads;
25
+ export interface JobRunner {
26
+ enqueue<K extends JobName>(name: K, payload: JobPayloads[K], options?: JobsOptions): Promise<void>;
27
+ close(): Promise<void>;
28
+ /** True when there is no queue and `enqueue` runs the job before returning. */
29
+ inline: boolean;
30
+ }
31
+ export interface JobHandlers {
32
+ derive?: (assetId: string, preset: string, format: string) => Promise<void>;
33
+ publish?: (contentId: string) => Promise<void>;
34
+ /** Executes a queued workflow run; set by the host once the engine exists. */
35
+ workflowRun?: (runId: string) => Promise<void>;
36
+ /** Sends one webhook delivery; set by the host once the webhook service exists. */
37
+ webhookDeliver?: (data: JobPayloads['webhook:deliver']) => Promise<void>;
38
+ }
39
+ /** Background work: webhook delivery with retries, image derivatives, scheduled publish. */
40
+ export declare function createJobRunner(manablox: Manablox, db: Database, repos: Repositories, handlers?: JobHandlers): JobRunner;
41
+ //#endregion
package/dist/index.js ADDED
@@ -0,0 +1,82 @@
1
+ import { schema } from "@manablox/db";
2
+ import { Queue, Worker } from "bullmq";
3
+ import { sql } from "drizzle-orm";
4
+ import { Redis } from "ioredis";
5
+ //#region src/index.ts
6
+ /** Background work: webhook delivery with retries, image derivatives, scheduled publish. */
7
+ function createJobRunner(manablox, db, repos, handlers = {}) {
8
+ const redisUrl = manablox.config.cache.redisUrl;
9
+ if (!redisUrl) return {
10
+ inline: true,
11
+ async enqueue(name, payload) {
12
+ await runJob(name, payload, db, repos, handlers, manablox);
13
+ },
14
+ async close() {}
15
+ };
16
+ const connection = new Redis(redisUrl, { maxRetriesPerRequest: null });
17
+ const queue = new Queue("manablox", { connection });
18
+ const worker = new Worker("manablox", async (job) => {
19
+ await runJob(job.name, job.data, db, repos, handlers, manablox);
20
+ }, {
21
+ connection,
22
+ concurrency: 8,
23
+ autorun: true
24
+ });
25
+ worker.on("failed", (job, error) => {
26
+ manablox.logger.warn({
27
+ job: job?.name,
28
+ err: error
29
+ }, "job failed");
30
+ });
31
+ manablox.onDispose(async () => {
32
+ await worker.close();
33
+ await queue.close();
34
+ connection.disconnect();
35
+ });
36
+ return {
37
+ inline: false,
38
+ async enqueue(name, payload, options) {
39
+ await queue.add(name, payload, {
40
+ attempts: 5,
41
+ backoff: {
42
+ type: "exponential",
43
+ delay: 2e3
44
+ },
45
+ removeOnComplete: 500,
46
+ removeOnFail: 1e3,
47
+ ...options
48
+ });
49
+ },
50
+ async close() {
51
+ await worker.close();
52
+ await queue.close();
53
+ }
54
+ };
55
+ }
56
+ async function runJob(name, payload, db, repos, handlers, manablox) {
57
+ switch (name) {
58
+ case "webhook:deliver": {
59
+ const data = payload;
60
+ await handlers.webhookDeliver?.(data);
61
+ break;
62
+ }
63
+ case "media:derive": {
64
+ const data = payload;
65
+ await handlers.derive?.(data.assetId, data.preset, data.format);
66
+ break;
67
+ }
68
+ case "content:scheduledPublish": {
69
+ const data = payload;
70
+ await handlers.publish?.(data.contentId);
71
+ break;
72
+ }
73
+ case "workflow:run": {
74
+ const data = payload;
75
+ await handlers.workflowRun?.(data.runId);
76
+ break;
77
+ }
78
+ case "maintenance:pruneApiKeys": await db.delete(schema.apikeys).where(sql`${schema.apikeys.expiresAt} is not null and ${schema.apikeys.expiresAt} < now()`);
79
+ }
80
+ }
81
+ //#endregion
82
+ export { createJobRunner };
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@manablox/jobs",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
7
- "types": "./src/index.ts",
8
- "default": "./src/index.ts"
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
9
  }
10
10
  },
11
- "main": "./src/index.ts",
12
- "types": "./src/index.ts",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
13
  "dependencies": {
14
- "@manablox/core": "0.2.0",
15
- "@manablox/db": "0.2.0",
14
+ "@manablox/core": "0.3.0",
15
+ "@manablox/db": "0.3.0",
16
16
  "bullmq": "^6.3.4",
17
17
  "ioredis": "^6.0.0",
18
18
  "drizzle-orm": "^0.45.2"
@@ -20,9 +20,16 @@
20
20
  "devDependencies": {
21
21
  "@manablox/config-typescript": "0.0.0",
22
22
  "@types/node": "^26.4.1",
23
+ "tsdown": "^0.23.0",
23
24
  "typescript": "^7.0.2"
24
25
  },
26
+ "files": [
27
+ "dist",
28
+ "!dist/**/*.map",
29
+ "README.md"
30
+ ],
25
31
  "scripts": {
32
+ "build": "tsdown",
26
33
  "typecheck": "tsc --noEmit"
27
34
  }
28
35
  }
package/src/index.ts DELETED
@@ -1,143 +0,0 @@
1
- import type { Manablox } from '@manablox/core';
2
- import { type Database, type Repositories, schema } from '@manablox/db';
3
- import { type JobsOptions, Queue, Worker } from 'bullmq';
4
- import { sql } from 'drizzle-orm';
5
- import { Redis } from 'ioredis';
6
-
7
- export interface JobPayloads {
8
- 'webhook:deliver': { webhookId: string; event: string; payload: Record<string, unknown> };
9
- 'media:derive': { assetId: string; preset: string; format: string };
10
- 'content:scheduledPublish': { contentId: string };
11
- 'workflow:run': { runId: string };
12
- 'maintenance:pruneApiKeys': Record<string, never>;
13
- }
14
-
15
- export type JobName = keyof JobPayloads;
16
-
17
- export interface JobRunner {
18
- enqueue<K extends JobName>(
19
- name: K,
20
- payload: JobPayloads[K],
21
- options?: JobsOptions,
22
- ): Promise<void>;
23
- close(): Promise<void>;
24
- /** True when there is no queue and `enqueue` runs the job before returning. */
25
- inline: boolean;
26
- }
27
-
28
- export interface JobHandlers {
29
- derive?: (assetId: string, preset: string, format: string) => Promise<void>;
30
- publish?: (contentId: string) => Promise<void>;
31
- /** Executes a queued workflow run; set by the host once the engine exists. */
32
- workflowRun?: (runId: string) => Promise<void>;
33
- /** Sends one webhook delivery; set by the host once the webhook service exists. */
34
- webhookDeliver?: (data: JobPayloads['webhook:deliver']) => Promise<void>;
35
- }
36
-
37
- /** Background work: webhook delivery with retries, image derivatives, scheduled publish. */
38
- export function createJobRunner(
39
- manablox: Manablox,
40
- db: Database,
41
- repos: Repositories,
42
- handlers: JobHandlers = {},
43
- ): JobRunner {
44
- const redisUrl = manablox.config.cache.redisUrl;
45
-
46
- // Without Redis, jobs run inline. Correct for a single-node install, and it keeps the
47
- // call sites identical either way.
48
- if (!redisUrl) {
49
- return {
50
- inline: true,
51
- async enqueue(name, payload) {
52
- await runJob(name, payload as never, db, repos, handlers, manablox);
53
- },
54
- async close() {},
55
- };
56
- }
57
-
58
- const connection = new Redis(redisUrl, { maxRetriesPerRequest: null });
59
- const queue = new Queue('manablox', { connection });
60
-
61
- const worker = new Worker(
62
- 'manablox',
63
- async (job) => {
64
- await runJob(job.name as JobName, job.data, db, repos, handlers, manablox);
65
- },
66
- {
67
- connection,
68
- concurrency: 8,
69
- // Exponential backoff so a webhook endpoint that is briefly down is retried
70
- // rather than dropped, and a permanently dead one stops being hammered.
71
- autorun: true,
72
- },
73
- );
74
-
75
- worker.on('failed', (job, error) => {
76
- manablox.logger.warn({ job: job?.name, err: error }, 'job failed');
77
- });
78
-
79
- manablox.onDispose(async () => {
80
- await worker.close();
81
- await queue.close();
82
- connection.disconnect();
83
- });
84
-
85
- return {
86
- inline: false,
87
- async enqueue(name, payload, options) {
88
- await queue.add(name, payload, {
89
- attempts: 5,
90
- backoff: { type: 'exponential', delay: 2000 },
91
- removeOnComplete: 500,
92
- removeOnFail: 1000,
93
- ...options,
94
- });
95
- },
96
- async close() {
97
- await worker.close();
98
- await queue.close();
99
- },
100
- };
101
- }
102
-
103
- async function runJob(
104
- name: JobName,
105
- payload: JobPayloads[JobName],
106
- db: Database,
107
- repos: Repositories,
108
- handlers: JobHandlers,
109
- manablox: Manablox,
110
- ): Promise<void> {
111
- switch (name) {
112
- case 'webhook:deliver': {
113
- const data = payload as JobPayloads['webhook:deliver'];
114
- await handlers.webhookDeliver?.(data);
115
- break;
116
- }
117
- case 'media:derive': {
118
- const data = payload as JobPayloads['media:derive'];
119
- await handlers.derive?.(data.assetId, data.preset, data.format);
120
- break;
121
- }
122
- case 'content:scheduledPublish': {
123
- const data = payload as JobPayloads['content:scheduledPublish'];
124
- await handlers.publish?.(data.contentId);
125
- break;
126
- }
127
- case 'workflow:run': {
128
- const data = payload as JobPayloads['workflow:run'];
129
- await handlers.workflowRun?.(data.runId);
130
- break;
131
- }
132
- case 'maintenance:pruneApiKeys': {
133
- await db
134
- .delete(schema.apikeys)
135
- .where(
136
- sql`${schema.apikeys.expiresAt} is not null and ${schema.apikeys.expiresAt} < now()`,
137
- );
138
- break;
139
- }
140
- }
141
- void repos;
142
- void manablox;
143
- }
package/tsconfig.json DELETED
@@ -1 +0,0 @@
1
- { "extends": "@manablox/config-typescript/library.json", "include": ["src"] }