@novasamatech/host-substrate-chain-connection 0.6.11 → 0.6.13
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 +63 -81
- package/dist/branchedProvider.d.ts +5 -1
- package/dist/branchedProvider.js +5 -3
- package/dist/branchedProvider.spec.js +8 -0
- package/dist/connectionPool.d.ts +7 -7
- package/dist/connectionPool.js +50 -40
- package/dist/connectionPool.spec.js +69 -56
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +2 -4
- package/dist/wsProvider.d.ts +6 -0
- package/dist/wsProvider.js +35 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -25,33 +25,32 @@ import {
|
|
|
25
25
|
import { dot } from '@polkadot-api/descriptors';
|
|
26
26
|
|
|
27
27
|
const polkadot: ChainConfig = {
|
|
28
|
-
|
|
29
|
-
nodes: [
|
|
28
|
+
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
|
|
29
|
+
nodes: ['wss://rpc.polkadot.io'],
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const chains = createChainConnection({
|
|
33
33
|
createProvider: (chain, onStatusChanged) =>
|
|
34
34
|
createWsJsonRpcProvider({
|
|
35
|
-
endpoints: chain.nodes
|
|
35
|
+
endpoints: chain.nodes,
|
|
36
36
|
onStatusChanged,
|
|
37
37
|
}),
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
// Acquire a connection, run a query, then release
|
|
41
|
+
const { api: client, unlock } = await chains.lockApi(polkadot);
|
|
42
|
+
const api = client.getTypedApi(dot);
|
|
43
|
+
const account = await api.query.System.Account.getValue('5GrwvaEF...');
|
|
44
|
+
unlock();
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## Table of contents
|
|
48
48
|
|
|
49
49
|
- [API](#api)
|
|
50
50
|
- [`createChainConnection`](#createchainconnectionconfig)
|
|
51
|
-
- [`requestApi`](#requestapichain-callback)
|
|
52
51
|
- [`lockApi`](#lockapichain)
|
|
53
52
|
- [`getProvider`](#getproviderchain)
|
|
54
|
-
- [`status` / `onStatusChanged`](#
|
|
53
|
+
- [`status` / `onStatusChanged`](#statusgenesishash--onstatuschangedgenesishash-callback)
|
|
55
54
|
- [`createWsJsonRpcProvider`](#createwsjsonrpcprovideroptions)
|
|
56
55
|
- [`createMetadataCache`](#createmetadatacacheoptions)
|
|
57
56
|
- [Recipes](#recipes)
|
|
@@ -82,38 +81,18 @@ function createChainConnection<C extends ChainConfig, T = PolkadotClient>(
|
|
|
82
81
|
| `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. |
|
|
83
82
|
| `clientOptions` | `(chain: C) => ClientOptions` | Optional. Returns [polkadot-api client options](https://papi.how/) - typically metadata cache hooks (`getMetadata` / `setMetadata`). |
|
|
84
83
|
| `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`. |
|
|
84
|
+
| `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. |
|
|
85
85
|
|
|
86
86
|
**`ChainConfig`** - minimum shape your chain objects must satisfy:
|
|
87
87
|
|
|
88
88
|
```ts
|
|
89
89
|
type ChainConfig = {
|
|
90
|
-
|
|
91
|
-
nodes:
|
|
90
|
+
genesisHash: string;
|
|
91
|
+
nodes: string[];
|
|
92
92
|
};
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
Returns a [`ChainConnection<C, T>`](#
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
### `requestApi(chain, callback)`
|
|
100
|
-
|
|
101
|
-
Acquires a connection, runs the callback, and releases automatically when it settles. Best for one-shot queries.
|
|
102
|
-
|
|
103
|
-
**Signature:**
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
requestApi<Return>(chain: C, callback: (api: T) => Return): Promise<Awaited<Return>>
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Example:**
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
const account = await chains.requestApi(polkadot, async (client) => {
|
|
113
|
-
const api = client.getTypedApi(dot);
|
|
114
|
-
return api.query.System.Account.getValue('5GrwvaEF...');
|
|
115
|
-
});
|
|
116
|
-
```
|
|
95
|
+
Returns a [`ChainConnection<C, T>`](#lockapichain) with the methods below.
|
|
117
96
|
|
|
118
97
|
---
|
|
119
98
|
|
|
@@ -124,7 +103,7 @@ Acquires a connection and holds it until `unlock()` is called. Use for subscript
|
|
|
124
103
|
**Signature:**
|
|
125
104
|
|
|
126
105
|
```ts
|
|
127
|
-
lockApi(chain: C): Promise<{ api: T; unlock: VoidFunction }>
|
|
106
|
+
function lockApi(chain: C): Promise<{ api: T; unlock: VoidFunction }>
|
|
128
107
|
```
|
|
129
108
|
|
|
130
109
|
**Example:**
|
|
@@ -157,7 +136,7 @@ Useful for passing a provider to an iframe, webview, or any library that expects
|
|
|
157
136
|
**Signature:**
|
|
158
137
|
|
|
159
138
|
```ts
|
|
160
|
-
getProvider(chain: C): JsonRpcProvider
|
|
139
|
+
function getProvider(chain: C): JsonRpcProvider
|
|
161
140
|
```
|
|
162
141
|
|
|
163
142
|
**Example:**
|
|
@@ -169,25 +148,25 @@ const provider = chains.getProvider(polkadot);
|
|
|
169
148
|
|
|
170
149
|
---
|
|
171
150
|
|
|
172
|
-
### `status(
|
|
151
|
+
### `status(genesisHash)` / `onStatusChanged(genesisHash, callback)`
|
|
173
152
|
|
|
174
|
-
Read or subscribe to connection status. Both take a `
|
|
153
|
+
Read or subscribe to connection status. Both take a `genesisHash` string. Returns `'disconnected'` for chains that have never been connected.
|
|
175
154
|
|
|
176
155
|
**Signature:**
|
|
177
156
|
|
|
178
157
|
```ts
|
|
179
|
-
status(
|
|
158
|
+
function status(genesisHash: string): ConnectionStatus
|
|
180
159
|
// ConnectionStatus = 'connecting' | 'connected' | 'disconnected'
|
|
181
160
|
|
|
182
|
-
onStatusChanged(
|
|
161
|
+
function onStatusChanged(genesisHash: string, callback: (status: ConnectionStatus) => void): VoidFunction
|
|
183
162
|
```
|
|
184
163
|
|
|
185
164
|
**Example:**
|
|
186
165
|
|
|
187
166
|
```ts
|
|
188
|
-
const currentStatus = chains.status(polkadot.
|
|
167
|
+
const currentStatus = chains.status(polkadot.genesisHash);
|
|
189
168
|
|
|
190
|
-
const unsubscribe = chains.onStatusChanged(polkadot.
|
|
169
|
+
const unsubscribe = chains.onStatusChanged(polkadot.genesisHash, (status) => {
|
|
191
170
|
console.info('Polkadot:', status);
|
|
192
171
|
});
|
|
193
172
|
|
|
@@ -199,7 +178,7 @@ unsubscribe();
|
|
|
199
178
|
|
|
200
179
|
### `createWsJsonRpcProvider(options)`
|
|
201
180
|
|
|
202
|
-
WebSocket provider factory. Wraps polkadot-api's `getWsProvider`
|
|
181
|
+
WebSocket provider factory. Wraps polkadot-api's `getWsProvider` and translates WebSocket events to `ConnectionStatus`. Active JSON-RPC subscriptions are automatically replayed after a reconnect — consumers using `getProvider` don't need to handle reconnects manually.
|
|
203
182
|
|
|
204
183
|
**Signature:**
|
|
205
184
|
|
|
@@ -231,7 +210,7 @@ Caches chain metadata in memory, with optional persistence via a `StorageAdapter
|
|
|
231
210
|
function createMetadataCache(options?: { storage?: StorageAdapter }): MetadataCache;
|
|
232
211
|
|
|
233
212
|
type MetadataCache = {
|
|
234
|
-
forChain(
|
|
213
|
+
forChain(genesisHash: string): ClientOptions;
|
|
235
214
|
};
|
|
236
215
|
```
|
|
237
216
|
|
|
@@ -268,22 +247,25 @@ type ResolvedApi = {
|
|
|
268
247
|
};
|
|
269
248
|
|
|
270
249
|
const chains = createChainConnection<ChainConfig, ResolvedApi>({
|
|
271
|
-
createProvider
|
|
272
|
-
createWsJsonRpcProvider({
|
|
273
|
-
endpoints: chain.nodes
|
|
250
|
+
createProvider(chain, onStatusChanged) {
|
|
251
|
+
return createWsJsonRpcProvider({
|
|
252
|
+
endpoints: chain.nodes,
|
|
274
253
|
onStatusChanged,
|
|
275
|
-
})
|
|
254
|
+
});
|
|
255
|
+
},
|
|
276
256
|
|
|
277
|
-
resolve
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
257
|
+
async resolve (_chain, client) {
|
|
258
|
+
return {
|
|
259
|
+
api: client.getTypedApi(dot),
|
|
260
|
+
client,
|
|
261
|
+
};
|
|
262
|
+
},
|
|
281
263
|
});
|
|
282
264
|
|
|
283
|
-
// Now
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
265
|
+
// Now lockApi returns ResolvedApi instead of PolkadotClient
|
|
266
|
+
const { api: resolved, unlock } = await chains.lockApi(polkadot);
|
|
267
|
+
const account = await resolved.api.query.System.Account.getValue('5GrwvaEF...');
|
|
268
|
+
unlock();
|
|
287
269
|
```
|
|
288
270
|
|
|
289
271
|
---
|
|
@@ -300,10 +282,10 @@ const metadataCache = createMetadataCache({
|
|
|
300
282
|
const chains = createChainConnection({
|
|
301
283
|
createProvider: (chain, onStatusChanged) =>
|
|
302
284
|
createWsJsonRpcProvider({
|
|
303
|
-
endpoints: chain.nodes
|
|
285
|
+
endpoints: chain.nodes,
|
|
304
286
|
onStatusChanged,
|
|
305
287
|
}),
|
|
306
|
-
clientOptions: (chain) => metadataCache.forChain(chain.
|
|
288
|
+
clientOptions: (chain) => metadataCache.forChain(chain.genesisHash),
|
|
307
289
|
});
|
|
308
290
|
```
|
|
309
291
|
|
|
@@ -334,7 +316,7 @@ const lightClientChainSpecs: Record<string, () => Promise<{ chainSpec: string }>
|
|
|
334
316
|
let smoldot: SmoldotClient | null = null;
|
|
335
317
|
|
|
336
318
|
const createLightClientProvider = (chain: MyChain): JsonRpcProvider => {
|
|
337
|
-
const getChainSpec = lightClientChainSpecs[chain.
|
|
319
|
+
const getChainSpec = lightClientChainSpecs[chain.genesisHash];
|
|
338
320
|
if (!getChainSpec) {
|
|
339
321
|
throw new Error(`Light client for chain "${chain.name}" is not supported`);
|
|
340
322
|
}
|
|
@@ -351,14 +333,14 @@ const createLightClientProvider = (chain: MyChain): JsonRpcProvider => {
|
|
|
351
333
|
|
|
352
334
|
const chains = createChainConnection<MyChain>({
|
|
353
335
|
createProvider: (chain, onStatusChanged) => {
|
|
354
|
-
if (chain.lightClient && chain.
|
|
336
|
+
if (chain.lightClient && chain.genesisHash in lightClientChainSpecs) {
|
|
355
337
|
// Light clients report connected immediately - Smoldot handles syncing internally.
|
|
356
338
|
onStatusChanged('connected');
|
|
357
339
|
return createLightClientProvider(chain);
|
|
358
340
|
}
|
|
359
341
|
|
|
360
342
|
return createWsJsonRpcProvider({
|
|
361
|
-
endpoints: chain.nodes
|
|
343
|
+
endpoints: chain.nodes,
|
|
362
344
|
onStatusChanged,
|
|
363
345
|
});
|
|
364
346
|
},
|
|
@@ -382,8 +364,8 @@ import { dot, ksm, type DotDescriptor, type KsmDescriptor } from '@polkadot-api/
|
|
|
382
364
|
import { type ChainDefinition, type CompatibilityToken, type TypedApi, getTypedCodecs } from 'polkadot-api';
|
|
383
365
|
|
|
384
366
|
const descriptorMap: Record<string, ChainDefinition> = {
|
|
385
|
-
[polkadot.
|
|
386
|
-
[kusama.
|
|
367
|
+
[polkadot.genesisHash]: dot,
|
|
368
|
+
[kusama.genesisHash]: ksm,
|
|
387
369
|
};
|
|
388
370
|
|
|
389
371
|
type ResolvedApi = {
|
|
@@ -396,12 +378,12 @@ type ResolvedApi = {
|
|
|
396
378
|
const chains = createChainConnection<MyChain, ResolvedApi>({
|
|
397
379
|
createProvider: (chain, onStatusChanged) =>
|
|
398
380
|
createWsJsonRpcProvider({
|
|
399
|
-
endpoints: chain.nodes
|
|
381
|
+
endpoints: chain.nodes,
|
|
400
382
|
onStatusChanged,
|
|
401
383
|
}),
|
|
402
384
|
|
|
403
385
|
resolve: async (chain, client) => {
|
|
404
|
-
const descriptor = descriptorMap[chain.
|
|
386
|
+
const descriptor = descriptorMap[chain.genesisHash];
|
|
405
387
|
const api = client.getTypedApi(descriptor);
|
|
406
388
|
|
|
407
389
|
// Pre-resolve once - these require async metadata fetches
|
|
@@ -454,7 +436,7 @@ graph LR
|
|
|
454
436
|
end
|
|
455
437
|
|
|
456
438
|
subgraph Host App
|
|
457
|
-
HA["
|
|
439
|
+
HA["lockApi()"]
|
|
458
440
|
Container
|
|
459
441
|
end
|
|
460
442
|
|
|
@@ -473,7 +455,7 @@ graph LR
|
|
|
473
455
|
|
|
474
456
|
Products embedded in iframes or webviews don't connect to RPC nodes directly. They send `remote_chain_*` requests to the **Container**, which obtains a provider from the **Connection Pool** via `getProvider(chain)`.
|
|
475
457
|
|
|
476
|
-
The host app's own code uses the same pool through `
|
|
458
|
+
The host app's own code uses the same pool through `lockApi` and `getProvider`. Everyone shares the same underlying connections - one per chain. The pool opens a connection on first use and closes it when the last consumer releases.
|
|
477
459
|
|
|
478
460
|
## Full example
|
|
479
461
|
|
|
@@ -515,7 +497,7 @@ type Chain = ChainConfig & {
|
|
|
515
497
|
// ---------------------------------------------------------------------------
|
|
516
498
|
// 2. Descriptor resolution
|
|
517
499
|
//
|
|
518
|
-
// Maps specName → default descriptor, with
|
|
500
|
+
// Maps specName → default descriptor, with genesisHash overrides for
|
|
519
501
|
// parachains that share a specName with their relay chain.
|
|
520
502
|
// ---------------------------------------------------------------------------
|
|
521
503
|
|
|
@@ -539,7 +521,7 @@ const specNameDefaults: Record<string, Descriptor> = {
|
|
|
539
521
|
};
|
|
540
522
|
|
|
541
523
|
const getDescriptor = (chain: Chain): Descriptor => {
|
|
542
|
-
return parachainOverrides[chain.
|
|
524
|
+
return parachainOverrides[chain.genesisHash]
|
|
543
525
|
?? specNameDefaults[chain.specName]
|
|
544
526
|
?? { type: 'dot', def: dot };
|
|
545
527
|
};
|
|
@@ -558,8 +540,8 @@ const lightClientChainSpecs: Record<string, () => Promise<{ chainSpec: string }>
|
|
|
558
540
|
|
|
559
541
|
let smoldot: SmoldotClient | null = null;
|
|
560
542
|
|
|
561
|
-
const createLightClientProvider = (
|
|
562
|
-
const getChainSpec = lightClientChainSpecs[
|
|
543
|
+
const createLightClientProvider = (genesisHash: string): JsonRpcProvider => {
|
|
544
|
+
const getChainSpec = lightClientChainSpecs[genesisHash]!;
|
|
563
545
|
|
|
564
546
|
const smoldotChain = getChainSpec().then(({ chainSpec }) => {
|
|
565
547
|
if (!smoldot) {
|
|
@@ -600,18 +582,18 @@ type TypedClient = {
|
|
|
600
582
|
|
|
601
583
|
const chains = createChainConnection<Chain, TypedClient>({
|
|
602
584
|
createProvider: (chain, onStatusChanged) => {
|
|
603
|
-
if (chain.
|
|
585
|
+
if (chain.genesisHash in lightClientChainSpecs) {
|
|
604
586
|
onStatusChanged('connected');
|
|
605
|
-
return createLightClientProvider(chain.
|
|
587
|
+
return createLightClientProvider(chain.genesisHash);
|
|
606
588
|
}
|
|
607
589
|
|
|
608
590
|
return createWsJsonRpcProvider({
|
|
609
|
-
endpoints: chain.nodes
|
|
591
|
+
endpoints: chain.nodes,
|
|
610
592
|
onStatusChanged,
|
|
611
593
|
});
|
|
612
594
|
},
|
|
613
595
|
|
|
614
|
-
clientOptions: (chain) => metadataCache.forChain(chain.
|
|
596
|
+
clientOptions: (chain) => metadataCache.forChain(chain.genesisHash),
|
|
615
597
|
|
|
616
598
|
resolve: async (chain, client) => {
|
|
617
599
|
const { type, def } = getDescriptor(chain);
|
|
@@ -631,16 +613,16 @@ const chains = createChainConnection<Chain, TypedClient>({
|
|
|
631
613
|
// ---------------------------------------------------------------------------
|
|
632
614
|
|
|
633
615
|
const polkadot: Chain = {
|
|
634
|
-
|
|
616
|
+
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
|
|
635
617
|
specName: 'polkadot',
|
|
636
618
|
name: 'Polkadot',
|
|
637
|
-
nodes: [
|
|
619
|
+
nodes: ['wss://rpc.polkadot.io', 'wss://polkadot-rpc.dwellir.com'],
|
|
638
620
|
};
|
|
639
621
|
|
|
640
622
|
// One-shot query
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
623
|
+
const { api: queried, unlock: unlockQuery } = await chains.lockApi(polkadot);
|
|
624
|
+
const account = await queried.api.query.System.Account.getValue('5GrwvaEF...');
|
|
625
|
+
unlockQuery();
|
|
644
626
|
|
|
645
627
|
// Long-lived subscription
|
|
646
628
|
const { api: resolved, unlock } = await chains.lockApi(polkadot);
|
|
@@ -654,7 +636,7 @@ sub.unsubscribe();
|
|
|
654
636
|
unlock();
|
|
655
637
|
|
|
656
638
|
// Connection status
|
|
657
|
-
const unsubscribe = chains.onStatusChanged(polkadot.
|
|
639
|
+
const unsubscribe = chains.onStatusChanged(polkadot.genesisHash, (status) => {
|
|
658
640
|
console.info(`${polkadot.name}:`, status);
|
|
659
641
|
});
|
|
660
642
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { JsonRpcProvider } from '@polkadot-api/json-rpc-provider';
|
|
2
2
|
import type { BranchedProvider } from './types.js';
|
|
3
|
-
|
|
3
|
+
type Params = {
|
|
4
|
+
enhanceBranch?(branch: JsonRpcProvider): JsonRpcProvider;
|
|
5
|
+
};
|
|
6
|
+
export declare const createBranchedProvider: (provider: JsonRpcProvider, params?: Params) => BranchedProvider;
|
|
7
|
+
export {};
|
package/dist/branchedProvider.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createNanoEvents } from 'nanoevents';
|
|
2
|
+
import { id } from './helpers.js';
|
|
2
3
|
import { createRefCounter } from './refCounter.js';
|
|
3
|
-
export const createBranchedProvider = (provider) => {
|
|
4
|
+
export const createBranchedProvider = (provider, params) => {
|
|
5
|
+
const enhancer = params?.enhanceBranch ?? id;
|
|
4
6
|
const messages = createNanoEvents();
|
|
5
7
|
const refs = createRefCounter();
|
|
6
8
|
let connection = null;
|
|
7
9
|
return {
|
|
8
10
|
branch(onDisconnect) {
|
|
9
|
-
return onMessage => {
|
|
11
|
+
return enhancer(onMessage => {
|
|
10
12
|
if (!connection) {
|
|
11
13
|
connection = provider(message => messages.emit('incoming', message));
|
|
12
14
|
}
|
|
@@ -25,7 +27,7 @@ export const createBranchedProvider = (provider) => {
|
|
|
25
27
|
unsub();
|
|
26
28
|
},
|
|
27
29
|
};
|
|
28
|
-
};
|
|
30
|
+
});
|
|
29
31
|
},
|
|
30
32
|
};
|
|
31
33
|
};
|
|
@@ -99,4 +99,12 @@ describe('createBranchedProvider', () => {
|
|
|
99
99
|
conn.send('{"method":"test"}');
|
|
100
100
|
expect(mock.send).toHaveBeenCalledWith('{"method":"test"}');
|
|
101
101
|
});
|
|
102
|
+
it('calls enhanceBranch for each new branch independently', () => {
|
|
103
|
+
const mock = createMockProvider();
|
|
104
|
+
const enhance = vi.fn((p) => p);
|
|
105
|
+
const branched = createBranchedProvider(mock.provider, { enhanceBranch: enhance });
|
|
106
|
+
branched.branch()(vi.fn());
|
|
107
|
+
branched.branch()(vi.fn());
|
|
108
|
+
expect(enhance).toHaveBeenCalledTimes(2);
|
|
109
|
+
});
|
|
102
110
|
});
|
package/dist/connectionPool.d.ts
CHANGED
|
@@ -3,18 +3,18 @@ import type { PolkadotClient } from 'polkadot-api';
|
|
|
3
3
|
import { createClient } from 'polkadot-api';
|
|
4
4
|
import type { ChainConfig, ConnectionStatus } from './types.js';
|
|
5
5
|
export type ChainConnectionConfig<C extends ChainConfig, T = PolkadotClient> = {
|
|
6
|
-
createProvider
|
|
7
|
-
clientOptions
|
|
8
|
-
resolve
|
|
6
|
+
createProvider(chain: C, onStatusChanged: (status: ConnectionStatus) => void): JsonRpcProvider;
|
|
7
|
+
clientOptions?(chain: C): Parameters<typeof createClient>[1];
|
|
8
|
+
resolve?(chain: C, client: PolkadotClient): Promise<T>;
|
|
9
|
+
destroyDelay?: number;
|
|
9
10
|
};
|
|
10
11
|
export type ChainConnection<C extends ChainConfig, T = PolkadotClient> = {
|
|
11
12
|
lockApi(chain: C): Promise<{
|
|
12
13
|
api: T;
|
|
13
14
|
unlock: VoidFunction;
|
|
14
15
|
}>;
|
|
15
|
-
requestApi<Return>(chain: C, callback: (api: T) => Return): Promise<Awaited<Return>>;
|
|
16
16
|
getProvider(chain: C): JsonRpcProvider;
|
|
17
|
-
status(
|
|
18
|
-
onStatusChanged(
|
|
17
|
+
status(genesisHash: string): ConnectionStatus;
|
|
18
|
+
onStatusChanged(genesisHash: string, callback: (status: ConnectionStatus) => void): VoidFunction;
|
|
19
19
|
};
|
|
20
|
-
export declare const createChainConnection: <C extends ChainConfig, T = PolkadotClient>(
|
|
20
|
+
export declare const createChainConnection: <C extends ChainConfig, T = PolkadotClient>({ resolve, clientOptions, createProvider, destroyDelay, }: ChainConnectionConfig<C, T>) => ChainConnection<C, T>;
|
package/dist/connectionPool.js
CHANGED
|
@@ -3,73 +3,93 @@ import { createClient } from 'polkadot-api';
|
|
|
3
3
|
import { createBranchedProvider } from './branchedProvider.js';
|
|
4
4
|
import { createConnectionManager } from './connectionManager.js';
|
|
5
5
|
import { createRefCounter } from './refCounter.js';
|
|
6
|
-
export const createChainConnection = (
|
|
6
|
+
export const createChainConnection = ({ resolve, clientOptions, createProvider, destroyDelay = 0, }) => {
|
|
7
7
|
const connections = createConnectionManager();
|
|
8
8
|
const refCounter = createRefCounter();
|
|
9
9
|
const existingClients = new Map();
|
|
10
10
|
// Resolve cache (when config.resolve is provided)
|
|
11
11
|
const resolvedApis = new Map();
|
|
12
12
|
const pendingResolutions = new Map();
|
|
13
|
+
const destructionTimers = new Map();
|
|
14
|
+
const cancelDestructionTimer = (genesisHash) => {
|
|
15
|
+
const timer = destructionTimers.get(genesisHash);
|
|
16
|
+
if (timer !== undefined) {
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
destructionTimers.delete(genesisHash);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
13
21
|
const getOrCreateClient = (chain) => {
|
|
14
|
-
const existing = existingClients.get(chain.
|
|
22
|
+
const existing = existingClients.get(chain.genesisHash);
|
|
15
23
|
if (existing)
|
|
16
24
|
return existing;
|
|
17
|
-
const provider =
|
|
25
|
+
const provider = createProvider(chain, status => connections.update(chain.genesisHash, status));
|
|
18
26
|
const branchedProvider = createBranchedProvider(provider);
|
|
19
|
-
const client = createClient(branchedProvider.branch(),
|
|
27
|
+
const client = createClient(branchedProvider.branch(), clientOptions?.(chain));
|
|
20
28
|
const pooled = { client, provider: branchedProvider };
|
|
21
|
-
existingClients.set(chain.
|
|
29
|
+
existingClients.set(chain.genesisHash, pooled);
|
|
22
30
|
return pooled;
|
|
23
31
|
};
|
|
24
|
-
const destroyClient = (
|
|
25
|
-
|
|
32
|
+
const destroyClient = (genesisHash) => {
|
|
33
|
+
cancelDestructionTimer(genesisHash);
|
|
34
|
+
const pooled = existingClients.get(genesisHash);
|
|
26
35
|
if (pooled) {
|
|
27
|
-
existingClients.delete(
|
|
28
|
-
connections.update(
|
|
36
|
+
existingClients.delete(genesisHash);
|
|
37
|
+
connections.update(genesisHash, 'disconnected');
|
|
29
38
|
pooled.client.destroy();
|
|
30
39
|
}
|
|
31
|
-
resolvedApis.delete(
|
|
32
|
-
pendingResolutions.delete(
|
|
40
|
+
resolvedApis.delete(genesisHash);
|
|
41
|
+
pendingResolutions.delete(genesisHash);
|
|
33
42
|
};
|
|
34
43
|
const rawAcquire = async (chain) => {
|
|
35
44
|
try {
|
|
36
|
-
|
|
45
|
+
if (destroyDelay > 0)
|
|
46
|
+
cancelDestructionTimer(chain.genesisHash);
|
|
47
|
+
refCounter.increment(chain.genesisHash);
|
|
37
48
|
const pooled = getOrCreateClient(chain);
|
|
38
|
-
await pooled.client.getBestBlocks();
|
|
39
49
|
return {
|
|
40
50
|
pooled,
|
|
41
51
|
unlock() {
|
|
42
|
-
refCounter.decrement(chain.
|
|
52
|
+
if (refCounter.decrement(chain.genesisHash) === 0) {
|
|
53
|
+
if (destroyDelay === 0) {
|
|
54
|
+
destroyClient(chain.genesisHash);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
destroyClient(chain.genesisHash);
|
|
59
|
+
}, destroyDelay);
|
|
60
|
+
destructionTimers.set(chain.genesisHash, timer);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
43
63
|
},
|
|
44
64
|
};
|
|
45
65
|
}
|
|
46
66
|
catch (error) {
|
|
47
|
-
if (refCounter.decrement(chain.
|
|
48
|
-
destroyClient(chain.
|
|
67
|
+
if (refCounter.decrement(chain.genesisHash) === 0) {
|
|
68
|
+
destroyClient(chain.genesisHash);
|
|
49
69
|
}
|
|
50
70
|
throw error;
|
|
51
71
|
}
|
|
52
72
|
};
|
|
53
73
|
const resolveApi = async (chain, polkadotClient) => {
|
|
54
|
-
if (!
|
|
74
|
+
if (!resolve)
|
|
55
75
|
return polkadotClient;
|
|
56
|
-
const existing = resolvedApis.get(chain.
|
|
76
|
+
const existing = resolvedApis.get(chain.genesisHash);
|
|
57
77
|
if (existing && existing.polkadotClient === polkadotClient)
|
|
58
78
|
return existing.resolved;
|
|
59
|
-
const pending = pendingResolutions.get(chain.
|
|
79
|
+
const pending = pendingResolutions.get(chain.genesisHash);
|
|
60
80
|
if (pending && pending.polkadotClient === polkadotClient)
|
|
61
81
|
return pending.promise;
|
|
62
82
|
const promise = (async () => {
|
|
63
83
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guarded by early return above
|
|
64
|
-
const resolved = await
|
|
65
|
-
resolvedApis.set(chain.
|
|
84
|
+
const resolved = await resolve(chain, polkadotClient);
|
|
85
|
+
resolvedApis.set(chain.genesisHash, { resolved, polkadotClient });
|
|
66
86
|
return resolved;
|
|
67
87
|
})();
|
|
68
|
-
pendingResolutions.set(chain.
|
|
69
|
-
promise.finally(() => pendingResolutions.delete(chain.
|
|
88
|
+
pendingResolutions.set(chain.genesisHash, { promise, polkadotClient });
|
|
89
|
+
promise.finally(() => pendingResolutions.delete(chain.genesisHash));
|
|
70
90
|
return promise;
|
|
71
91
|
};
|
|
72
|
-
|
|
92
|
+
return {
|
|
73
93
|
async lockApi(chain) {
|
|
74
94
|
const { pooled, unlock } = await rawAcquire(chain);
|
|
75
95
|
try {
|
|
@@ -78,32 +98,22 @@ export const createChainConnection = (config) => {
|
|
|
78
98
|
}
|
|
79
99
|
catch (error) {
|
|
80
100
|
unlock();
|
|
81
|
-
resolvedApis.delete(chain.
|
|
82
|
-
pendingResolutions.delete(chain.
|
|
101
|
+
resolvedApis.delete(chain.genesisHash);
|
|
102
|
+
pendingResolutions.delete(chain.genesisHash);
|
|
83
103
|
throw error;
|
|
84
104
|
}
|
|
85
105
|
},
|
|
86
|
-
requestApi: (async (chain, callback) => {
|
|
87
|
-
const { api, unlock } = await connection.lockApi(chain);
|
|
88
|
-
try {
|
|
89
|
-
return await callback(api);
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
unlock();
|
|
93
|
-
}
|
|
94
|
-
}),
|
|
95
106
|
getProvider(chain) {
|
|
96
107
|
return getSyncProvider(async () => {
|
|
97
108
|
const { pooled, unlock } = await rawAcquire(chain);
|
|
98
109
|
return pooled.provider.branch(unlock);
|
|
99
110
|
});
|
|
100
111
|
},
|
|
101
|
-
status(
|
|
102
|
-
return connections.getConnectionStatus(
|
|
112
|
+
status(genesisHash) {
|
|
113
|
+
return connections.getConnectionStatus(genesisHash);
|
|
103
114
|
},
|
|
104
|
-
onStatusChanged(
|
|
105
|
-
return connections.onStatusChange(
|
|
115
|
+
onStatusChanged(genesisHash, callback) {
|
|
116
|
+
return connections.onStatusChange(genesisHash, callback);
|
|
106
117
|
},
|
|
107
118
|
};
|
|
108
|
-
return connection;
|
|
109
119
|
};
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { createChainConnection } from './connectionPool.js';
|
|
3
3
|
vi.mock('polkadot-api', () => ({
|
|
4
4
|
createClient: vi.fn((_provider, _options) => createMockClient()),
|
|
5
5
|
}));
|
|
6
|
-
const createMockClient = () => ({
|
|
7
|
-
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
8
|
-
destroy: vi.fn(),
|
|
9
|
-
});
|
|
6
|
+
const createMockClient = () => ({ destroy: vi.fn() });
|
|
10
7
|
const createMockProvider = () => {
|
|
11
8
|
const send = vi.fn();
|
|
12
9
|
const disconnect = vi.fn();
|
|
@@ -17,7 +14,7 @@ const createMockProvider = () => {
|
|
|
17
14
|
};
|
|
18
15
|
return { provider, send, disconnect, simulateMessage: (msg) => onMessage?.(msg) };
|
|
19
16
|
};
|
|
20
|
-
const testChain = (id) => ({
|
|
17
|
+
const testChain = (id) => ({ genesisHash: id, nodes: ['wss://test'] });
|
|
21
18
|
const createTestConnection = (overrides) => {
|
|
22
19
|
const mockProvider = createMockProvider();
|
|
23
20
|
const connection = createChainConnection({
|
|
@@ -52,12 +49,6 @@ describe('createChainConnection', () => {
|
|
|
52
49
|
u1();
|
|
53
50
|
u2();
|
|
54
51
|
});
|
|
55
|
-
it('calls getBestBlocks to verify connectivity', async () => {
|
|
56
|
-
const { connection } = createTestConnection();
|
|
57
|
-
const { api, unlock } = await connection.lockApi(testChain('a'));
|
|
58
|
-
expect(api.getBestBlocks).toHaveBeenCalled();
|
|
59
|
-
unlock();
|
|
60
|
-
});
|
|
61
52
|
});
|
|
62
53
|
describe('lockApi — with resolve', () => {
|
|
63
54
|
it('calls resolve with chain and polkadotClient', async () => {
|
|
@@ -90,24 +81,6 @@ describe('createChainConnection', () => {
|
|
|
90
81
|
});
|
|
91
82
|
});
|
|
92
83
|
describe('lockApi — error handling', () => {
|
|
93
|
-
it('throws when getBestBlocks rejects', async () => {
|
|
94
|
-
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
95
|
-
getBestBlocks: vi.fn().mockRejectedValue(new Error('connection failed')),
|
|
96
|
-
destroy: vi.fn(),
|
|
97
|
-
});
|
|
98
|
-
const { connection } = createTestConnection();
|
|
99
|
-
await expect(connection.lockApi(testChain('a'))).rejects.toThrow('connection failed');
|
|
100
|
-
});
|
|
101
|
-
it('destroys client on error when ref count reaches 0', async () => {
|
|
102
|
-
const destroyFn = vi.fn();
|
|
103
|
-
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
104
|
-
getBestBlocks: vi.fn().mockRejectedValue(new Error('fail')),
|
|
105
|
-
destroy: destroyFn,
|
|
106
|
-
});
|
|
107
|
-
const { connection } = createTestConnection();
|
|
108
|
-
await expect(connection.lockApi(testChain('a'))).rejects.toThrow();
|
|
109
|
-
expect(destroyFn).toHaveBeenCalled();
|
|
110
|
-
});
|
|
111
84
|
it('throws when resolve rejects and calls unlock', async () => {
|
|
112
85
|
const resolve = vi.fn().mockRejectedValue(new Error('resolve failed'));
|
|
113
86
|
const { connection } = createTestConnection({ resolve });
|
|
@@ -127,32 +100,6 @@ describe('createChainConnection', () => {
|
|
|
127
100
|
unlock();
|
|
128
101
|
});
|
|
129
102
|
});
|
|
130
|
-
describe('requestApi', () => {
|
|
131
|
-
it('calls callback with api and returns result', async () => {
|
|
132
|
-
const { connection } = createTestConnection();
|
|
133
|
-
const result = await connection.requestApi(testChain('a'), api => {
|
|
134
|
-
expect(api).toBeDefined();
|
|
135
|
-
return 42;
|
|
136
|
-
});
|
|
137
|
-
expect(result).toBe(42);
|
|
138
|
-
});
|
|
139
|
-
it('unlocks after callback completes', async () => {
|
|
140
|
-
const { connection } = createTestConnection();
|
|
141
|
-
await connection.requestApi(testChain('a'), () => 'done');
|
|
142
|
-
// Subsequent request should work without issue
|
|
143
|
-
const result = await connection.requestApi(testChain('a'), () => 'again');
|
|
144
|
-
expect(result).toBe('again');
|
|
145
|
-
});
|
|
146
|
-
it('unlocks when callback throws', async () => {
|
|
147
|
-
const { connection } = createTestConnection();
|
|
148
|
-
await expect(connection.requestApi(testChain('a'), () => {
|
|
149
|
-
throw new Error('callback error');
|
|
150
|
-
})).rejects.toThrow('callback error');
|
|
151
|
-
// Should still work after error
|
|
152
|
-
const result = await connection.requestApi(testChain('a'), () => 'ok');
|
|
153
|
-
expect(result).toBe('ok');
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
103
|
describe('status / onStatusChanged', () => {
|
|
157
104
|
it('returns disconnected for unknown chain', () => {
|
|
158
105
|
const { connection } = createTestConnection();
|
|
@@ -189,4 +136,70 @@ describe('createChainConnection', () => {
|
|
|
189
136
|
expect(callback).not.toHaveBeenCalled();
|
|
190
137
|
});
|
|
191
138
|
});
|
|
139
|
+
describe('lockApi — connection lifecycle', () => {
|
|
140
|
+
it('destroys client synchronously when last lock is released (no destroyDelay)', async () => {
|
|
141
|
+
const destroyFn = vi.fn();
|
|
142
|
+
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
143
|
+
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
144
|
+
destroy: destroyFn,
|
|
145
|
+
});
|
|
146
|
+
const { connection } = createTestConnection();
|
|
147
|
+
const { unlock } = await connection.lockApi(testChain('a'));
|
|
148
|
+
unlock();
|
|
149
|
+
expect(destroyFn).toHaveBeenCalledOnce();
|
|
150
|
+
});
|
|
151
|
+
it('does not destroy while any lock is still held', async () => {
|
|
152
|
+
const destroyFn = vi.fn();
|
|
153
|
+
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
154
|
+
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
155
|
+
destroy: destroyFn,
|
|
156
|
+
});
|
|
157
|
+
const { connection } = createTestConnection();
|
|
158
|
+
const chain = testChain('a');
|
|
159
|
+
const { unlock: u1 } = await connection.lockApi(chain);
|
|
160
|
+
const { unlock: u2 } = await connection.lockApi(chain);
|
|
161
|
+
u1();
|
|
162
|
+
expect(destroyFn).not.toHaveBeenCalled();
|
|
163
|
+
u2();
|
|
164
|
+
expect(destroyFn).toHaveBeenCalledOnce();
|
|
165
|
+
});
|
|
166
|
+
describe('with destroyDelay', () => {
|
|
167
|
+
beforeEach(() => {
|
|
168
|
+
vi.useFakeTimers();
|
|
169
|
+
});
|
|
170
|
+
afterEach(() => {
|
|
171
|
+
vi.useRealTimers();
|
|
172
|
+
});
|
|
173
|
+
it('defers destruction by destroyDelay ms', async () => {
|
|
174
|
+
const destroyFn = vi.fn();
|
|
175
|
+
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
176
|
+
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
177
|
+
destroy: destroyFn,
|
|
178
|
+
});
|
|
179
|
+
const { connection } = createTestConnection({ destroyDelay: 1000 });
|
|
180
|
+
const { unlock } = await connection.lockApi(testChain('a'));
|
|
181
|
+
unlock();
|
|
182
|
+
expect(destroyFn).not.toHaveBeenCalled();
|
|
183
|
+
vi.advanceTimersByTime(1000);
|
|
184
|
+
expect(destroyFn).toHaveBeenCalledOnce();
|
|
185
|
+
});
|
|
186
|
+
it('cancels destruction timer when connection is re-acquired before delay elapses', async () => {
|
|
187
|
+
const destroyFn = vi.fn();
|
|
188
|
+
vi.mocked(await import('polkadot-api')).createClient.mockReturnValueOnce({
|
|
189
|
+
getBestBlocks: vi.fn().mockResolvedValue([]),
|
|
190
|
+
destroy: destroyFn,
|
|
191
|
+
});
|
|
192
|
+
const { connection } = createTestConnection({ destroyDelay: 1000 });
|
|
193
|
+
const chain = testChain('a');
|
|
194
|
+
const { api: api1, unlock: u1 } = await connection.lockApi(chain);
|
|
195
|
+
u1();
|
|
196
|
+
// Re-acquire before timer fires — same client must be returned, no destruction
|
|
197
|
+
const { api: api2, unlock: u2 } = await connection.lockApi(chain);
|
|
198
|
+
vi.advanceTimersByTime(1000);
|
|
199
|
+
expect(destroyFn).not.toHaveBeenCalled();
|
|
200
|
+
expect(api1).toBe(api2);
|
|
201
|
+
u2();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
192
205
|
});
|
package/dist/helpers.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type ChainConnection, type ChainConnectionConfig, createChainConnection } from './connectionPool.js';
|
|
2
2
|
export { type MetadataCache, createMetadataCache } from './metadataCache.js';
|
|
3
3
|
export { withSubscriptionReplay } from './subscriptionReplayProvider.js';
|
|
4
|
-
export { createWsJsonRpcProvider } from './
|
|
4
|
+
export { createWsJsonRpcProvider } from './wsProvider.js';
|
|
5
5
|
export { type ChainConfig, type ConnectionStatus } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createChainConnection } from './connectionPool.js';
|
|
2
2
|
export { createMetadataCache } from './metadataCache.js';
|
|
3
3
|
export { withSubscriptionReplay } from './subscriptionReplayProvider.js';
|
|
4
|
-
export { createWsJsonRpcProvider } from './
|
|
4
|
+
export { createWsJsonRpcProvider } from './wsProvider.js';
|
|
5
5
|
export {} from './types.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { JsonRpcProvider } from '@polkadot-api/json-rpc-provider';
|
|
2
2
|
import type { PolkadotClient } from 'polkadot-api';
|
|
3
3
|
export type ChainConfig = {
|
|
4
|
-
|
|
5
|
-
nodes:
|
|
6
|
-
url: string;
|
|
7
|
-
}>;
|
|
4
|
+
genesisHash: string;
|
|
5
|
+
nodes: string[];
|
|
8
6
|
};
|
|
9
7
|
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected';
|
|
10
8
|
export type BranchedProvider = {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JsonRpcProvider } from '@polkadot-api/json-rpc-provider';
|
|
2
|
+
import type { ConnectionStatus } from './types.js';
|
|
3
|
+
export declare const createWsJsonRpcProvider: (options: {
|
|
4
|
+
endpoints: string[];
|
|
5
|
+
onStatusChanged?: (status: ConnectionStatus) => void;
|
|
6
|
+
}) => JsonRpcProvider;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WsEvent, getWsProvider } from '@polkadot-api/ws-provider';
|
|
2
|
+
import { noop } from './helpers.js';
|
|
3
|
+
import { withSubscriptionReplay } from './subscriptionReplayProvider.js';
|
|
4
|
+
export const createWsJsonRpcProvider = (options) => {
|
|
5
|
+
let notifyReconnect = noop;
|
|
6
|
+
const onReconnect = (cb) => {
|
|
7
|
+
notifyReconnect = cb;
|
|
8
|
+
return () => {
|
|
9
|
+
notifyReconnect = noop;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
return withSubscriptionReplay(getWsProvider(options.endpoints, {
|
|
13
|
+
heartbeatTimeout: Number.POSITIVE_INFINITY,
|
|
14
|
+
onStatusChanged: event => {
|
|
15
|
+
let status;
|
|
16
|
+
switch (event.type) {
|
|
17
|
+
case WsEvent.CONNECTING:
|
|
18
|
+
status = 'connecting';
|
|
19
|
+
break;
|
|
20
|
+
case WsEvent.CONNECTED:
|
|
21
|
+
notifyReconnect();
|
|
22
|
+
status = 'connected';
|
|
23
|
+
break;
|
|
24
|
+
case WsEvent.ERROR:
|
|
25
|
+
case WsEvent.CLOSE:
|
|
26
|
+
status = 'disconnected';
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
status = 'disconnected';
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
options.onStatusChanged?.(status);
|
|
33
|
+
},
|
|
34
|
+
}), onReconnect);
|
|
35
|
+
};
|
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.6.
|
|
4
|
+
"version": "0.6.13",
|
|
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,9 +25,10 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@novasamatech/storage-adapter": "0.6.
|
|
28
|
+
"@novasamatech/storage-adapter": "0.6.13",
|
|
29
29
|
"@polkadot-api/json-rpc-provider": "^0.0.4",
|
|
30
30
|
"@polkadot-api/json-rpc-provider-proxy": "^0.2.8",
|
|
31
|
+
"@polkadot-api/ws-provider": "^0.7.5",
|
|
31
32
|
"nanoevents": "^9.1.0",
|
|
32
33
|
"polkadot-api": "^1.23.3"
|
|
33
34
|
},
|