Loading src/codec.test.ts +23 −25 Original line number Diff line number Diff line import { assertEquals } from "@std/assert/assert-equals"; import { assertThrows } from "@std/assert/assert-throws"; import { DecodingError, EncodingError, JsonCodec } from "@fedify/redis/codec"; import assert from "node:assert/strict"; import { Buffer } from "node:buffer"; import { DecodingError, EncodingError, JsonCodec } from "./codec.ts"; import { test } from "node:test"; Deno.test("JsonCodec", async (t) => { test("JsonCodec.encode()", () => { const codec = new JsonCodec(); await t.step("encode()", () => { assertEquals( assert.deepStrictEqual( codec.encode({ foo: "bar" }), Buffer.from(new TextEncoder().encode('{"foo":"bar"}')), ); assertThrows( assert.throws( () => codec.encode(1n), EncodingError, ); }); await t.step("decode()", () => { assertEquals( test("JsonCodec.decode()", () => { const codec = new JsonCodec(); assert.deepStrictEqual( codec.decode(Buffer.from(new TextEncoder().encode('{"foo":"bar"}'))), { foo: "bar" }, ); assertThrows( assert.throws( () => codec.decode(Buffer.from(new TextEncoder().encode("invalid"))), DecodingError, ); }); }); src/deno.json +8 −5 Original line number Diff line number Diff line Loading @@ -9,16 +9,19 @@ "./mq": "./mq.ts" }, "imports": { "@deno/dnt": "jsr:@deno/dnt@^0.41.3", "@fedify/fedify": "jsr:@fedify/fedify@1.5.0", "@logtape/logtape": "jsr:@logtape/logtape@^0.9.0", "@std/assert": "jsr:@std/assert@^0.226.0", "@fedify/fedify": "jsr:@fedify/fedify@1.7.2", "@logtape/logtape": "jsr:@logtape/logtape@^1.0.2", "@std/async": "jsr:@std/async@^0.224.2", "ioredis": "npm:ioredis@^5.4.1" "ioredis": "npm:ioredis@^5.6.1" }, "unstable": [ "temporal" ], "nodeModulesDir": "none", "exclude": [ "dist", "node_modules" ], "tasks": { "check": "deno fmt --check && deno lint && deno check *.ts", "test": "deno test --allow-net --allow-env" Loading src/kv.test.ts +35 −16 Original line number Diff line number Diff line import { assertEquals } from "@std/assert/assert-equals"; import { RedisKvStore } from "@fedify/redis/kv"; import { Redis } from "ioredis"; import { RedisKvStore } from "./kv.ts"; import assert from "node:assert/strict"; import process from "node:process"; import { test } from "node:test"; Deno.test("DenoKvStore", async (t) => { const redis = new Redis(); const redisUrl = process.env.REDIS_URL; const skip = redisUrl == null; function getRedis(): { redis: Redis; keyPrefix: string; store: RedisKvStore } { const redis = new Redis(redisUrl!); const keyPrefix = `fedify_test_${crypto.randomUUID()}::`; const store = new RedisKvStore(redis, { keyPrefix }); return { redis, keyPrefix, store }; } await t.step("get()", async () => { test("DenoKvStore.get()", { skip }, async () => { const { redis, keyPrefix, store } = getRedis(); try { await redis.set(`${keyPrefix}foo::bar`, '"foobar"'); assertEquals(await store.get(["foo", "bar"]), "foobar"); assert.strictEqual(await store.get(["foo", "bar"]), "foobar"); } finally { redis.disconnect(); } }); await t.step("set()", async () => { test("DenoKvStore.set()", { skip }, async () => { const { redis, keyPrefix, store } = getRedis(); try { await store.set(["foo", "baz"], "baz"); assertEquals(await redis.get(`${keyPrefix}foo::baz`), '"baz"'); assert.strictEqual(await redis.get(`${keyPrefix}foo::baz`), '"baz"'); } finally { redis.disconnect(); } }); await t.step("delete()", async () => { assertEquals(await redis.exists(`${keyPrefix}foo::baz`), 1); test("DenoKvStore.delete()", { skip }, async () => { const { redis, keyPrefix, store } = getRedis(); try { await redis.set(`${keyPrefix}foo::baz`, '"baz"'); await store.delete(["foo", "baz"]); assertEquals(await redis.exists(`${keyPrefix}foo::baz`), 0); }); assert.equal(await redis.exists(`${keyPrefix}foo::baz`), 0); } finally { redis.disconnect(); } }); src/mq.test.ts +43 −41 Original line number Diff line number Diff line import { assertEquals } from "@std/assert/assert-equals"; import { assertGreater } from "@std/assert/assert-greater"; import { RedisMessageQueue } from "@fedify/redis/mq"; import * as temporal from "@js-temporal/polyfill"; import { delay } from "@std/async/delay"; import { Redis } from "ioredis"; import { RedisMessageQueue } from "./mq.ts"; import assert from "node:assert/strict"; import process from "node:process"; import { test } from "node:test"; let Temporal: typeof temporal.Temporal; if ("Temporal" in globalThis) { Temporal = globalThis.Temporal; } else { Temporal = temporal.Temporal; } const redisUrl = process.env.REDIS_URL; Deno.test("RedisMessageQueue", async (t) => { test("RedisMessageQueue", { skip: redisUrl == null }, async () => { const channelKey = `fedify_test_channel_${crypto.randomUUID()}`; const queueKey = `fedify_test_queue_${crypto.randomUUID()}`; const lockKey = `fedify_test_lock_${crypto.randomUUID()}`; const mq = new RedisMessageQueue(() => new Redis(), { using mq = new RedisMessageQueue(() => new Redis(redisUrl!), { pollInterval: { seconds: 1 }, channelKey, queueKey, lockKey, }); const mq2 = new RedisMessageQueue(() => new Redis(), { using mq2 = new RedisMessageQueue(() => new Redis(redisUrl!), { pollInterval: { seconds: 1 }, channelKey, queueKey, Loading @@ -30,67 +41,58 @@ Deno.test("RedisMessageQueue", async (t) => { messages.push(message); }, controller); await t.step("enqueue()", async () => { try { // enqueue() await mq.enqueue("Hello, world!"); }); await waitFor(() => messages.length > 0, 15_000); await t.step("listen()", () => { assertEquals(messages, ["Hello, world!"]); }); // listen() assert.deepStrictEqual(messages, ["Hello, world!"]); // enqueue() with delay let started = 0; await t.step("enqueue() with delay", async () => { started = Date.now(); await mq.enqueue( "Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) }, ); }); await waitFor(() => messages.length > 1, 15_000); await t.step("listen() with delay", () => { assertEquals(messages, ["Hello, world!", "Delayed message"]); assertGreater(Date.now() - started, 3_000); }); // listen() with delay assert.deepStrictEqual(messages, ["Hello, world!", "Delayed message"]); assert.ok(Date.now() - started > 3_000); await t.step("enqueue() [bulk]", async () => { // enqueue() [bulk] for (let i = 0; i < 1_000; i++) await mq.enqueue(i); }); await waitFor(() => messages.length > 1_001, 30_000); await t.step("listen() [bulk]", () => { // listen() [bulk] const numbers: Set<number> = new Set(); for (let i = 0; i < 1_000; i++) numbers.add(i); assertEquals(new Set(messages.slice(2)), numbers); }); assert.deepStrictEqual(new Set(messages.slice(2)), numbers); // Reset messages array for the next test: while (messages.length > 0) messages.pop(); await t.step("enqueueMany()", async () => { // enqueueMany() const bulkMessages = Array.from({ length: 500 }, (_, i) => `bulk-${i}`); await mq.enqueueMany(bulkMessages); }); await waitFor(() => messages.length >= 500, 30_000); await t.step("listen() after enqueueMany()", () => { // listen() after enqueueMany() const expectedMessages = new Set( Array.from({ length: 500 }, (_, i) => `bulk-${i}`), ); assertEquals(new Set(messages), expectedMessages); }); assert.deepStrictEqual(new Set(messages), expectedMessages); } finally { controller.abort(); await listening; await listening2; mq[Symbol.dispose](); mq2[Symbol.dispose](); } }); async function waitFor( Loading src/package.json 0 → 100644 +76 −0 Original line number Diff line number Diff line { "name": "@fedify/redis", "version": "0.5.0", "description": "Redis drivers for Fedify", "keywords": [ "fedify", "redis" ], "license": "MIT", "author": { "name": "Hong Minhee", "email": "hong@minhee.org", "url": "https://hongminhee.org/" }, "homepage": "https://github.com/fedify-dev/redis", "repository": { "type": "git", "url": "git+https://github.com/fedify-dev/redis.git", "directory": "redis" }, "bugs": { "url": "https://github.com/fedify-dev/redis/issues" }, "funding": [ "https://opencollective.com/fedify", "https://github.com/sponsors/dahlia" ], "type": "module", "main": "./dist/mod.js", "module": "./dist/mod.js", "types": "./dist/mod.d.ts", "exports": { ".": { "types": "./dist/mod.d.ts", "import": "./dist/mod.js" }, "./codec": { "types": "./dist/codec.d.ts", "import": "./dist/codec.js" }, "./kv": { "types": "./dist/kv.d.ts", "import": "./dist/kv.js" }, "./mq": { "types": "./dist/mq.d.ts", "import": "./dist/mq.js" }, "./package.json": "./package.json" }, "files": [ "dist", "package.json" ], "dependencies": { "@js-temporal/polyfill": "^0.5.1", "@logtape/logtape": "^1.0.2" }, "peerDependencies": { "@fedify/fedify": "^1.7.2", "ioredis": "^5.6.1" }, "devDependencies": { "@std/async": "jsr:^1.0.13", "@types/node": "^24.0.10", "tsdown": "^0.12.9", "typescript": "^5.8.3" }, "scripts": { "build": "tsdown", "prepack": "tsdown", "prepublish": "tsdown", "test": "tsdown && node --experimental-transform-types --test", "test:bun": "tsdown && bun test --timeout=10000" } } Loading
src/codec.test.ts +23 −25 Original line number Diff line number Diff line import { assertEquals } from "@std/assert/assert-equals"; import { assertThrows } from "@std/assert/assert-throws"; import { DecodingError, EncodingError, JsonCodec } from "@fedify/redis/codec"; import assert from "node:assert/strict"; import { Buffer } from "node:buffer"; import { DecodingError, EncodingError, JsonCodec } from "./codec.ts"; import { test } from "node:test"; Deno.test("JsonCodec", async (t) => { test("JsonCodec.encode()", () => { const codec = new JsonCodec(); await t.step("encode()", () => { assertEquals( assert.deepStrictEqual( codec.encode({ foo: "bar" }), Buffer.from(new TextEncoder().encode('{"foo":"bar"}')), ); assertThrows( assert.throws( () => codec.encode(1n), EncodingError, ); }); await t.step("decode()", () => { assertEquals( test("JsonCodec.decode()", () => { const codec = new JsonCodec(); assert.deepStrictEqual( codec.decode(Buffer.from(new TextEncoder().encode('{"foo":"bar"}'))), { foo: "bar" }, ); assertThrows( assert.throws( () => codec.decode(Buffer.from(new TextEncoder().encode("invalid"))), DecodingError, ); }); });
src/deno.json +8 −5 Original line number Diff line number Diff line Loading @@ -9,16 +9,19 @@ "./mq": "./mq.ts" }, "imports": { "@deno/dnt": "jsr:@deno/dnt@^0.41.3", "@fedify/fedify": "jsr:@fedify/fedify@1.5.0", "@logtape/logtape": "jsr:@logtape/logtape@^0.9.0", "@std/assert": "jsr:@std/assert@^0.226.0", "@fedify/fedify": "jsr:@fedify/fedify@1.7.2", "@logtape/logtape": "jsr:@logtape/logtape@^1.0.2", "@std/async": "jsr:@std/async@^0.224.2", "ioredis": "npm:ioredis@^5.4.1" "ioredis": "npm:ioredis@^5.6.1" }, "unstable": [ "temporal" ], "nodeModulesDir": "none", "exclude": [ "dist", "node_modules" ], "tasks": { "check": "deno fmt --check && deno lint && deno check *.ts", "test": "deno test --allow-net --allow-env" Loading
src/kv.test.ts +35 −16 Original line number Diff line number Diff line import { assertEquals } from "@std/assert/assert-equals"; import { RedisKvStore } from "@fedify/redis/kv"; import { Redis } from "ioredis"; import { RedisKvStore } from "./kv.ts"; import assert from "node:assert/strict"; import process from "node:process"; import { test } from "node:test"; Deno.test("DenoKvStore", async (t) => { const redis = new Redis(); const redisUrl = process.env.REDIS_URL; const skip = redisUrl == null; function getRedis(): { redis: Redis; keyPrefix: string; store: RedisKvStore } { const redis = new Redis(redisUrl!); const keyPrefix = `fedify_test_${crypto.randomUUID()}::`; const store = new RedisKvStore(redis, { keyPrefix }); return { redis, keyPrefix, store }; } await t.step("get()", async () => { test("DenoKvStore.get()", { skip }, async () => { const { redis, keyPrefix, store } = getRedis(); try { await redis.set(`${keyPrefix}foo::bar`, '"foobar"'); assertEquals(await store.get(["foo", "bar"]), "foobar"); assert.strictEqual(await store.get(["foo", "bar"]), "foobar"); } finally { redis.disconnect(); } }); await t.step("set()", async () => { test("DenoKvStore.set()", { skip }, async () => { const { redis, keyPrefix, store } = getRedis(); try { await store.set(["foo", "baz"], "baz"); assertEquals(await redis.get(`${keyPrefix}foo::baz`), '"baz"'); assert.strictEqual(await redis.get(`${keyPrefix}foo::baz`), '"baz"'); } finally { redis.disconnect(); } }); await t.step("delete()", async () => { assertEquals(await redis.exists(`${keyPrefix}foo::baz`), 1); test("DenoKvStore.delete()", { skip }, async () => { const { redis, keyPrefix, store } = getRedis(); try { await redis.set(`${keyPrefix}foo::baz`, '"baz"'); await store.delete(["foo", "baz"]); assertEquals(await redis.exists(`${keyPrefix}foo::baz`), 0); }); assert.equal(await redis.exists(`${keyPrefix}foo::baz`), 0); } finally { redis.disconnect(); } });
src/mq.test.ts +43 −41 Original line number Diff line number Diff line import { assertEquals } from "@std/assert/assert-equals"; import { assertGreater } from "@std/assert/assert-greater"; import { RedisMessageQueue } from "@fedify/redis/mq"; import * as temporal from "@js-temporal/polyfill"; import { delay } from "@std/async/delay"; import { Redis } from "ioredis"; import { RedisMessageQueue } from "./mq.ts"; import assert from "node:assert/strict"; import process from "node:process"; import { test } from "node:test"; let Temporal: typeof temporal.Temporal; if ("Temporal" in globalThis) { Temporal = globalThis.Temporal; } else { Temporal = temporal.Temporal; } const redisUrl = process.env.REDIS_URL; Deno.test("RedisMessageQueue", async (t) => { test("RedisMessageQueue", { skip: redisUrl == null }, async () => { const channelKey = `fedify_test_channel_${crypto.randomUUID()}`; const queueKey = `fedify_test_queue_${crypto.randomUUID()}`; const lockKey = `fedify_test_lock_${crypto.randomUUID()}`; const mq = new RedisMessageQueue(() => new Redis(), { using mq = new RedisMessageQueue(() => new Redis(redisUrl!), { pollInterval: { seconds: 1 }, channelKey, queueKey, lockKey, }); const mq2 = new RedisMessageQueue(() => new Redis(), { using mq2 = new RedisMessageQueue(() => new Redis(redisUrl!), { pollInterval: { seconds: 1 }, channelKey, queueKey, Loading @@ -30,67 +41,58 @@ Deno.test("RedisMessageQueue", async (t) => { messages.push(message); }, controller); await t.step("enqueue()", async () => { try { // enqueue() await mq.enqueue("Hello, world!"); }); await waitFor(() => messages.length > 0, 15_000); await t.step("listen()", () => { assertEquals(messages, ["Hello, world!"]); }); // listen() assert.deepStrictEqual(messages, ["Hello, world!"]); // enqueue() with delay let started = 0; await t.step("enqueue() with delay", async () => { started = Date.now(); await mq.enqueue( "Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) }, ); }); await waitFor(() => messages.length > 1, 15_000); await t.step("listen() with delay", () => { assertEquals(messages, ["Hello, world!", "Delayed message"]); assertGreater(Date.now() - started, 3_000); }); // listen() with delay assert.deepStrictEqual(messages, ["Hello, world!", "Delayed message"]); assert.ok(Date.now() - started > 3_000); await t.step("enqueue() [bulk]", async () => { // enqueue() [bulk] for (let i = 0; i < 1_000; i++) await mq.enqueue(i); }); await waitFor(() => messages.length > 1_001, 30_000); await t.step("listen() [bulk]", () => { // listen() [bulk] const numbers: Set<number> = new Set(); for (let i = 0; i < 1_000; i++) numbers.add(i); assertEquals(new Set(messages.slice(2)), numbers); }); assert.deepStrictEqual(new Set(messages.slice(2)), numbers); // Reset messages array for the next test: while (messages.length > 0) messages.pop(); await t.step("enqueueMany()", async () => { // enqueueMany() const bulkMessages = Array.from({ length: 500 }, (_, i) => `bulk-${i}`); await mq.enqueueMany(bulkMessages); }); await waitFor(() => messages.length >= 500, 30_000); await t.step("listen() after enqueueMany()", () => { // listen() after enqueueMany() const expectedMessages = new Set( Array.from({ length: 500 }, (_, i) => `bulk-${i}`), ); assertEquals(new Set(messages), expectedMessages); }); assert.deepStrictEqual(new Set(messages), expectedMessages); } finally { controller.abort(); await listening; await listening2; mq[Symbol.dispose](); mq2[Symbol.dispose](); } }); async function waitFor( Loading
src/package.json 0 → 100644 +76 −0 Original line number Diff line number Diff line { "name": "@fedify/redis", "version": "0.5.0", "description": "Redis drivers for Fedify", "keywords": [ "fedify", "redis" ], "license": "MIT", "author": { "name": "Hong Minhee", "email": "hong@minhee.org", "url": "https://hongminhee.org/" }, "homepage": "https://github.com/fedify-dev/redis", "repository": { "type": "git", "url": "git+https://github.com/fedify-dev/redis.git", "directory": "redis" }, "bugs": { "url": "https://github.com/fedify-dev/redis/issues" }, "funding": [ "https://opencollective.com/fedify", "https://github.com/sponsors/dahlia" ], "type": "module", "main": "./dist/mod.js", "module": "./dist/mod.js", "types": "./dist/mod.d.ts", "exports": { ".": { "types": "./dist/mod.d.ts", "import": "./dist/mod.js" }, "./codec": { "types": "./dist/codec.d.ts", "import": "./dist/codec.js" }, "./kv": { "types": "./dist/kv.d.ts", "import": "./dist/kv.js" }, "./mq": { "types": "./dist/mq.d.ts", "import": "./dist/mq.js" }, "./package.json": "./package.json" }, "files": [ "dist", "package.json" ], "dependencies": { "@js-temporal/polyfill": "^0.5.1", "@logtape/logtape": "^1.0.2" }, "peerDependencies": { "@fedify/fedify": "^1.7.2", "ioredis": "^5.6.1" }, "devDependencies": { "@std/async": "jsr:^1.0.13", "@types/node": "^24.0.10", "tsdown": "^0.12.9", "typescript": "^5.8.3" }, "scripts": { "build": "tsdown", "prepack": "tsdown", "prepublish": "tsdown", "test": "tsdown && node --experimental-transform-types --test", "test:bun": "tsdown && bun test --timeout=10000" } }