Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
bun-version: 1.4.4
- run: bun install
- run: bun run lint
- run: python3 test/police-source-discovery.test.py
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# One image, one Railway service. Web and workers are the same code; ROLES picks
# which of them this container runs.
FROM oven/bun:1.4.0-slim AS base
FROM oven/bun:1.4.4-slim AS base
WORKDIR /app

FROM base AS deps
Expand Down
14 changes: 7 additions & 7 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build:client": "bun build-client.js"
},
"dependencies": {
"@logicsrc/openaccess": "^0.3.0",
"@logicsrc/openaccess": "^0.3.1",
"@nichedb/adapters": "workspace:*",
"@nichedb/auth": "workspace:*",
"@nichedb/config": "workspace:*",
Expand All @@ -22,15 +22,15 @@
"@nichedb/premium": "workspace:*",
"@nichedb/queue": "workspace:*",
"@profullstack/footer": "latest",
"@profullstack/leaderboard": "^0.3.0",
"@profullstack/leaderboard": "^0.3.1",
"@profullstack/nichedb": "workspace:*",
"@profullstack/notifications": "^0.1.0",
"@profullstack/notifications": "^0.1.4",
"@profullstack/openprofile": "^0.2.0",
"@profullstack/partners": "0.2.0",
"@profullstack/profile": "0.1.2",
"@profullstack/throttle": "^0.3.3",
"@profullstack/profile": "0.1.6",
"@profullstack/throttle": "^0.3.4",
"@profullstack/x402-gateway": "0.13.1",
"@simplewebauthn/browser": "^13.2.0",
"hono": "^4.10.3"
"@simplewebauthn/browser": "^14.0.0",
"hono": "^4.13.14"
}
}
16 changes: 13 additions & 3 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.12/schema.json",
"files": { "includes": ["**", "!**/node_modules", "!**/public/vendor-*.js", "!**/fixtures"] },
"$schema": "https://biomejs.dev/schemas/2.6.3/schema.json",
"files": {
"includes": [
"**",
"!**/node_modules",
"!**/public/vendor-*.js",
"!**/fixtures",
"!**/*.md",
"!**/*.yml",
"!**/*.yaml"
]
},
"formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 100 },
"javascript": { "formatter": { "quoteStyle": "single", "semicolons": "always" } },
"linter": { "enabled": true, "rules": { "recommended": true } }
"linter": { "enabled": true, "rules": { "preset": "none" } }
}
292 changes: 152 additions & 140 deletions bun.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@
"engines": {
"bun": ">=1.3.0"
},
"packageManager": "bun@1.4.0",
"packageManager": "bun@1.4.4",
"devDependencies": {
"@biomejs/biome": "^2.3.0",
"@electric-sql/pglite": "^0.5.5",
"@biomejs/biome": "^2.6.3",
"@electric-sql/pglite": "^0.5.11",
"@profullstack/favicon-generator": "^1.2.1",
"@profullstack/svgcrusher": "file:vendor/profullstack-svgcrusher-0.1.0.tgz"
},
"overrides": {
"sharp": "^0.35.5",
"tmp": "^0.2.6"
}
}
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"@nichedb/config": "workspace:*",
"@nichedb/db": "workspace:*",
"@profullstack/api-key-manager": "^0.3.0",
"@simplewebauthn/server": "^13.2.0"
"@simplewebauthn/server": "^14.0.3"
}
}
2 changes: 1 addition & 1 deletion packages/notify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"@nichedb/config": "workspace:*",
"@nichedb/db": "workspace:*",
"@profullstack/emailer": "^1.0.3",
"@profullstack/notifications": "^0.1.0"
"@profullstack/notifications": "^0.1.4"
}
}
2 changes: 1 addition & 1 deletion packages/payments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"./referrals": "./src/referrals.js"
},
"dependencies": {
"@profullstack/coinpay": "^0.8.0",
"@profullstack/coinpay": "^0.25.0",
"@profullstack/referrals": "^0.1.0"
}
}
4 changes: 2 additions & 2 deletions packages/queue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@nichedb/db": "workspace:*",
"@nichedb/notify": "workspace:*",
"@profullstack/autoblog": "^0.4.0",
"bullmq": "^5.63.0",
"ioredis": "^5.9.0"
"bullmq": "^6.4.2",
"ioredis": "^6.0.0"
}
}
52 changes: 52 additions & 0 deletions packages/queue/src/install-schedules.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, test } from 'bun:test';

/**
* installSchedules runs on every boot of the web process and the worker. In
* 2026-10 a BullMQ major removed the repeatable-job API it called, and because
* neither the build nor the unit tests run startup code, the container
* crash-looped in production. This drives the real function against a real
* Redis, twice, the way two boots would.
*
* It runs in a child process: index.js opens its connection from REDIS_URL at
* import, and config may already be loaded in this test process.
*/
const redisUrl = process.env.REDIS_TEST_URL;

const script = `
const q = await import(${JSON.stringify(new URL('./index.js', import.meta.url).href)});
await q.installSchedules({ log: () => {} });
await q.installSchedules({ log: () => {} });
const out = {};
for (const [name, queue] of Object.entries(q.queues)) {
const s = await queue.getJobSchedulers();
if (s.length) out[name] = s.map((x) => x.id ?? x.key).sort();
}
out.expected = q.scheduleSpecs().map(([, id]) => id).sort();
console.log(JSON.stringify(out));
await q.closeQueues();
`;

describe.skipIf(!redisUrl)('installSchedules with Redis', () => {
test('boots twice and leaves exactly one scheduler per spec', async () => {
const proc = Bun.spawn([process.execPath, '-e', script], {
// config requires DATABASE_URL at import; nothing here connects to it.
env: {
DATABASE_URL: 'postgres://unused@127.0.0.1:1/unused',
...process.env,
REDIS_URL: redisUrl,
},
stdout: 'pipe',
stderr: 'pipe',
});
const [stdout, stderr, code] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(stderr).not.toContain('is not a function');
expect(code, stderr).toBe(0);
const out = JSON.parse(stdout.trim().split('\n').at(-1));
const { expected, ...installed } = out;
expect(Object.values(installed).flat().sort()).toEqual(expected);
}, 30_000);
});
12 changes: 10 additions & 2 deletions packages/queue/src/retention.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,16 @@ export async function pruneQueues(
return report;
}

/**
* The raw Redis client behind a queue. BullMQ 6 moved it from `queue.client` to
* the queue's backend (`queue.getBackend().client`); the fallback keeps 5 working.
*/
function redisClient(queue) {
return queue.getBackend ? queue.getBackend().client : queue.client;
}

async function pruneQueue(queue, { maxLen, maxBytes, step, pauseMs }) {
const client = await queue.client;
const client = await redisClient(queue);
const key = queue.keys.events;
// Clean first: each clean call itself appends a `cleaned` event to the stream.
const completed = await cleanInSteps(queue, KEEP_COMPLETED.age * 1000, 'completed', {
Expand Down Expand Up @@ -122,7 +130,7 @@ async function cleanInSteps(queue, graceMs, type, { step, pauseMs }) {
// Ask first: a clean call appends a `cleaned` event even when it removes
// nothing, so calling it unconditionally would grow the stream on every boot.
// Finished sets are scored by finishedOn, the same clock clean() goes by.
const client = await queue.client;
const client = await redisClient(queue);
const due = Number(await client.zcount(queue.keys[type], '-inf', Date.now() - graceMs));
if (!due) return 0;
let total = 0;
Expand Down
Loading