@objectstack/service-cluster 5.1.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.
@@ -0,0 +1,264 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/testing.ts
2
+ var _vitest = require('vitest');
3
+ function runPubSubContract(name, make) {
4
+ _vitest.describe.call(void 0, `IPubSub contract \u2014 ${name}`, () => {
5
+ _vitest.it.call(void 0, "delivers published messages to subscribers", async () => {
6
+ const bus = await make();
7
+ const received = [];
8
+ bus.subscribe("ch", (msg) => {
9
+ received.push(msg.payload);
10
+ });
11
+ await bus.publish("ch", { n: 1 });
12
+ await bus.publish("ch", { n: 2 });
13
+ _vitest.expect.call(void 0, received).toEqual([{ n: 1 }, { n: 2 }]);
14
+ await bus.close();
15
+ });
16
+ _vitest.it.call(void 0, "does not deliver to other channels", async () => {
17
+ const bus = await make();
18
+ const h = _vitest.vi.fn();
19
+ bus.subscribe("a", h);
20
+ await bus.publish("b", { x: 1 });
21
+ _vitest.expect.call(void 0, h).not.toHaveBeenCalled();
22
+ await bus.close();
23
+ });
24
+ _vitest.it.call(void 0, "supports multiple subscribers per channel", async () => {
25
+ const bus = await make();
26
+ const a = _vitest.vi.fn();
27
+ const b = _vitest.vi.fn();
28
+ bus.subscribe("ch", a);
29
+ bus.subscribe("ch", b);
30
+ await bus.publish("ch", "hi");
31
+ _vitest.expect.call(void 0, a).toHaveBeenCalledTimes(1);
32
+ _vitest.expect.call(void 0, b).toHaveBeenCalledTimes(1);
33
+ await bus.close();
34
+ });
35
+ _vitest.it.call(void 0, "unsubscribe stops delivery and is idempotent", async () => {
36
+ const bus = await make();
37
+ const h = _vitest.vi.fn();
38
+ const off = bus.subscribe("ch", h);
39
+ await bus.publish("ch", 1);
40
+ off();
41
+ off();
42
+ await bus.publish("ch", 2);
43
+ _vitest.expect.call(void 0, h).toHaveBeenCalledTimes(1);
44
+ await bus.close();
45
+ });
46
+ _vitest.it.call(void 0, "isolates handler errors from siblings", async () => {
47
+ const errors = [];
48
+ const bus = await make();
49
+ bus.subscribe("ch", () => {
50
+ throw new Error("boom");
51
+ });
52
+ const ok = _vitest.vi.fn();
53
+ bus.subscribe("ch", ok);
54
+ await _vitest.expect.call(void 0, bus.publish("ch", 1)).resolves.toBeUndefined();
55
+ _vitest.expect.call(void 0, ok).toHaveBeenCalledTimes(1);
56
+ await bus.close();
57
+ void errors;
58
+ });
59
+ _vitest.it.call(void 0, "close rejects further publishes", async () => {
60
+ const bus = await make();
61
+ await bus.close();
62
+ await _vitest.expect.call(void 0, bus.publish("ch", 1)).rejects.toThrow(/closed/);
63
+ });
64
+ });
65
+ }
66
+ function runLockContract(name, make) {
67
+ _vitest.describe.call(void 0, `ILock contract \u2014 ${name}`, () => {
68
+ _vitest.it.call(void 0, "acquires and releases a free lock", async () => {
69
+ const l = await make();
70
+ const h = await l.acquire("k");
71
+ _vitest.expect.call(void 0, h).not.toBeNull();
72
+ _vitest.expect.call(void 0, h.isHeld()).toBe(true);
73
+ await h.release();
74
+ _vitest.expect.call(void 0, h.isHeld()).toBe(false);
75
+ await l.close();
76
+ });
77
+ _vitest.it.call(void 0, "fails fast when waitMs=0 and lock is held", async () => {
78
+ const l = await make();
79
+ const h1 = await l.acquire("k");
80
+ _vitest.expect.call(void 0, h1).not.toBeNull();
81
+ const h2 = await l.acquire("k");
82
+ _vitest.expect.call(void 0, h2).toBeNull();
83
+ await h1.release();
84
+ await l.close();
85
+ });
86
+ _vitest.it.call(void 0, "hands off lock to a waiter on release", async () => {
87
+ const l = await make();
88
+ const h1 = await l.acquire("k");
89
+ const waiterP = l.acquire("k", { waitMs: 1e3 });
90
+ await new Promise((r) => setTimeout(r, 10));
91
+ await h1.release();
92
+ const h2 = await waiterP;
93
+ _vitest.expect.call(void 0, h2).not.toBeNull();
94
+ _vitest.expect.call(void 0, h2.isHeld()).toBe(true);
95
+ await h2.release();
96
+ await l.close();
97
+ });
98
+ _vitest.it.call(void 0, "TTL auto-releases a stuck holder", async () => {
99
+ const l = await make();
100
+ const h1 = await l.acquire("k", { ttlMs: 30 });
101
+ _vitest.expect.call(void 0, h1).not.toBeNull();
102
+ await new Promise((r) => setTimeout(r, 60));
103
+ _vitest.expect.call(void 0, h1.isHeld()).toBe(false);
104
+ const h2 = await l.acquire("k");
105
+ _vitest.expect.call(void 0, h2).not.toBeNull();
106
+ await h2.release();
107
+ await l.close();
108
+ });
109
+ _vitest.it.call(void 0, "fencing tokens are monotonically increasing per key", async () => {
110
+ const l = await make();
111
+ const h1 = await l.acquire("k");
112
+ const t1 = h1.fencingToken;
113
+ await h1.release();
114
+ const h2 = await l.acquire("k");
115
+ _vitest.expect.call(void 0, h2.fencingToken).toBeGreaterThan(t1);
116
+ await h2.release();
117
+ await l.close();
118
+ });
119
+ _vitest.it.call(void 0, "renew extends the lease", async () => {
120
+ const l = await make();
121
+ const h = await l.acquire("k", { ttlMs: 40 });
122
+ await new Promise((r) => setTimeout(r, 20));
123
+ await h.renew(100);
124
+ await new Promise((r) => setTimeout(r, 30));
125
+ _vitest.expect.call(void 0, h.isHeld()).toBe(true);
126
+ await h.release();
127
+ await l.close();
128
+ });
129
+ _vitest.it.call(void 0, "renew on a lost lock throws", async () => {
130
+ const l = await make();
131
+ const h = await l.acquire("k", { ttlMs: 20 });
132
+ await new Promise((r) => setTimeout(r, 50));
133
+ await _vitest.expect.call(void 0, h.renew()).rejects.toThrow();
134
+ await l.close();
135
+ });
136
+ _vitest.it.call(void 0, "withLock returns fn result and auto-releases", async () => {
137
+ const l = await make();
138
+ const result = await l.withLock("k", async () => 42);
139
+ _vitest.expect.call(void 0, result).toBe(42);
140
+ const h = await l.acquire("k");
141
+ _vitest.expect.call(void 0, h).not.toBeNull();
142
+ await h.release();
143
+ await l.close();
144
+ });
145
+ _vitest.it.call(void 0, "withLock returns null without calling fn on timeout", async () => {
146
+ const l = await make();
147
+ const h = await l.acquire("k");
148
+ const fn = _vitest.vi.fn();
149
+ const result = await l.withLock("k", fn, { waitMs: 0 });
150
+ _vitest.expect.call(void 0, result).toBeNull();
151
+ _vitest.expect.call(void 0, fn).not.toHaveBeenCalled();
152
+ await h.release();
153
+ await l.close();
154
+ });
155
+ _vitest.it.call(void 0, "release is idempotent", async () => {
156
+ const l = await make();
157
+ const h = await l.acquire("k");
158
+ await h.release();
159
+ await _vitest.expect.call(void 0, h.release()).resolves.toBeUndefined();
160
+ await l.close();
161
+ });
162
+ });
163
+ }
164
+ function runKVContract(name, make) {
165
+ _vitest.describe.call(void 0, `IKV contract \u2014 ${name}`, () => {
166
+ _vitest.it.call(void 0, "set then get round-trips and increments version", async () => {
167
+ const kv = await make();
168
+ const a = await kv.set("k", { v: 1 });
169
+ _vitest.expect.call(void 0, a.version).toBe(1n);
170
+ const got = await kv.get("k");
171
+ _vitest.expect.call(void 0, _optionalChain([got, 'optionalAccess', _ => _.value])).toEqual({ v: 1 });
172
+ const b = await kv.set("k", { v: 2 });
173
+ _vitest.expect.call(void 0, b.version).toBe(2n);
174
+ await kv.close();
175
+ });
176
+ _vitest.it.call(void 0, "get on missing key returns undefined", async () => {
177
+ const kv = await make();
178
+ _vitest.expect.call(void 0, await kv.get("absent")).toBeUndefined();
179
+ await kv.close();
180
+ });
181
+ _vitest.it.call(void 0, "delete removes the key", async () => {
182
+ const kv = await make();
183
+ await kv.set("k", 1);
184
+ _vitest.expect.call(void 0, await kv.delete("k")).toBe(true);
185
+ _vitest.expect.call(void 0, await kv.get("k")).toBeUndefined();
186
+ _vitest.expect.call(void 0, await kv.delete("k")).toBe(false);
187
+ await kv.close();
188
+ });
189
+ _vitest.it.call(void 0, "ifVersion=0n requires absent key", async () => {
190
+ const kv = await make();
191
+ await kv.set("k", "first", { ifVersion: 0n });
192
+ await _vitest.expect.call(void 0, kv.set("k", "dup", { ifVersion: 0n })).rejects.toThrow(
193
+ /version mismatch/i
194
+ );
195
+ await kv.close();
196
+ });
197
+ _vitest.it.call(void 0, "cas succeeds on match, fails on mismatch", async () => {
198
+ const kv = await make();
199
+ const e1 = await kv.set("k", 1);
200
+ const e2 = await kv.cas("k", e1.version, 2);
201
+ _vitest.expect.call(void 0, _optionalChain([e2, 'optionalAccess', _2 => _2.value])).toBe(2);
202
+ const failed = await kv.cas("k", e1.version, 3);
203
+ _vitest.expect.call(void 0, failed).toBeUndefined();
204
+ await kv.close();
205
+ });
206
+ _vitest.it.call(void 0, "TTL expires entries", async () => {
207
+ const kv = await make();
208
+ await kv.set("k", "v", { ttl: 0.03 });
209
+ _vitest.expect.call(void 0, await _asyncOptionalChain([(await kv.get("k")), 'optionalAccess', async _3 => _3.value])).toBe("v");
210
+ await new Promise((r) => setTimeout(r, 60));
211
+ _vitest.expect.call(void 0, await kv.get("k")).toBeUndefined();
212
+ await kv.close();
213
+ });
214
+ });
215
+ }
216
+ function runCounterContract(name, make) {
217
+ _vitest.describe.call(void 0, `ICounter contract \u2014 ${name}`, () => {
218
+ _vitest.it.call(void 0, "starts at 0, incr returns new value", async () => {
219
+ const c = await make();
220
+ _vitest.expect.call(void 0, await c.peek("k")).toBe(0n);
221
+ _vitest.expect.call(void 0, await c.incr("k")).toBe(1n);
222
+ _vitest.expect.call(void 0, await c.incr("k")).toBe(2n);
223
+ _vitest.expect.call(void 0, await c.peek("k")).toBe(2n);
224
+ await c.close();
225
+ });
226
+ _vitest.it.call(void 0, "incr by custom delta", async () => {
227
+ const c = await make();
228
+ _vitest.expect.call(void 0, await c.incr("k", { by: 5 })).toBe(5n);
229
+ _vitest.expect.call(void 0, await c.incr("k", { by: -2 })).toBe(3n);
230
+ await c.close();
231
+ });
232
+ _vitest.it.call(void 0, "reset", async () => {
233
+ const c = await make();
234
+ await c.incr("k", { by: 10 });
235
+ await c.reset("k", 100n);
236
+ _vitest.expect.call(void 0, await c.peek("k")).toBe(100n);
237
+ await c.reset("k");
238
+ _vitest.expect.call(void 0, await c.peek("k")).toBe(0n);
239
+ await c.close();
240
+ });
241
+ _vitest.it.call(void 0, "isolates keys", async () => {
242
+ const c = await make();
243
+ await c.incr("a");
244
+ await c.incr("b", { by: 7 });
245
+ _vitest.expect.call(void 0, await c.peek("a")).toBe(1n);
246
+ _vitest.expect.call(void 0, await c.peek("b")).toBe(7n);
247
+ await c.close();
248
+ });
249
+ });
250
+ }
251
+ function runFullContract(name, f) {
252
+ runPubSubContract(name, f.makePubSub);
253
+ runLockContract(name, f.makeLock);
254
+ runKVContract(name, f.makeKV);
255
+ runCounterContract(name, f.makeCounter);
256
+ }
257
+
258
+
259
+
260
+
261
+
262
+
263
+ exports.runCounterContract = runCounterContract; exports.runFullContract = runFullContract; exports.runKVContract = runKVContract; exports.runLockContract = runLockContract; exports.runPubSubContract = runPubSubContract;
264
+ //# sourceMappingURL=testing.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/framework/framework/packages/services/service-cluster/dist/testing.cjs","../src/testing.ts"],"names":[],"mappings":"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","file":"/home/runner/work/framework/framework/packages/services/service-cluster/dist/testing.cjs","sourcesContent":[null,"// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Generic contract tests for cluster primitives.\n *\n * These are written once and run against any driver. The memory driver\n * suite calls them directly; future postgres/redis driver packages will\n * `import { runPubSubContract } from '@objectstack/service-cluster/testing'`\n * to get the same coverage for free.\n */\n\nimport { describe, expect, it, vi } from 'vitest';\nimport type {\n IPubSub,\n ILock,\n IKV,\n ICounter,\n} from '@objectstack/spec/contracts';\n\nexport interface ContractFactories {\n makePubSub: () => Promise<IPubSub>;\n makeLock: () => Promise<ILock>;\n makeKV: () => Promise<IKV>;\n makeCounter: () => Promise<ICounter>;\n}\n\nexport function runPubSubContract(name: string, make: () => Promise<IPubSub>) {\n describe(`IPubSub contract — ${name}`, () => {\n it('delivers published messages to subscribers', async () => {\n const bus = await make();\n const received: unknown[] = [];\n bus.subscribe<{ n: number }>('ch', (msg) => {\n received.push(msg.payload);\n });\n await bus.publish('ch', { n: 1 });\n await bus.publish('ch', { n: 2 });\n expect(received).toEqual([{ n: 1 }, { n: 2 }]);\n await bus.close();\n });\n\n it('does not deliver to other channels', async () => {\n const bus = await make();\n const h = vi.fn();\n bus.subscribe('a', h);\n await bus.publish('b', { x: 1 });\n expect(h).not.toHaveBeenCalled();\n await bus.close();\n });\n\n it('supports multiple subscribers per channel', async () => {\n const bus = await make();\n const a = vi.fn();\n const b = vi.fn();\n bus.subscribe('ch', a);\n bus.subscribe('ch', b);\n await bus.publish('ch', 'hi');\n expect(a).toHaveBeenCalledTimes(1);\n expect(b).toHaveBeenCalledTimes(1);\n await bus.close();\n });\n\n it('unsubscribe stops delivery and is idempotent', async () => {\n const bus = await make();\n const h = vi.fn();\n const off = bus.subscribe('ch', h);\n await bus.publish('ch', 1);\n off();\n off(); // idempotent\n await bus.publish('ch', 2);\n expect(h).toHaveBeenCalledTimes(1);\n await bus.close();\n });\n\n it('isolates handler errors from siblings', async () => {\n const errors: unknown[] = [];\n const bus = await make();\n // Memory driver allows override via constructor; for the\n // generic contract we just verify no throw leaks to publish().\n bus.subscribe('ch', () => {\n throw new Error('boom');\n });\n const ok = vi.fn();\n bus.subscribe('ch', ok);\n await expect(bus.publish('ch', 1)).resolves.toBeUndefined();\n expect(ok).toHaveBeenCalledTimes(1);\n await bus.close();\n void errors;\n });\n\n it('close rejects further publishes', async () => {\n const bus = await make();\n await bus.close();\n await expect(bus.publish('ch', 1)).rejects.toThrow(/closed/);\n });\n });\n}\n\nexport function runLockContract(name: string, make: () => Promise<ILock>) {\n describe(`ILock contract — ${name}`, () => {\n it('acquires and releases a free lock', async () => {\n const l = await make();\n const h = await l.acquire('k');\n expect(h).not.toBeNull();\n expect(h!.isHeld()).toBe(true);\n await h!.release();\n expect(h!.isHeld()).toBe(false);\n await l.close();\n });\n\n it('fails fast when waitMs=0 and lock is held', async () => {\n const l = await make();\n const h1 = await l.acquire('k');\n expect(h1).not.toBeNull();\n const h2 = await l.acquire('k');\n expect(h2).toBeNull();\n await h1!.release();\n await l.close();\n });\n\n it('hands off lock to a waiter on release', async () => {\n const l = await make();\n const h1 = await l.acquire('k');\n const waiterP = l.acquire('k', { waitMs: 1000 });\n await new Promise((r) => setTimeout(r, 10));\n await h1!.release();\n const h2 = await waiterP;\n expect(h2).not.toBeNull();\n expect(h2!.isHeld()).toBe(true);\n await h2!.release();\n await l.close();\n });\n\n it('TTL auto-releases a stuck holder', async () => {\n const l = await make();\n const h1 = await l.acquire('k', { ttlMs: 30 });\n expect(h1).not.toBeNull();\n await new Promise((r) => setTimeout(r, 60));\n expect(h1!.isHeld()).toBe(false);\n const h2 = await l.acquire('k');\n expect(h2).not.toBeNull();\n await h2!.release();\n await l.close();\n });\n\n it('fencing tokens are monotonically increasing per key', async () => {\n const l = await make();\n const h1 = await l.acquire('k');\n const t1 = h1!.fencingToken;\n await h1!.release();\n const h2 = await l.acquire('k');\n expect(h2!.fencingToken).toBeGreaterThan(t1);\n await h2!.release();\n await l.close();\n });\n\n it('renew extends the lease', async () => {\n const l = await make();\n const h = await l.acquire('k', { ttlMs: 40 });\n await new Promise((r) => setTimeout(r, 20));\n await h!.renew(100);\n await new Promise((r) => setTimeout(r, 30));\n // total 50ms in, original TTL would have expired at 40ms.\n expect(h!.isHeld()).toBe(true);\n await h!.release();\n await l.close();\n });\n\n it('renew on a lost lock throws', async () => {\n const l = await make();\n const h = await l.acquire('k', { ttlMs: 20 });\n await new Promise((r) => setTimeout(r, 50));\n await expect(h!.renew()).rejects.toThrow();\n await l.close();\n });\n\n it('withLock returns fn result and auto-releases', async () => {\n const l = await make();\n const result = await l.withLock('k', async () => 42);\n expect(result).toBe(42);\n // Lock should be free immediately.\n const h = await l.acquire('k');\n expect(h).not.toBeNull();\n await h!.release();\n await l.close();\n });\n\n it('withLock returns null without calling fn on timeout', async () => {\n const l = await make();\n const h = await l.acquire('k');\n const fn = vi.fn();\n const result = await l.withLock('k', fn, { waitMs: 0 });\n expect(result).toBeNull();\n expect(fn).not.toHaveBeenCalled();\n await h!.release();\n await l.close();\n });\n\n it('release is idempotent', async () => {\n const l = await make();\n const h = await l.acquire('k');\n await h!.release();\n await expect(h!.release()).resolves.toBeUndefined();\n await l.close();\n });\n });\n}\n\nexport function runKVContract(name: string, make: () => Promise<IKV>) {\n describe(`IKV contract — ${name}`, () => {\n it('set then get round-trips and increments version', async () => {\n const kv = await make();\n const a = await kv.set('k', { v: 1 });\n expect(a.version).toBe(1n);\n const got = await kv.get<{ v: number }>('k');\n expect(got?.value).toEqual({ v: 1 });\n const b = await kv.set('k', { v: 2 });\n expect(b.version).toBe(2n);\n await kv.close();\n });\n\n it('get on missing key returns undefined', async () => {\n const kv = await make();\n expect(await kv.get('absent')).toBeUndefined();\n await kv.close();\n });\n\n it('delete removes the key', async () => {\n const kv = await make();\n await kv.set('k', 1);\n expect(await kv.delete('k')).toBe(true);\n expect(await kv.get('k')).toBeUndefined();\n expect(await kv.delete('k')).toBe(false);\n await kv.close();\n });\n\n it('ifVersion=0n requires absent key', async () => {\n const kv = await make();\n await kv.set('k', 'first', { ifVersion: 0n });\n await expect(kv.set('k', 'dup', { ifVersion: 0n })).rejects.toThrow(\n /version mismatch/i,\n );\n await kv.close();\n });\n\n it('cas succeeds on match, fails on mismatch', async () => {\n const kv = await make();\n const e1 = await kv.set('k', 1);\n const e2 = await kv.cas('k', e1.version, 2);\n expect(e2?.value).toBe(2);\n const failed = await kv.cas('k', e1.version, 3);\n expect(failed).toBeUndefined();\n await kv.close();\n });\n\n it('TTL expires entries', async () => {\n const kv = await make();\n await kv.set('k', 'v', { ttl: 0.03 }); // 30ms\n expect((await kv.get('k'))?.value).toBe('v');\n await new Promise((r) => setTimeout(r, 60));\n expect(await kv.get('k')).toBeUndefined();\n await kv.close();\n });\n });\n}\n\nexport function runCounterContract(name: string, make: () => Promise<ICounter>) {\n describe(`ICounter contract — ${name}`, () => {\n it('starts at 0, incr returns new value', async () => {\n const c = await make();\n expect(await c.peek('k')).toBe(0n);\n expect(await c.incr('k')).toBe(1n);\n expect(await c.incr('k')).toBe(2n);\n expect(await c.peek('k')).toBe(2n);\n await c.close();\n });\n\n it('incr by custom delta', async () => {\n const c = await make();\n expect(await c.incr('k', { by: 5 })).toBe(5n);\n expect(await c.incr('k', { by: -2 })).toBe(3n);\n await c.close();\n });\n\n it('reset', async () => {\n const c = await make();\n await c.incr('k', { by: 10 });\n await c.reset('k', 100n);\n expect(await c.peek('k')).toBe(100n);\n await c.reset('k');\n expect(await c.peek('k')).toBe(0n);\n await c.close();\n });\n\n it('isolates keys', async () => {\n const c = await make();\n await c.incr('a');\n await c.incr('b', { by: 7 });\n expect(await c.peek('a')).toBe(1n);\n expect(await c.peek('b')).toBe(7n);\n await c.close();\n });\n });\n}\n\nexport function runFullContract(name: string, f: ContractFactories) {\n runPubSubContract(name, f.makePubSub);\n runLockContract(name, f.makeLock);\n runKVContract(name, f.makeKV);\n runCounterContract(name, f.makeCounter);\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { IPubSub, ILock, IKV, ICounter } from '@objectstack/spec/contracts';
2
+
3
+ interface ContractFactories {
4
+ makePubSub: () => Promise<IPubSub>;
5
+ makeLock: () => Promise<ILock>;
6
+ makeKV: () => Promise<IKV>;
7
+ makeCounter: () => Promise<ICounter>;
8
+ }
9
+ declare function runPubSubContract(name: string, make: () => Promise<IPubSub>): void;
10
+ declare function runLockContract(name: string, make: () => Promise<ILock>): void;
11
+ declare function runKVContract(name: string, make: () => Promise<IKV>): void;
12
+ declare function runCounterContract(name: string, make: () => Promise<ICounter>): void;
13
+ declare function runFullContract(name: string, f: ContractFactories): void;
14
+
15
+ export { type ContractFactories, runCounterContract, runFullContract, runKVContract, runLockContract, runPubSubContract };
@@ -0,0 +1,15 @@
1
+ import { IPubSub, ILock, IKV, ICounter } from '@objectstack/spec/contracts';
2
+
3
+ interface ContractFactories {
4
+ makePubSub: () => Promise<IPubSub>;
5
+ makeLock: () => Promise<ILock>;
6
+ makeKV: () => Promise<IKV>;
7
+ makeCounter: () => Promise<ICounter>;
8
+ }
9
+ declare function runPubSubContract(name: string, make: () => Promise<IPubSub>): void;
10
+ declare function runLockContract(name: string, make: () => Promise<ILock>): void;
11
+ declare function runKVContract(name: string, make: () => Promise<IKV>): void;
12
+ declare function runCounterContract(name: string, make: () => Promise<ICounter>): void;
13
+ declare function runFullContract(name: string, f: ContractFactories): void;
14
+
15
+ export { type ContractFactories, runCounterContract, runFullContract, runKVContract, runLockContract, runPubSubContract };
@@ -0,0 +1,264 @@
1
+ // src/testing.ts
2
+ import { describe, expect, it, vi } from "vitest";
3
+ function runPubSubContract(name, make) {
4
+ describe(`IPubSub contract \u2014 ${name}`, () => {
5
+ it("delivers published messages to subscribers", async () => {
6
+ const bus = await make();
7
+ const received = [];
8
+ bus.subscribe("ch", (msg) => {
9
+ received.push(msg.payload);
10
+ });
11
+ await bus.publish("ch", { n: 1 });
12
+ await bus.publish("ch", { n: 2 });
13
+ expect(received).toEqual([{ n: 1 }, { n: 2 }]);
14
+ await bus.close();
15
+ });
16
+ it("does not deliver to other channels", async () => {
17
+ const bus = await make();
18
+ const h = vi.fn();
19
+ bus.subscribe("a", h);
20
+ await bus.publish("b", { x: 1 });
21
+ expect(h).not.toHaveBeenCalled();
22
+ await bus.close();
23
+ });
24
+ it("supports multiple subscribers per channel", async () => {
25
+ const bus = await make();
26
+ const a = vi.fn();
27
+ const b = vi.fn();
28
+ bus.subscribe("ch", a);
29
+ bus.subscribe("ch", b);
30
+ await bus.publish("ch", "hi");
31
+ expect(a).toHaveBeenCalledTimes(1);
32
+ expect(b).toHaveBeenCalledTimes(1);
33
+ await bus.close();
34
+ });
35
+ it("unsubscribe stops delivery and is idempotent", async () => {
36
+ const bus = await make();
37
+ const h = vi.fn();
38
+ const off = bus.subscribe("ch", h);
39
+ await bus.publish("ch", 1);
40
+ off();
41
+ off();
42
+ await bus.publish("ch", 2);
43
+ expect(h).toHaveBeenCalledTimes(1);
44
+ await bus.close();
45
+ });
46
+ it("isolates handler errors from siblings", async () => {
47
+ const errors = [];
48
+ const bus = await make();
49
+ bus.subscribe("ch", () => {
50
+ throw new Error("boom");
51
+ });
52
+ const ok = vi.fn();
53
+ bus.subscribe("ch", ok);
54
+ await expect(bus.publish("ch", 1)).resolves.toBeUndefined();
55
+ expect(ok).toHaveBeenCalledTimes(1);
56
+ await bus.close();
57
+ void errors;
58
+ });
59
+ it("close rejects further publishes", async () => {
60
+ const bus = await make();
61
+ await bus.close();
62
+ await expect(bus.publish("ch", 1)).rejects.toThrow(/closed/);
63
+ });
64
+ });
65
+ }
66
+ function runLockContract(name, make) {
67
+ describe(`ILock contract \u2014 ${name}`, () => {
68
+ it("acquires and releases a free lock", async () => {
69
+ const l = await make();
70
+ const h = await l.acquire("k");
71
+ expect(h).not.toBeNull();
72
+ expect(h.isHeld()).toBe(true);
73
+ await h.release();
74
+ expect(h.isHeld()).toBe(false);
75
+ await l.close();
76
+ });
77
+ it("fails fast when waitMs=0 and lock is held", async () => {
78
+ const l = await make();
79
+ const h1 = await l.acquire("k");
80
+ expect(h1).not.toBeNull();
81
+ const h2 = await l.acquire("k");
82
+ expect(h2).toBeNull();
83
+ await h1.release();
84
+ await l.close();
85
+ });
86
+ it("hands off lock to a waiter on release", async () => {
87
+ const l = await make();
88
+ const h1 = await l.acquire("k");
89
+ const waiterP = l.acquire("k", { waitMs: 1e3 });
90
+ await new Promise((r) => setTimeout(r, 10));
91
+ await h1.release();
92
+ const h2 = await waiterP;
93
+ expect(h2).not.toBeNull();
94
+ expect(h2.isHeld()).toBe(true);
95
+ await h2.release();
96
+ await l.close();
97
+ });
98
+ it("TTL auto-releases a stuck holder", async () => {
99
+ const l = await make();
100
+ const h1 = await l.acquire("k", { ttlMs: 30 });
101
+ expect(h1).not.toBeNull();
102
+ await new Promise((r) => setTimeout(r, 60));
103
+ expect(h1.isHeld()).toBe(false);
104
+ const h2 = await l.acquire("k");
105
+ expect(h2).not.toBeNull();
106
+ await h2.release();
107
+ await l.close();
108
+ });
109
+ it("fencing tokens are monotonically increasing per key", async () => {
110
+ const l = await make();
111
+ const h1 = await l.acquire("k");
112
+ const t1 = h1.fencingToken;
113
+ await h1.release();
114
+ const h2 = await l.acquire("k");
115
+ expect(h2.fencingToken).toBeGreaterThan(t1);
116
+ await h2.release();
117
+ await l.close();
118
+ });
119
+ it("renew extends the lease", async () => {
120
+ const l = await make();
121
+ const h = await l.acquire("k", { ttlMs: 40 });
122
+ await new Promise((r) => setTimeout(r, 20));
123
+ await h.renew(100);
124
+ await new Promise((r) => setTimeout(r, 30));
125
+ expect(h.isHeld()).toBe(true);
126
+ await h.release();
127
+ await l.close();
128
+ });
129
+ it("renew on a lost lock throws", async () => {
130
+ const l = await make();
131
+ const h = await l.acquire("k", { ttlMs: 20 });
132
+ await new Promise((r) => setTimeout(r, 50));
133
+ await expect(h.renew()).rejects.toThrow();
134
+ await l.close();
135
+ });
136
+ it("withLock returns fn result and auto-releases", async () => {
137
+ const l = await make();
138
+ const result = await l.withLock("k", async () => 42);
139
+ expect(result).toBe(42);
140
+ const h = await l.acquire("k");
141
+ expect(h).not.toBeNull();
142
+ await h.release();
143
+ await l.close();
144
+ });
145
+ it("withLock returns null without calling fn on timeout", async () => {
146
+ const l = await make();
147
+ const h = await l.acquire("k");
148
+ const fn = vi.fn();
149
+ const result = await l.withLock("k", fn, { waitMs: 0 });
150
+ expect(result).toBeNull();
151
+ expect(fn).not.toHaveBeenCalled();
152
+ await h.release();
153
+ await l.close();
154
+ });
155
+ it("release is idempotent", async () => {
156
+ const l = await make();
157
+ const h = await l.acquire("k");
158
+ await h.release();
159
+ await expect(h.release()).resolves.toBeUndefined();
160
+ await l.close();
161
+ });
162
+ });
163
+ }
164
+ function runKVContract(name, make) {
165
+ describe(`IKV contract \u2014 ${name}`, () => {
166
+ it("set then get round-trips and increments version", async () => {
167
+ const kv = await make();
168
+ const a = await kv.set("k", { v: 1 });
169
+ expect(a.version).toBe(1n);
170
+ const got = await kv.get("k");
171
+ expect(got?.value).toEqual({ v: 1 });
172
+ const b = await kv.set("k", { v: 2 });
173
+ expect(b.version).toBe(2n);
174
+ await kv.close();
175
+ });
176
+ it("get on missing key returns undefined", async () => {
177
+ const kv = await make();
178
+ expect(await kv.get("absent")).toBeUndefined();
179
+ await kv.close();
180
+ });
181
+ it("delete removes the key", async () => {
182
+ const kv = await make();
183
+ await kv.set("k", 1);
184
+ expect(await kv.delete("k")).toBe(true);
185
+ expect(await kv.get("k")).toBeUndefined();
186
+ expect(await kv.delete("k")).toBe(false);
187
+ await kv.close();
188
+ });
189
+ it("ifVersion=0n requires absent key", async () => {
190
+ const kv = await make();
191
+ await kv.set("k", "first", { ifVersion: 0n });
192
+ await expect(kv.set("k", "dup", { ifVersion: 0n })).rejects.toThrow(
193
+ /version mismatch/i
194
+ );
195
+ await kv.close();
196
+ });
197
+ it("cas succeeds on match, fails on mismatch", async () => {
198
+ const kv = await make();
199
+ const e1 = await kv.set("k", 1);
200
+ const e2 = await kv.cas("k", e1.version, 2);
201
+ expect(e2?.value).toBe(2);
202
+ const failed = await kv.cas("k", e1.version, 3);
203
+ expect(failed).toBeUndefined();
204
+ await kv.close();
205
+ });
206
+ it("TTL expires entries", async () => {
207
+ const kv = await make();
208
+ await kv.set("k", "v", { ttl: 0.03 });
209
+ expect((await kv.get("k"))?.value).toBe("v");
210
+ await new Promise((r) => setTimeout(r, 60));
211
+ expect(await kv.get("k")).toBeUndefined();
212
+ await kv.close();
213
+ });
214
+ });
215
+ }
216
+ function runCounterContract(name, make) {
217
+ describe(`ICounter contract \u2014 ${name}`, () => {
218
+ it("starts at 0, incr returns new value", async () => {
219
+ const c = await make();
220
+ expect(await c.peek("k")).toBe(0n);
221
+ expect(await c.incr("k")).toBe(1n);
222
+ expect(await c.incr("k")).toBe(2n);
223
+ expect(await c.peek("k")).toBe(2n);
224
+ await c.close();
225
+ });
226
+ it("incr by custom delta", async () => {
227
+ const c = await make();
228
+ expect(await c.incr("k", { by: 5 })).toBe(5n);
229
+ expect(await c.incr("k", { by: -2 })).toBe(3n);
230
+ await c.close();
231
+ });
232
+ it("reset", async () => {
233
+ const c = await make();
234
+ await c.incr("k", { by: 10 });
235
+ await c.reset("k", 100n);
236
+ expect(await c.peek("k")).toBe(100n);
237
+ await c.reset("k");
238
+ expect(await c.peek("k")).toBe(0n);
239
+ await c.close();
240
+ });
241
+ it("isolates keys", async () => {
242
+ const c = await make();
243
+ await c.incr("a");
244
+ await c.incr("b", { by: 7 });
245
+ expect(await c.peek("a")).toBe(1n);
246
+ expect(await c.peek("b")).toBe(7n);
247
+ await c.close();
248
+ });
249
+ });
250
+ }
251
+ function runFullContract(name, f) {
252
+ runPubSubContract(name, f.makePubSub);
253
+ runLockContract(name, f.makeLock);
254
+ runKVContract(name, f.makeKV);
255
+ runCounterContract(name, f.makeCounter);
256
+ }
257
+ export {
258
+ runCounterContract,
259
+ runFullContract,
260
+ runKVContract,
261
+ runLockContract,
262
+ runPubSubContract
263
+ };
264
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Generic contract tests for cluster primitives.\n *\n * These are written once and run against any driver. The memory driver\n * suite calls them directly; future postgres/redis driver packages will\n * `import { runPubSubContract } from '@objectstack/service-cluster/testing'`\n * to get the same coverage for free.\n */\n\nimport { describe, expect, it, vi } from 'vitest';\nimport type {\n IPubSub,\n ILock,\n IKV,\n ICounter,\n} from '@objectstack/spec/contracts';\n\nexport interface ContractFactories {\n makePubSub: () => Promise<IPubSub>;\n makeLock: () => Promise<ILock>;\n makeKV: () => Promise<IKV>;\n makeCounter: () => Promise<ICounter>;\n}\n\nexport function runPubSubContract(name: string, make: () => Promise<IPubSub>) {\n describe(`IPubSub contract — ${name}`, () => {\n it('delivers published messages to subscribers', async () => {\n const bus = await make();\n const received: unknown[] = [];\n bus.subscribe<{ n: number }>('ch', (msg) => {\n received.push(msg.payload);\n });\n await bus.publish('ch', { n: 1 });\n await bus.publish('ch', { n: 2 });\n expect(received).toEqual([{ n: 1 }, { n: 2 }]);\n await bus.close();\n });\n\n it('does not deliver to other channels', async () => {\n const bus = await make();\n const h = vi.fn();\n bus.subscribe('a', h);\n await bus.publish('b', { x: 1 });\n expect(h).not.toHaveBeenCalled();\n await bus.close();\n });\n\n it('supports multiple subscribers per channel', async () => {\n const bus = await make();\n const a = vi.fn();\n const b = vi.fn();\n bus.subscribe('ch', a);\n bus.subscribe('ch', b);\n await bus.publish('ch', 'hi');\n expect(a).toHaveBeenCalledTimes(1);\n expect(b).toHaveBeenCalledTimes(1);\n await bus.close();\n });\n\n it('unsubscribe stops delivery and is idempotent', async () => {\n const bus = await make();\n const h = vi.fn();\n const off = bus.subscribe('ch', h);\n await bus.publish('ch', 1);\n off();\n off(); // idempotent\n await bus.publish('ch', 2);\n expect(h).toHaveBeenCalledTimes(1);\n await bus.close();\n });\n\n it('isolates handler errors from siblings', async () => {\n const errors: unknown[] = [];\n const bus = await make();\n // Memory driver allows override via constructor; for the\n // generic contract we just verify no throw leaks to publish().\n bus.subscribe('ch', () => {\n throw new Error('boom');\n });\n const ok = vi.fn();\n bus.subscribe('ch', ok);\n await expect(bus.publish('ch', 1)).resolves.toBeUndefined();\n expect(ok).toHaveBeenCalledTimes(1);\n await bus.close();\n void errors;\n });\n\n it('close rejects further publishes', async () => {\n const bus = await make();\n await bus.close();\n await expect(bus.publish('ch', 1)).rejects.toThrow(/closed/);\n });\n });\n}\n\nexport function runLockContract(name: string, make: () => Promise<ILock>) {\n describe(`ILock contract — ${name}`, () => {\n it('acquires and releases a free lock', async () => {\n const l = await make();\n const h = await l.acquire('k');\n expect(h).not.toBeNull();\n expect(h!.isHeld()).toBe(true);\n await h!.release();\n expect(h!.isHeld()).toBe(false);\n await l.close();\n });\n\n it('fails fast when waitMs=0 and lock is held', async () => {\n const l = await make();\n const h1 = await l.acquire('k');\n expect(h1).not.toBeNull();\n const h2 = await l.acquire('k');\n expect(h2).toBeNull();\n await h1!.release();\n await l.close();\n });\n\n it('hands off lock to a waiter on release', async () => {\n const l = await make();\n const h1 = await l.acquire('k');\n const waiterP = l.acquire('k', { waitMs: 1000 });\n await new Promise((r) => setTimeout(r, 10));\n await h1!.release();\n const h2 = await waiterP;\n expect(h2).not.toBeNull();\n expect(h2!.isHeld()).toBe(true);\n await h2!.release();\n await l.close();\n });\n\n it('TTL auto-releases a stuck holder', async () => {\n const l = await make();\n const h1 = await l.acquire('k', { ttlMs: 30 });\n expect(h1).not.toBeNull();\n await new Promise((r) => setTimeout(r, 60));\n expect(h1!.isHeld()).toBe(false);\n const h2 = await l.acquire('k');\n expect(h2).not.toBeNull();\n await h2!.release();\n await l.close();\n });\n\n it('fencing tokens are monotonically increasing per key', async () => {\n const l = await make();\n const h1 = await l.acquire('k');\n const t1 = h1!.fencingToken;\n await h1!.release();\n const h2 = await l.acquire('k');\n expect(h2!.fencingToken).toBeGreaterThan(t1);\n await h2!.release();\n await l.close();\n });\n\n it('renew extends the lease', async () => {\n const l = await make();\n const h = await l.acquire('k', { ttlMs: 40 });\n await new Promise((r) => setTimeout(r, 20));\n await h!.renew(100);\n await new Promise((r) => setTimeout(r, 30));\n // total 50ms in, original TTL would have expired at 40ms.\n expect(h!.isHeld()).toBe(true);\n await h!.release();\n await l.close();\n });\n\n it('renew on a lost lock throws', async () => {\n const l = await make();\n const h = await l.acquire('k', { ttlMs: 20 });\n await new Promise((r) => setTimeout(r, 50));\n await expect(h!.renew()).rejects.toThrow();\n await l.close();\n });\n\n it('withLock returns fn result and auto-releases', async () => {\n const l = await make();\n const result = await l.withLock('k', async () => 42);\n expect(result).toBe(42);\n // Lock should be free immediately.\n const h = await l.acquire('k');\n expect(h).not.toBeNull();\n await h!.release();\n await l.close();\n });\n\n it('withLock returns null without calling fn on timeout', async () => {\n const l = await make();\n const h = await l.acquire('k');\n const fn = vi.fn();\n const result = await l.withLock('k', fn, { waitMs: 0 });\n expect(result).toBeNull();\n expect(fn).not.toHaveBeenCalled();\n await h!.release();\n await l.close();\n });\n\n it('release is idempotent', async () => {\n const l = await make();\n const h = await l.acquire('k');\n await h!.release();\n await expect(h!.release()).resolves.toBeUndefined();\n await l.close();\n });\n });\n}\n\nexport function runKVContract(name: string, make: () => Promise<IKV>) {\n describe(`IKV contract — ${name}`, () => {\n it('set then get round-trips and increments version', async () => {\n const kv = await make();\n const a = await kv.set('k', { v: 1 });\n expect(a.version).toBe(1n);\n const got = await kv.get<{ v: number }>('k');\n expect(got?.value).toEqual({ v: 1 });\n const b = await kv.set('k', { v: 2 });\n expect(b.version).toBe(2n);\n await kv.close();\n });\n\n it('get on missing key returns undefined', async () => {\n const kv = await make();\n expect(await kv.get('absent')).toBeUndefined();\n await kv.close();\n });\n\n it('delete removes the key', async () => {\n const kv = await make();\n await kv.set('k', 1);\n expect(await kv.delete('k')).toBe(true);\n expect(await kv.get('k')).toBeUndefined();\n expect(await kv.delete('k')).toBe(false);\n await kv.close();\n });\n\n it('ifVersion=0n requires absent key', async () => {\n const kv = await make();\n await kv.set('k', 'first', { ifVersion: 0n });\n await expect(kv.set('k', 'dup', { ifVersion: 0n })).rejects.toThrow(\n /version mismatch/i,\n );\n await kv.close();\n });\n\n it('cas succeeds on match, fails on mismatch', async () => {\n const kv = await make();\n const e1 = await kv.set('k', 1);\n const e2 = await kv.cas('k', e1.version, 2);\n expect(e2?.value).toBe(2);\n const failed = await kv.cas('k', e1.version, 3);\n expect(failed).toBeUndefined();\n await kv.close();\n });\n\n it('TTL expires entries', async () => {\n const kv = await make();\n await kv.set('k', 'v', { ttl: 0.03 }); // 30ms\n expect((await kv.get('k'))?.value).toBe('v');\n await new Promise((r) => setTimeout(r, 60));\n expect(await kv.get('k')).toBeUndefined();\n await kv.close();\n });\n });\n}\n\nexport function runCounterContract(name: string, make: () => Promise<ICounter>) {\n describe(`ICounter contract — ${name}`, () => {\n it('starts at 0, incr returns new value', async () => {\n const c = await make();\n expect(await c.peek('k')).toBe(0n);\n expect(await c.incr('k')).toBe(1n);\n expect(await c.incr('k')).toBe(2n);\n expect(await c.peek('k')).toBe(2n);\n await c.close();\n });\n\n it('incr by custom delta', async () => {\n const c = await make();\n expect(await c.incr('k', { by: 5 })).toBe(5n);\n expect(await c.incr('k', { by: -2 })).toBe(3n);\n await c.close();\n });\n\n it('reset', async () => {\n const c = await make();\n await c.incr('k', { by: 10 });\n await c.reset('k', 100n);\n expect(await c.peek('k')).toBe(100n);\n await c.reset('k');\n expect(await c.peek('k')).toBe(0n);\n await c.close();\n });\n\n it('isolates keys', async () => {\n const c = await make();\n await c.incr('a');\n await c.incr('b', { by: 7 });\n expect(await c.peek('a')).toBe(1n);\n expect(await c.peek('b')).toBe(7n);\n await c.close();\n });\n });\n}\n\nexport function runFullContract(name: string, f: ContractFactories) {\n runPubSubContract(name, f.makePubSub);\n runLockContract(name, f.makeLock);\n runKVContract(name, f.makeKV);\n runCounterContract(name, f.makeCounter);\n}\n"],"mappings":"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","names":[]}