@novasamatech/host-substrate-chain-connection 0.9.2 → 0.9.4
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/README.md +1 -0
- package/dist/connectionPool.d.ts +3 -1
- package/dist/connectionPool.js +2 -2
- package/dist/connectionPool.spec.js +31 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -86,6 +86,7 @@ function createChainConnection<C extends ChainConfig, T = PolkadotClient>(
|
|
|
86
86
|
|---|---|---|
|
|
87
87
|
| `createProvider` | `(chain: C, onStatusChanged: (status: ConnectionStatus) => void) => JsonRpcProvider` | Factory for the underlying JSON-RPC transport. Called once per chain. Use `onStatusChanged` to feed connection status back into the pool. |
|
|
88
88
|
| `clientOptions` | `(chain: C) => ClientOptions` | Optional. Returns [polkadot-api client options](https://papi.how/) - typically metadata cache hooks (`getMetadata` / `setMetadata`). |
|
|
89
|
+
| `createClient` | `typeof createClient` | Optional. Overrides polkadot-api's `createClient`. Defaults to it; an injection point for tests and for wrapping client construction. |
|
|
89
90
|
| `resolve` | `(chain: C, client: PolkadotClient) => Promise<T>` | Optional. Transforms the raw `PolkadotClient` into your app's API type. The result is cached per chain. If omitted, `T` defaults to `PolkadotClient`. |
|
|
90
91
|
| `destroyDelay` | `number` | Optional. Milliseconds to wait before destroying a connection after the last caller releases. Defaults to `0` (destroy immediately). Useful to avoid reconnect churn when callers release and re-acquire in quick succession. |
|
|
91
92
|
|
package/dist/connectionPool.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { ChainConfig, ConnectionStatus } from './types.js';
|
|
|
4
4
|
export type ChainConnectionConfig<C extends ChainConfig, T = PolkadotClient> = {
|
|
5
5
|
createProvider(chain: C, onStatusChanged: (status: ConnectionStatus) => void): JsonRpcProvider;
|
|
6
6
|
clientOptions?(chain: C): Parameters<typeof createClient>[1];
|
|
7
|
+
/** Defaults to polkadot-api's `createClient`. An injection point for tests and for wrapping client construction — polkadot-api is imported either way. */
|
|
8
|
+
createClient?: typeof createClient;
|
|
7
9
|
resolve?(chain: C, client: PolkadotClient): Promise<T>;
|
|
8
10
|
destroyDelay?: number;
|
|
9
11
|
};
|
|
@@ -24,4 +26,4 @@ export type ChainConnection<C extends ChainConfig, T = PolkadotClient> = {
|
|
|
24
26
|
pauseAll(): void;
|
|
25
27
|
resumeAll(): void;
|
|
26
28
|
};
|
|
27
|
-
export declare const createChainConnection: <C extends ChainConfig, T = PolkadotClient>({ resolve, clientOptions, createProvider, destroyDelay, }: ChainConnectionConfig<C, T>) => ChainConnection<C, T>;
|
|
29
|
+
export declare const createChainConnection: <C extends ChainConfig, T = PolkadotClient>({ resolve, clientOptions, createProvider, createClient: makeClient, destroyDelay, }: ChainConnectionConfig<C, T>) => ChainConnection<C, T>;
|
package/dist/connectionPool.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createBranchedProvider } from './branchedProvider.js';
|
|
|
4
4
|
import { createConnectionManager } from './connectionManager.js';
|
|
5
5
|
import { createRefCounter } from './refCounter.js';
|
|
6
6
|
import { isPausable } from './wsProvider.js';
|
|
7
|
-
export const createChainConnection = ({ resolve, clientOptions, createProvider, destroyDelay = 0, }) => {
|
|
7
|
+
export const createChainConnection = ({ resolve, clientOptions, createProvider, createClient: makeClient = createClient, destroyDelay = 0, }) => {
|
|
8
8
|
const connections = createConnectionManager();
|
|
9
9
|
const refCounter = createRefCounter();
|
|
10
10
|
const existingClients = new Map();
|
|
@@ -25,7 +25,7 @@ export const createChainConnection = ({ resolve, clientOptions, createProvider,
|
|
|
25
25
|
return existing;
|
|
26
26
|
const rootProvider = createProvider(chain, status => connections.update(chain.genesisHash, status));
|
|
27
27
|
const branchedProvider = createBranchedProvider(rootProvider);
|
|
28
|
-
const client =
|
|
28
|
+
const client = makeClient(branchedProvider.branch(), clientOptions?.(chain));
|
|
29
29
|
const pooled = { client, provider: branchedProvider, rootProvider };
|
|
30
30
|
existingClients.set(chain.genesisHash, pooled);
|
|
31
31
|
return pooled;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { createChainConnection } from './connectionPool.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
3
|
+
// An in-memory stand-in for polkadot-api's `createClient`: one client object per
|
|
4
|
+
// call, each counting its own `destroy()`.
|
|
5
|
+
const createClientFactory = () => {
|
|
6
|
+
const clients = [];
|
|
7
|
+
const factory = ((_provider, _options) => {
|
|
8
|
+
const client = { destroyed: 0 };
|
|
9
|
+
clients.push(client);
|
|
10
|
+
return { destroy: () => client.destroyed++ };
|
|
11
|
+
});
|
|
12
|
+
return { createClient: factory, last: () => clients[clients.length - 1] };
|
|
13
|
+
};
|
|
7
14
|
const createMockProvider = () => {
|
|
8
15
|
const send = vi.fn();
|
|
9
16
|
const disconnect = vi.fn();
|
|
@@ -20,11 +27,13 @@ const createMockProvider = () => {
|
|
|
20
27
|
const testChain = (id) => ({ genesisHash: id });
|
|
21
28
|
const createTestConnection = (overrides) => {
|
|
22
29
|
const mockProvider = createMockProvider();
|
|
30
|
+
const clientFactory = createClientFactory();
|
|
23
31
|
const connection = createChainConnection({
|
|
24
32
|
createProvider: () => mockProvider.provider,
|
|
33
|
+
createClient: clientFactory.createClient,
|
|
25
34
|
...overrides,
|
|
26
35
|
});
|
|
27
|
-
return { connection, mockProvider };
|
|
36
|
+
return { connection, mockProvider, clients: clientFactory };
|
|
28
37
|
};
|
|
29
38
|
describe('createChainConnection', () => {
|
|
30
39
|
describe('lockApi', () => {
|
|
@@ -141,30 +150,20 @@ describe('createChainConnection', () => {
|
|
|
141
150
|
});
|
|
142
151
|
describe('lockApi — connection lifecycle', () => {
|
|
143
152
|
it('destroys client synchronously when last lock is released (no destroyDelay)', async () => {
|
|
144
|
-
const
|
|
145
|
-
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
146
|
-
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
147
|
-
destroy: destroyFn,
|
|
148
|
-
});
|
|
149
|
-
const { connection } = createTestConnection();
|
|
153
|
+
const { connection, clients } = createTestConnection();
|
|
150
154
|
const { unlock } = await connection.lockApi(testChain('a'));
|
|
151
155
|
unlock();
|
|
152
|
-
expect(
|
|
156
|
+
expect(clients.last().destroyed).toBe(1);
|
|
153
157
|
});
|
|
154
158
|
it('does not destroy while any lock is still held', async () => {
|
|
155
|
-
const
|
|
156
|
-
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
157
|
-
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
158
|
-
destroy: destroyFn,
|
|
159
|
-
});
|
|
160
|
-
const { connection } = createTestConnection();
|
|
159
|
+
const { connection, clients } = createTestConnection();
|
|
161
160
|
const chain = testChain('a');
|
|
162
161
|
const { unlock: u1 } = await connection.lockApi(chain);
|
|
163
162
|
const { unlock: u2 } = await connection.lockApi(chain);
|
|
164
163
|
u1();
|
|
165
|
-
expect(
|
|
164
|
+
expect(clients.last().destroyed).toBe(0);
|
|
166
165
|
u2();
|
|
167
|
-
expect(
|
|
166
|
+
expect(clients.last().destroyed).toBe(1);
|
|
168
167
|
});
|
|
169
168
|
describe('with destroyDelay', () => {
|
|
170
169
|
beforeEach(() => {
|
|
@@ -174,32 +173,22 @@ describe('createChainConnection', () => {
|
|
|
174
173
|
vi.useRealTimers();
|
|
175
174
|
});
|
|
176
175
|
it('defers destruction by destroyDelay ms', async () => {
|
|
177
|
-
const
|
|
178
|
-
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
179
|
-
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
180
|
-
destroy: destroyFn,
|
|
181
|
-
});
|
|
182
|
-
const { connection } = createTestConnection({ destroyDelay: 1000 });
|
|
176
|
+
const { connection, clients } = createTestConnection({ destroyDelay: 1000 });
|
|
183
177
|
const { unlock } = await connection.lockApi(testChain('a'));
|
|
184
178
|
unlock();
|
|
185
|
-
expect(
|
|
179
|
+
expect(clients.last().destroyed).toBe(0);
|
|
186
180
|
vi.advanceTimersByTime(1000);
|
|
187
|
-
expect(
|
|
181
|
+
expect(clients.last().destroyed).toBe(1);
|
|
188
182
|
});
|
|
189
183
|
it('cancels destruction timer when connection is re-acquired before delay elapses', async () => {
|
|
190
|
-
const
|
|
191
|
-
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
192
|
-
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
193
|
-
destroy: destroyFn,
|
|
194
|
-
});
|
|
195
|
-
const { connection } = createTestConnection({ destroyDelay: 1000 });
|
|
184
|
+
const { connection, clients } = createTestConnection({ destroyDelay: 1000 });
|
|
196
185
|
const chain = testChain('a');
|
|
197
186
|
const { api: api1, unlock: u1 } = await connection.lockApi(chain);
|
|
198
187
|
u1();
|
|
199
188
|
// Re-acquire before timer fires — same client must be returned, no destruction
|
|
200
189
|
const { api: api2, unlock: u2 } = await connection.lockApi(chain);
|
|
201
190
|
vi.advanceTimersByTime(1000);
|
|
202
|
-
expect(
|
|
191
|
+
expect(clients.last().destroyed).toBe(0);
|
|
203
192
|
expect(api1).toBe(api2);
|
|
204
193
|
u2();
|
|
205
194
|
});
|
|
@@ -221,6 +210,7 @@ describe('createChainConnection', () => {
|
|
|
221
210
|
const providerByChain = { a: chainA.provider, b: chainB.provider };
|
|
222
211
|
const connection = createChainConnection({
|
|
223
212
|
createProvider: chain => providerByChain[chain.genesisHash],
|
|
213
|
+
createClient: createClientFactory().createClient,
|
|
224
214
|
});
|
|
225
215
|
const { unlock: u1 } = await connection.lockApi(testChain('a'));
|
|
226
216
|
const { unlock: u2 } = await connection.lockApi(testChain('b'));
|
|
@@ -232,7 +222,10 @@ describe('createChainConnection', () => {
|
|
|
232
222
|
});
|
|
233
223
|
it('calls resume on every pausable provider', async () => {
|
|
234
224
|
const chainA = createPausableMockProvider();
|
|
235
|
-
const connection = createChainConnection({
|
|
225
|
+
const connection = createChainConnection({
|
|
226
|
+
createProvider: () => chainA.provider,
|
|
227
|
+
createClient: createClientFactory().createClient,
|
|
228
|
+
});
|
|
236
229
|
const { unlock } = await connection.lockApi(testChain('a'));
|
|
237
230
|
connection.pauseAll();
|
|
238
231
|
connection.resumeAll();
|
|
@@ -245,6 +238,7 @@ describe('createChainConnection', () => {
|
|
|
245
238
|
const pausable = createPausableMockProvider();
|
|
246
239
|
const connection = createChainConnection({
|
|
247
240
|
createProvider: c => (c.genesisHash === 'b' ? pausable.provider : createMockProvider().provider),
|
|
241
|
+
createClient: createClientFactory().createClient,
|
|
248
242
|
});
|
|
249
243
|
await connection.lockApi(testChain('a'));
|
|
250
244
|
await connection.lockApi(testChain('b'));
|
|
@@ -257,6 +251,7 @@ describe('createChainConnection', () => {
|
|
|
257
251
|
const chainA = createPausableMockProvider();
|
|
258
252
|
const connection = createChainConnection({
|
|
259
253
|
createProvider: () => chainA.provider,
|
|
254
|
+
createClient: createClientFactory().createClient,
|
|
260
255
|
destroyDelay: 0,
|
|
261
256
|
});
|
|
262
257
|
const { unlock } = await connection.lockApi(testChain('a'));
|
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.9.
|
|
4
|
+
"version": "0.9.4",
|
|
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.9.
|
|
28
|
+
"@novasamatech/storage-adapter": "0.9.4",
|
|
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",
|