@novasamatech/host-substrate-chain-connection 0.7.7 → 0.7.8-0
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.
|
@@ -239,14 +239,19 @@ describe('createChainConnection', () => {
|
|
|
239
239
|
expect(chainA.resume).toHaveBeenCalledTimes(1);
|
|
240
240
|
unlock();
|
|
241
241
|
});
|
|
242
|
-
it('skips providers
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})
|
|
249
|
-
|
|
242
|
+
it('skips non-pausable providers without preventing pause on the rest', async () => {
|
|
243
|
+
// Mixed pool: 'a' is non-pausable, 'b' is pausable. The pausable one must
|
|
244
|
+
// still receive pause/resume even though the other is silently skipped.
|
|
245
|
+
const pausable = createPausableMockProvider();
|
|
246
|
+
const connection = createChainConnection({
|
|
247
|
+
createProvider: c => (c.genesisHash === 'b' ? pausable.provider : createMockProvider().provider),
|
|
248
|
+
});
|
|
249
|
+
await connection.lockApi(testChain('a'));
|
|
250
|
+
await connection.lockApi(testChain('b'));
|
|
251
|
+
connection.pauseAll();
|
|
252
|
+
connection.resumeAll();
|
|
253
|
+
expect(pausable.pause).toHaveBeenCalledTimes(1);
|
|
254
|
+
expect(pausable.resume).toHaveBeenCalledTimes(1);
|
|
250
255
|
});
|
|
251
256
|
it('does not call pause on providers for chains that have been destroyed', async () => {
|
|
252
257
|
const chainA = createPausableMockProvider();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { getSyncProvider } from '@polkadot-api/json-rpc-provider-proxy';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { createPauseController } from './pauseController.js';
|
|
4
|
+
const flushMicroAndTimers = async () => {
|
|
5
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
6
|
+
};
|
|
7
|
+
const req = (id, method, params = []) => ({
|
|
8
|
+
jsonrpc: '2.0',
|
|
9
|
+
id,
|
|
10
|
+
method,
|
|
11
|
+
params,
|
|
12
|
+
});
|
|
13
|
+
const followNotif = (subscription, result) => ({
|
|
14
|
+
jsonrpc: '2.0',
|
|
15
|
+
method: 'chainHead_v1_followEvent',
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
params: { subscription, result },
|
|
18
|
+
});
|
|
19
|
+
describe('pauseController × getSyncProvider integration', () => {
|
|
20
|
+
it('routes follow events to the new listener after pause+resume+refollow (best blocks must keep flowing)', async () => {
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
try {
|
|
23
|
+
const invocations = [];
|
|
24
|
+
const base = (onMessage, onHalt) => {
|
|
25
|
+
const send = vi.fn();
|
|
26
|
+
const disconnect = vi.fn();
|
|
27
|
+
invocations.push({
|
|
28
|
+
onMessage: onMessage,
|
|
29
|
+
onHalt: onHalt,
|
|
30
|
+
send,
|
|
31
|
+
disconnect,
|
|
32
|
+
});
|
|
33
|
+
return { send, disconnect };
|
|
34
|
+
};
|
|
35
|
+
const pc = createPauseController();
|
|
36
|
+
const inner = pc.middleware(base);
|
|
37
|
+
// Wrap the pausable inner in getSyncProvider so we get the same proxy /
|
|
38
|
+
// synthetic-Stop machinery the WS provider gets via getWsProvider.
|
|
39
|
+
const provider = getSyncProvider(onReady => {
|
|
40
|
+
onReady((onMsg, onHalt) => inner(onMsg, onHalt));
|
|
41
|
+
return () => {
|
|
42
|
+
/* no teardown for the scheduling step */
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const events = [];
|
|
46
|
+
const conn = provider(msg => events.push(msg));
|
|
47
|
+
// getSyncProvider schedules input via setTimeout(0); flush it so the
|
|
48
|
+
// base receives onMessage/onHalt and the proxy reaches Connected.
|
|
49
|
+
await flushMicroAndTimers();
|
|
50
|
+
expect(invocations).toHaveLength(1);
|
|
51
|
+
// Initial follow: consumer sends chainHead_v1_follow, the base "chain"
|
|
52
|
+
// assigns sub-id-1, and a bestBlockChanged event arrives.
|
|
53
|
+
conn.send(req(1, 'chainHead_v1_follow', [true]));
|
|
54
|
+
expect(invocations[0].send).toHaveBeenCalledWith(req(1, 'chainHead_v1_follow', [true]));
|
|
55
|
+
invocations[0].onMessage({ jsonrpc: '2.0', id: 1, result: 'sub-id-1' });
|
|
56
|
+
invocations[0].onMessage(followNotif('sub-id-1', { event: 'bestBlockChanged', bestBlockHash: '0xaa' }));
|
|
57
|
+
expect(events).toContainEqual({ jsonrpc: '2.0', id: 1, result: 'sub-id-1' });
|
|
58
|
+
expect(events).toContainEqual(followNotif('sub-id-1', { event: 'bestBlockChanged', bestBlockHash: '0xaa' }));
|
|
59
|
+
// === pause ===
|
|
60
|
+
// pauseController.pause() disconnects the live socket and manually fires
|
|
61
|
+
// onHalt({type:'paused'}). The proxy reacts by synthesizing a stop
|
|
62
|
+
// notification for every active chainHead — which is what surfaces to
|
|
63
|
+
// the consumer as "the follow died, please refollow".
|
|
64
|
+
events.length = 0;
|
|
65
|
+
pc.pause();
|
|
66
|
+
const syntheticStop = events.find(m =>
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
m.method === 'chainHead_v1_follow' &&
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
m.params?.subscription === 'sub-id-1' &&
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
m.params?.result?.event === 'stop');
|
|
73
|
+
expect(syntheticStop).toBeDefined();
|
|
74
|
+
expect(invocations[0].disconnect).toHaveBeenCalledTimes(1);
|
|
75
|
+
// Consumer's substrate-client refollows synchronously inside the stop
|
|
76
|
+
// handler. The new chainHead_v1_follow goes into the proxy's pending
|
|
77
|
+
// queue while the base is paused.
|
|
78
|
+
conn.send(req(2, 'chainHead_v1_follow', [true]));
|
|
79
|
+
expect(invocations[0].send).not.toHaveBeenCalledWith(req(2, 'chainHead_v1_follow', [true]));
|
|
80
|
+
// === resume ===
|
|
81
|
+
pc.resume();
|
|
82
|
+
// After resume, the base is re-invoked (new "connection") and the
|
|
83
|
+
// queued send is flushed. proxy.start() has its own setTimeout backoff;
|
|
84
|
+
// flush all timers so the new connection is established.
|
|
85
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
86
|
+
expect(invocations.length).toBeGreaterThanOrEqual(2);
|
|
87
|
+
const fresh = invocations[invocations.length - 1];
|
|
88
|
+
// The buffered chainHead_v1_follow must hit the fresh base.
|
|
89
|
+
expect(fresh.send).toHaveBeenCalledWith(req(2, 'chainHead_v1_follow', [true]));
|
|
90
|
+
// Server assigns a NEW subId on the new connection.
|
|
91
|
+
fresh.onMessage({ jsonrpc: '2.0', id: 2, result: 'sub-id-2' });
|
|
92
|
+
// bestBlockChanged on sub-id-2 must reach the consumer — this is the
|
|
93
|
+
// assertion that fails if pause/resume drops best-block flow.
|
|
94
|
+
events.length = 0;
|
|
95
|
+
fresh.onMessage(followNotif('sub-id-2', { event: 'bestBlockChanged', bestBlockHash: '0xcc' }));
|
|
96
|
+
expect(events).toContainEqual(followNotif('sub-id-2', { event: 'bestBlockChanged', bestBlockHash: '0xcc' }));
|
|
97
|
+
// And no stale sub-id-1 events should have leaked through after refollow.
|
|
98
|
+
expect(events.some(m =>
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
m.method === 'chainHead_v1_follow' && m.params?.subscription === 'sub-id-1')).toBe(false);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
vi.useRealTimers();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/host-substrate-chain-connection",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.8-0",
|
|
5
5
|
"description": "Chain connection pool with ref counting and provider branching for Polkadot API",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@novasamatech/storage-adapter": "0.7.
|
|
28
|
+
"@novasamatech/storage-adapter": "0.7.8-0",
|
|
29
29
|
"@polkadot-api/ws-provider": "^0.9.0",
|
|
30
30
|
"@polkadot-api/json-rpc-provider": "^0.2.0",
|
|
31
31
|
"@polkadot-api/json-rpc-provider-proxy": "^0.4.0",
|