@objectstack/service-cluster 5.1.2 → 5.1.5
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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +23 -0
- package/dist/testing.cjs +8 -8
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +8 -8
- package/dist/testing.js.map +1 -1
- package/package.json +3 -3
- package/src/testing.ts +9 -9
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/service-cluster@5.1.
|
|
2
|
+
> @objectstack/service-cluster@5.1.5 build /home/runner/work/framework/framework/packages/services/service-cluster
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts, src/testing.ts
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
[32mESM[39m [1mdist/testing.js [22m[32m8.60 KB[39m
|
|
15
15
|
[32mESM[39m [1mdist/index.js.map [22m[32m33.85 KB[39m
|
|
16
16
|
[32mESM[39m [1mdist/testing.js.map [22m[32m18.62 KB[39m
|
|
17
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m ⚡️ Build success in 920ms
|
|
18
18
|
[32mCJS[39m [1mdist/index.cjs [22m[32m13.98 KB[39m
|
|
19
|
-
[32mCJS[39m [1mdist/testing.cjs [22m[32m11.
|
|
19
|
+
[32mCJS[39m [1mdist/testing.cjs [22m[32m11.60 KB[39m
|
|
20
20
|
[32mCJS[39m [1mdist/index.cjs.map [22m[32m37.23 KB[39m
|
|
21
21
|
[32mCJS[39m [1mdist/testing.cjs.map [22m[32m14.18 KB[39m
|
|
22
|
-
[32mCJS[39m ⚡️ Build success in
|
|
22
|
+
[32mCJS[39m ⚡️ Build success in 932ms
|
|
23
23
|
[34mDTS[39m Build start
|
|
24
|
-
[32mDTS[39m ⚡️ Build success in
|
|
24
|
+
[32mDTS[39m ⚡️ Build success in 18267ms
|
|
25
25
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m8.05 KB[39m
|
|
26
26
|
[32mDTS[39m [1mdist/testing.d.ts [22m[32m794.00 B[39m
|
|
27
27
|
[32mDTS[39m [1mdist/index.d.cts [22m[32m8.05 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @objectstack/service-cluster
|
|
2
2
|
|
|
3
|
+
## 5.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [b4c74a9]
|
|
8
|
+
- @objectstack/spec@6.2.0
|
|
9
|
+
- @objectstack/core@6.2.0
|
|
10
|
+
|
|
11
|
+
## 5.1.4
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- @objectstack/spec@6.1.1
|
|
16
|
+
- @objectstack/core@6.1.1
|
|
17
|
+
|
|
18
|
+
## 5.1.3
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [93c0589]
|
|
23
|
+
- @objectstack/spec@6.1.0
|
|
24
|
+
- @objectstack/core@6.1.0
|
|
25
|
+
|
|
3
26
|
## 5.1.2
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/testing.cjs
CHANGED
|
@@ -97,9 +97,9 @@ function runLockContract(name, make) {
|
|
|
97
97
|
});
|
|
98
98
|
_vitest.it.call(void 0, "TTL auto-releases a stuck holder", async () => {
|
|
99
99
|
const l = await make();
|
|
100
|
-
const h1 = await l.acquire("k", { ttlMs:
|
|
100
|
+
const h1 = await l.acquire("k", { ttlMs: 50 });
|
|
101
101
|
_vitest.expect.call(void 0, h1).not.toBeNull();
|
|
102
|
-
await new Promise((r) => setTimeout(r,
|
|
102
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
103
103
|
_vitest.expect.call(void 0, h1.isHeld()).toBe(false);
|
|
104
104
|
const h2 = await l.acquire("k");
|
|
105
105
|
_vitest.expect.call(void 0, h2).not.toBeNull();
|
|
@@ -118,18 +118,18 @@ function runLockContract(name, make) {
|
|
|
118
118
|
});
|
|
119
119
|
_vitest.it.call(void 0, "renew extends the lease", async () => {
|
|
120
120
|
const l = await make();
|
|
121
|
-
const h = await l.acquire("k", { ttlMs:
|
|
122
|
-
await new Promise((r) => setTimeout(r,
|
|
123
|
-
await h.renew(
|
|
124
|
-
await new Promise((r) => setTimeout(r,
|
|
121
|
+
const h = await l.acquire("k", { ttlMs: 200 });
|
|
122
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
123
|
+
await h.renew(400);
|
|
124
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
125
125
|
_vitest.expect.call(void 0, h.isHeld()).toBe(true);
|
|
126
126
|
await h.release();
|
|
127
127
|
await l.close();
|
|
128
128
|
});
|
|
129
129
|
_vitest.it.call(void 0, "renew on a lost lock throws", async () => {
|
|
130
130
|
const l = await make();
|
|
131
|
-
const h = await l.acquire("k", { ttlMs:
|
|
132
|
-
await new Promise((r) => setTimeout(r,
|
|
131
|
+
const h = await l.acquire("k", { ttlMs: 50 });
|
|
132
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
133
133
|
await _vitest.expect.call(void 0, h.renew()).rejects.toThrow();
|
|
134
134
|
await l.close();
|
|
135
135
|
});
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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: 50 });\n expect(h1).not.toBeNull();\n await new Promise((r) => setTimeout(r, 150));\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: 200 });\n await new Promise((r) => setTimeout(r, 50));\n await h!.renew(400);\n await new Promise((r) => setTimeout(r, 250));\n // total 300ms in, original TTL would have expired at 200ms.\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: 50 });\n await new Promise((r) => setTimeout(r, 150));\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"]}
|
package/dist/testing.js
CHANGED
|
@@ -97,9 +97,9 @@ function runLockContract(name, make) {
|
|
|
97
97
|
});
|
|
98
98
|
it("TTL auto-releases a stuck holder", async () => {
|
|
99
99
|
const l = await make();
|
|
100
|
-
const h1 = await l.acquire("k", { ttlMs:
|
|
100
|
+
const h1 = await l.acquire("k", { ttlMs: 50 });
|
|
101
101
|
expect(h1).not.toBeNull();
|
|
102
|
-
await new Promise((r) => setTimeout(r,
|
|
102
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
103
103
|
expect(h1.isHeld()).toBe(false);
|
|
104
104
|
const h2 = await l.acquire("k");
|
|
105
105
|
expect(h2).not.toBeNull();
|
|
@@ -118,18 +118,18 @@ function runLockContract(name, make) {
|
|
|
118
118
|
});
|
|
119
119
|
it("renew extends the lease", async () => {
|
|
120
120
|
const l = await make();
|
|
121
|
-
const h = await l.acquire("k", { ttlMs:
|
|
122
|
-
await new Promise((r) => setTimeout(r,
|
|
123
|
-
await h.renew(
|
|
124
|
-
await new Promise((r) => setTimeout(r,
|
|
121
|
+
const h = await l.acquire("k", { ttlMs: 200 });
|
|
122
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
123
|
+
await h.renew(400);
|
|
124
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
125
125
|
expect(h.isHeld()).toBe(true);
|
|
126
126
|
await h.release();
|
|
127
127
|
await l.close();
|
|
128
128
|
});
|
|
129
129
|
it("renew on a lost lock throws", async () => {
|
|
130
130
|
const l = await make();
|
|
131
|
-
const h = await l.acquire("k", { ttlMs:
|
|
132
|
-
await new Promise((r) => setTimeout(r,
|
|
131
|
+
const h = await l.acquire("k", { ttlMs: 50 });
|
|
132
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
133
133
|
await expect(h.renew()).rejects.toThrow();
|
|
134
134
|
await l.close();
|
|
135
135
|
});
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +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":[]}
|
|
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: 50 });\n expect(h1).not.toBeNull();\n await new Promise((r) => setTimeout(r, 150));\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: 200 });\n await new Promise((r) => setTimeout(r, 50));\n await h!.renew(400);\n await new Promise((r) => setTimeout(r, 250));\n // total 300ms in, original TTL would have expired at 200ms.\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: 50 });\n await new Promise((r) => setTimeout(r, 150));\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-cluster",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Cluster Service for ObjectStack — pluggable PubSub/Lock/KV/Counter primitives. Memory driver included; postgres/redis drivers ship separately.",
|
|
6
6
|
"type": "module",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@objectstack/core": "6.
|
|
23
|
-
"@objectstack/spec": "6.
|
|
22
|
+
"@objectstack/core": "6.2.0",
|
|
23
|
+
"@objectstack/spec": "6.2.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^25.9.1",
|
package/src/testing.ts
CHANGED
|
@@ -132,9 +132,9 @@ export function runLockContract(name: string, make: () => Promise<ILock>) {
|
|
|
132
132
|
|
|
133
133
|
it('TTL auto-releases a stuck holder', async () => {
|
|
134
134
|
const l = await make();
|
|
135
|
-
const h1 = await l.acquire('k', { ttlMs:
|
|
135
|
+
const h1 = await l.acquire('k', { ttlMs: 50 });
|
|
136
136
|
expect(h1).not.toBeNull();
|
|
137
|
-
await new Promise((r) => setTimeout(r,
|
|
137
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
138
138
|
expect(h1!.isHeld()).toBe(false);
|
|
139
139
|
const h2 = await l.acquire('k');
|
|
140
140
|
expect(h2).not.toBeNull();
|
|
@@ -155,11 +155,11 @@ export function runLockContract(name: string, make: () => Promise<ILock>) {
|
|
|
155
155
|
|
|
156
156
|
it('renew extends the lease', async () => {
|
|
157
157
|
const l = await make();
|
|
158
|
-
const h = await l.acquire('k', { ttlMs:
|
|
159
|
-
await new Promise((r) => setTimeout(r,
|
|
160
|
-
await h!.renew(
|
|
161
|
-
await new Promise((r) => setTimeout(r,
|
|
162
|
-
// total
|
|
158
|
+
const h = await l.acquire('k', { ttlMs: 200 });
|
|
159
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
160
|
+
await h!.renew(400);
|
|
161
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
162
|
+
// total 300ms in, original TTL would have expired at 200ms.
|
|
163
163
|
expect(h!.isHeld()).toBe(true);
|
|
164
164
|
await h!.release();
|
|
165
165
|
await l.close();
|
|
@@ -167,8 +167,8 @@ export function runLockContract(name: string, make: () => Promise<ILock>) {
|
|
|
167
167
|
|
|
168
168
|
it('renew on a lost lock throws', async () => {
|
|
169
169
|
const l = await make();
|
|
170
|
-
const h = await l.acquire('k', { ttlMs:
|
|
171
|
-
await new Promise((r) => setTimeout(r,
|
|
170
|
+
const h = await l.acquire('k', { ttlMs: 50 });
|
|
171
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
172
172
|
await expect(h!.renew()).rejects.toThrow();
|
|
173
173
|
await l.close();
|
|
174
174
|
});
|