@objectstack/service-cluster-redis 16.1.0 → 17.0.0-rc.1
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/CHANGELOG.md +333 -0
- package/package.json +10 -4
- package/.turbo/turbo-build.log +0 -22
- package/src/client.ts +0 -75
- package/src/counter.ts +0 -63
- package/src/index.ts +0 -111
- package/src/kv.ts +0 -187
- package/src/lock.ts +0 -205
- package/src/pubsub.ts +0 -188
- package/src/redis.contract.test.ts +0 -163
- package/tsconfig.json +0 -10
- package/tsup.config.ts +0 -14
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Driver contract tests for the Redis cluster driver, run against
|
|
5
|
-
* `ioredis-mock` so they execute without a real Redis instance in CI.
|
|
6
|
-
*
|
|
7
|
-
* The same suites can be invoked against a live Redis by setting
|
|
8
|
-
* `RUN_REAL_REDIS=1` and providing `REDIS_URL` — see the conditional
|
|
9
|
-
* `describe.skipIf` blocks at the bottom.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// @ts-expect-error — ioredis-mock has no published types
|
|
13
|
-
import RedisMock from 'ioredis-mock';
|
|
14
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
15
|
-
import {
|
|
16
|
-
runLockContract,
|
|
17
|
-
runKVContract,
|
|
18
|
-
runCounterContract,
|
|
19
|
-
} from '@objectstack/service-cluster/testing';
|
|
20
|
-
// Load the driver entrypoint at module eval — importing it registers the
|
|
21
|
-
// 'redis' cluster driver as a side-effect, which `defineCluster({ driver:
|
|
22
|
-
// 'redis' })` then resolves. Doing this here (not via `await import()` inside
|
|
23
|
-
// the timed test bodies) keeps the one-time cold module-load cost out of the
|
|
24
|
-
// per-test 5s timeout — the wiring tests below were flaky on slow CI for
|
|
25
|
-
// exactly that reason (the first test paid the full import cost and timed out).
|
|
26
|
-
import './index.js';
|
|
27
|
-
import { defineCluster } from '@objectstack/service-cluster';
|
|
28
|
-
|
|
29
|
-
import { RedisPubSub } from './pubsub.js';
|
|
30
|
-
import { RedisLock } from './lock.js';
|
|
31
|
-
import { RedisKV } from './kv.js';
|
|
32
|
-
import { RedisCounter } from './counter.js';
|
|
33
|
-
|
|
34
|
-
// ioredis-mock shares state across instances by default, so each
|
|
35
|
-
// primitive gets a unique key-prefix per test to ensure isolation.
|
|
36
|
-
let suffix = 0;
|
|
37
|
-
const uniquePrefix = () => `t${++suffix}:`;
|
|
38
|
-
const makeClient = () => new RedisMock();
|
|
39
|
-
|
|
40
|
-
runLockContract('redis(mock)', async () =>
|
|
41
|
-
new RedisLock({ client: makeClient(), keyPrefix: uniquePrefix() }),
|
|
42
|
-
);
|
|
43
|
-
runKVContract('redis(mock)', async () =>
|
|
44
|
-
new RedisKV({ client: makeClient(), keyPrefix: uniquePrefix() }),
|
|
45
|
-
);
|
|
46
|
-
runCounterContract('redis(mock)', async () =>
|
|
47
|
-
new RedisCounter({ client: makeClient(), keyPrefix: uniquePrefix() }),
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// PubSub: Redis delivery is async (network roundtrip even for mock), so
|
|
51
|
-
// we can't reuse the synchronous contract suite. Cover the same surface
|
|
52
|
-
// here with explicit waits.
|
|
53
|
-
describe('IPubSub contract — redis(mock)', () => {
|
|
54
|
-
const flush = () => new Promise<void>((r) => setTimeout(r, 10));
|
|
55
|
-
|
|
56
|
-
it('delivers published messages to subscribers', async () => {
|
|
57
|
-
const bus = new RedisPubSub({ client: makeClient(), keyPrefix: uniquePrefix() });
|
|
58
|
-
const received: unknown[] = [];
|
|
59
|
-
bus.subscribe<{ n: number }>('ch', (msg) => { received.push(msg.payload); });
|
|
60
|
-
await flush(); // let SUBSCRIBE register
|
|
61
|
-
await bus.publish('ch', { n: 1 });
|
|
62
|
-
await bus.publish('ch', { n: 2 });
|
|
63
|
-
await flush();
|
|
64
|
-
expect(received).toEqual([{ n: 1 }, { n: 2 }]);
|
|
65
|
-
await bus.close();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('does not deliver to other channels', async () => {
|
|
69
|
-
const bus = new RedisPubSub({ client: makeClient(), keyPrefix: uniquePrefix() });
|
|
70
|
-
const h = vi.fn();
|
|
71
|
-
bus.subscribe('a', h);
|
|
72
|
-
await flush();
|
|
73
|
-
await bus.publish('b', { x: 1 });
|
|
74
|
-
await flush();
|
|
75
|
-
expect(h).not.toHaveBeenCalled();
|
|
76
|
-
await bus.close();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('supports multiple subscribers per channel', async () => {
|
|
80
|
-
const bus = new RedisPubSub({ client: makeClient(), keyPrefix: uniquePrefix() });
|
|
81
|
-
const a = vi.fn();
|
|
82
|
-
const b = vi.fn();
|
|
83
|
-
bus.subscribe('ch', a);
|
|
84
|
-
bus.subscribe('ch', b);
|
|
85
|
-
await flush();
|
|
86
|
-
await bus.publish('ch', 'hi');
|
|
87
|
-
await flush();
|
|
88
|
-
expect(a).toHaveBeenCalledTimes(1);
|
|
89
|
-
expect(b).toHaveBeenCalledTimes(1);
|
|
90
|
-
await bus.close();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('unsubscribe stops delivery and is idempotent', async () => {
|
|
94
|
-
const bus = new RedisPubSub({ client: makeClient(), keyPrefix: uniquePrefix() });
|
|
95
|
-
const h = vi.fn();
|
|
96
|
-
const off = bus.subscribe('ch', h);
|
|
97
|
-
await flush();
|
|
98
|
-
await bus.publish('ch', 1);
|
|
99
|
-
await flush();
|
|
100
|
-
off();
|
|
101
|
-
off();
|
|
102
|
-
await bus.publish('ch', 2);
|
|
103
|
-
await flush();
|
|
104
|
-
expect(h).toHaveBeenCalledTimes(1);
|
|
105
|
-
await bus.close();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('isolates handler errors from siblings', async () => {
|
|
109
|
-
const bus = new RedisPubSub({ client: makeClient(), keyPrefix: uniquePrefix() });
|
|
110
|
-
bus.subscribe('ch', () => { throw new Error('boom'); });
|
|
111
|
-
const ok = vi.fn();
|
|
112
|
-
bus.subscribe('ch', ok);
|
|
113
|
-
await flush();
|
|
114
|
-
await expect(bus.publish('ch', 1)).resolves.toBeUndefined();
|
|
115
|
-
await flush();
|
|
116
|
-
expect(ok).toHaveBeenCalledTimes(1);
|
|
117
|
-
await bus.close();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('close rejects further publishes', async () => {
|
|
121
|
-
const bus = new RedisPubSub({ client: makeClient(), keyPrefix: uniquePrefix() });
|
|
122
|
-
await bus.close();
|
|
123
|
-
await expect(bus.publish('ch', 1)).rejects.toThrow(/closed/);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
describe('Redis driver — wiring', () => {
|
|
128
|
-
it('exports a registerable driver and defineCluster picks it up', async () => {
|
|
129
|
-
const client = makeClient();
|
|
130
|
-
const cluster = defineCluster({
|
|
131
|
-
driver: 'redis',
|
|
132
|
-
nodeId: 'mock-node',
|
|
133
|
-
driverOptions: { client, keyPrefix: uniquePrefix() },
|
|
134
|
-
});
|
|
135
|
-
expect(cluster.driver).toBe('redis');
|
|
136
|
-
expect(cluster.nodeId).toBe('mock-node');
|
|
137
|
-
|
|
138
|
-
expect(await cluster.counter.incr('seq')).toBe(1n);
|
|
139
|
-
expect(await cluster.counter.incr('seq')).toBe(2n);
|
|
140
|
-
|
|
141
|
-
await cluster.kv.set('k', { hello: 'world' });
|
|
142
|
-
const got = await cluster.kv.get<{ hello: string }>('k');
|
|
143
|
-
expect(got?.value).toEqual({ hello: 'world' });
|
|
144
|
-
|
|
145
|
-
const handle = await cluster.lock.acquire('foo');
|
|
146
|
-
expect(handle).not.toBeNull();
|
|
147
|
-
expect(handle!.fencingToken).toBeGreaterThan(0n);
|
|
148
|
-
await handle!.release();
|
|
149
|
-
|
|
150
|
-
await cluster.close();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('does NOT quit caller-owned client on close', async () => {
|
|
154
|
-
const client = makeClient();
|
|
155
|
-
const cluster = defineCluster({
|
|
156
|
-
driver: 'redis',
|
|
157
|
-
nodeId: 'mock-node-2',
|
|
158
|
-
driverOptions: { client, keyPrefix: uniquePrefix() },
|
|
159
|
-
});
|
|
160
|
-
await cluster.close();
|
|
161
|
-
await expect(client.set('post-close', '1')).resolves.toBe('OK');
|
|
162
|
-
});
|
|
163
|
-
});
|
package/tsconfig.json
DELETED
package/tsup.config.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { defineConfig } from 'tsup';
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
entry: ['src/index.ts'],
|
|
7
|
-
splitting: true,
|
|
8
|
-
sourcemap: true,
|
|
9
|
-
clean: true,
|
|
10
|
-
dts: !process.env.OS_SKIP_DTS,
|
|
11
|
-
format: ['esm', 'cjs'],
|
|
12
|
-
target: 'es2020',
|
|
13
|
-
external: ['vitest', 'ioredis'],
|
|
14
|
-
});
|