From 8c43407dba9c4c4782f22912f5aea1f0987a5777 Mon Sep 17 00:00:00 2001 From: Skyler Blue Spillers <92972770+skylerblue333@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:12:59 -0500 Subject: [PATCH 1/6] test: use dependency-free Node test runner --- src/queue.test.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/queue.test.ts b/src/queue.test.ts index bbf4f6b..2c14567 100644 --- a/src/queue.test.ts +++ b/src/queue.test.ts @@ -1,13 +1,17 @@ -import { createEnvelope } from './queue'; +import assert from "node:assert/strict"; +import test from "node:test"; +import { createEnvelope } from "./queue.js"; -describe('message queue framework', () => { - it('creates deterministic message envelopes when an id is supplied', () => { - const message = createEnvelope('payment.completed', { amount: 25 }, 'evt-1'); - expect(message).toEqual(expect.objectContaining({ id: 'evt-1', type: 'payment.completed', payload: { amount: 25 } })); - expect(Date.parse(message.createdAt)).not.toBeNaN(); - }); +test("creates deterministic message envelopes when an id is supplied", () => { + const message = createEnvelope("payment.completed", { amount: 25 }, "evt-1"); + assert.equal(message.id, "evt-1"); + assert.equal(message.type, "payment.completed"); + assert.deepEqual(message.payload, { amount: 25 }); + assert.equal(Number.isNaN(Date.parse(message.createdAt)), false); +}); - it('rejects empty message types', () => { - expect(() => createEnvelope('', {})).toThrow('job type is required'); - }); +test("trims job types and rejects blank message types", () => { + const message = createEnvelope(" feed.index ", {}, "evt-2"); + assert.equal(message.type, "feed.index"); + assert.throws(() => createEnvelope(" ", {}), /job type is required/); }); From 1f469f8bfa0ea7d36ea5d1bccf92b20fb81b9bfb Mon Sep 17 00:00:00 2001 From: Skyler Blue Spillers <92972770+skylerblue333@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:13:07 -0500 Subject: [PATCH 2/6] build: make TypeScript queue tests executable --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ddf2238..3e34844 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "skycoin-message-queue", - "version": "1.0.0", + "version": "1.1.0", "private": true, "type": "module", "main": "dist/index.js", "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit", - "test": "node --test test/*.test.mjs" + "test": "npm run build && node --test dist/queue.test.js" }, "dependencies": { "bullmq": "^5.0.0", From 234efbfb189c80cbb2c718002281e4d0e5df40b8 Mon Sep 17 00:00:00 2001 From: Skyler Blue Spillers <92972770+skylerblue333@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:13:13 -0500 Subject: [PATCH 3/6] ci: enforce TypeScript queue tests and dependency audit --- .github/workflows/ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82a8105..cff6f13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: queue-ci on: push: @@ -9,7 +9,7 @@ permissions: jobs: verify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -17,8 +17,10 @@ jobs: node-version: 22 cache: npm - name: Install dependencies - run: npm install + run: npm install --ignore-scripts - name: Typecheck run: npm run typecheck - - name: Build - run: npm run build + - name: Tests + run: npm test + - name: Production dependency audit + run: npm audit --omit=dev --audit-level=high From 9f9750f6acfd7a7f6024e27550752865bc24c966 Mon Sep 17 00:00:00 2001 From: Skyler Blue Spillers <92972770+skylerblue333@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:17:32 -0500 Subject: [PATCH 4/6] fix: remove npm cache dependency on absent lockfile --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cff6f13..2315dde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 22 - cache: npm - name: Install dependencies run: npm install --ignore-scripts - name: Typecheck From a745713b5ce8742adfcccb1bfa3548e76c668214 Mon Sep 17 00:00:00 2001 From: Skyler Blue Spillers <92972770+skylerblue333@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:21:22 -0500 Subject: [PATCH 5/6] fix: use constructable Redis export --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 96c5596..aa486dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { Queue } from "bullmq"; -import IORedis from "ioredis"; +import { Redis } from "ioredis"; export interface MessageEnvelope { id: string; @@ -9,7 +9,7 @@ export interface MessageEnvelope { } const redisUrl = process.env.REDIS_URL ?? "redis://localhost:6379"; -const connection = new IORedis(redisUrl, { maxRetriesPerRequest: null }); +const connection = new Redis(redisUrl, { maxRetriesPerRequest: null }); export const queue = new Queue("skycoin-events", { connection, From 0d0654cbd73cefb05b588bb149d22be2f6a61fa3 Mon Sep 17 00:00:00 2001 From: Skyler Blue Spillers <92972770+skylerblue333@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:21:42 -0500 Subject: [PATCH 6/6] fix: align Redis and envelope types --- src/queue.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/queue.ts b/src/queue.ts index 837145b..9fbf2f5 100644 --- a/src/queue.ts +++ b/src/queue.ts @@ -1,17 +1,17 @@ import { Queue, Worker, type Job, type JobsOptions } from "bullmq"; -import IORedis from "ioredis"; +import { Redis } from "ioredis"; import { randomUUID } from "node:crypto"; export type SkycoinJob> = { id: string; type: string; payload: T; createdAt: string }; -export function createEnvelope(type: string, payload: T, id = randomUUID()): SkycoinJob { +export function createEnvelope(type: string, payload: T, id: string = randomUUID()): SkycoinJob { if (!type.trim()) throw new Error("job type is required"); return { id, type: type.trim(), payload, createdAt: new Date().toISOString() }; } export function createQueue>(name = process.env.QUEUE_NAME ?? "skycoin-events") { if (!name.trim()) throw new Error("queue name is required"); - const connection = new IORedis(process.env.REDIS_URL ?? "redis://localhost:6379", { maxRetriesPerRequest: null }); + const connection = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379", { maxRetriesPerRequest: null }); return { queue: new Queue>(name, { connection }), connection }; } @@ -21,6 +21,6 @@ export async function publish(queue: Queue>, message: SkycoinJo export function startWorker>(name: string, handler: (job: Job>) => Promise) { if (!name.trim()) throw new Error("queue name is required"); - const connection = new IORedis(process.env.REDIS_URL ?? "redis://localhost:6379", { maxRetriesPerRequest: null }); + const connection = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379", { maxRetriesPerRequest: null }); return new Worker>(name, handler, { connection, concurrency: Math.max(1, Number(process.env.WORKER_CONCURRENCY ?? 10)) }); }