@pezkuwi/rpc-provider 16.5.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/README.md +68 -0
- package/build/bundle.d.ts +5 -0
- package/build/coder/error.d.ts +29 -0
- package/build/coder/index.d.ts +8 -0
- package/build/defaults.d.ts +5 -0
- package/build/http/index.d.ts +81 -0
- package/build/http/types.d.ts +7 -0
- package/build/index.d.ts +2 -0
- package/build/lru.d.ts +15 -0
- package/build/mock/index.d.ts +35 -0
- package/build/mock/mockHttp.d.ts +9 -0
- package/build/mock/mockWs.d.ts +26 -0
- package/build/mock/types.d.ts +23 -0
- package/build/packageDetect.d.ts +1 -0
- package/build/packageInfo.d.ts +6 -0
- package/build/substrate-connect/Health.d.ts +7 -0
- package/build/substrate-connect/index.d.ts +22 -0
- package/build/substrate-connect/types.d.ts +12 -0
- package/build/types.d.ts +85 -0
- package/build/ws/errors.d.ts +1 -0
- package/build/ws/index.d.ts +121 -0
- package/package.json +43 -0
- package/src/bundle.ts +8 -0
- package/src/coder/decodeResponse.spec.ts +70 -0
- package/src/coder/encodeJson.spec.ts +20 -0
- package/src/coder/encodeObject.spec.ts +25 -0
- package/src/coder/error.spec.ts +111 -0
- package/src/coder/error.ts +66 -0
- package/src/coder/index.ts +88 -0
- package/src/defaults.ts +10 -0
- package/src/http/index.spec.ts +72 -0
- package/src/http/index.ts +238 -0
- package/src/http/send.spec.ts +61 -0
- package/src/http/types.ts +11 -0
- package/src/index.ts +6 -0
- package/src/lru.spec.ts +74 -0
- package/src/lru.ts +197 -0
- package/src/mock/index.ts +259 -0
- package/src/mock/mockHttp.ts +35 -0
- package/src/mock/mockWs.ts +92 -0
- package/src/mock/on.spec.ts +43 -0
- package/src/mock/send.spec.ts +38 -0
- package/src/mock/subscribe.spec.ts +81 -0
- package/src/mock/types.ts +36 -0
- package/src/mock/unsubscribe.spec.ts +57 -0
- package/src/mod.ts +4 -0
- package/src/packageDetect.ts +12 -0
- package/src/packageInfo.ts +6 -0
- package/src/substrate-connect/Health.ts +325 -0
- package/src/substrate-connect/index.spec.ts +638 -0
- package/src/substrate-connect/index.ts +415 -0
- package/src/substrate-connect/types.ts +16 -0
- package/src/types.ts +101 -0
- package/src/ws/connect.spec.ts +167 -0
- package/src/ws/errors.ts +41 -0
- package/src/ws/index.spec.ts +97 -0
- package/src/ws/index.ts +652 -0
- package/src/ws/send.spec.ts +126 -0
- package/src/ws/state.spec.ts +20 -0
- package/src/ws/subscribe.spec.ts +68 -0
- package/src/ws/unsubscribe.spec.ts +100 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.spec.json +18 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/* eslint-disable camelcase */
|
|
5
|
+
|
|
6
|
+
import type { Header } from '@pezkuwi/types/interfaces';
|
|
7
|
+
import type { Codec, Registry } from '@pezkuwi/types/types';
|
|
8
|
+
import type { ProviderInterface, ProviderInterfaceEmitCb, ProviderInterfaceEmitted } from '../types.js';
|
|
9
|
+
import type { MockStateDb, MockStateSubscriptionCallback, MockStateSubscriptions } from './types.js';
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from 'eventemitter3';
|
|
12
|
+
|
|
13
|
+
import { createTestKeyring } from '@pezkuwi/keyring/testing';
|
|
14
|
+
import { decorateStorage, Metadata } from '@pezkuwi/types';
|
|
15
|
+
import jsonrpc from '@pezkuwi/types/interfaces/jsonrpc';
|
|
16
|
+
import rpcHeader from '@pezkuwi/types-support/json/Header.004.json' assert { type: 'json' };
|
|
17
|
+
import rpcSignedBlock from '@pezkuwi/types-support/json/SignedBlock.004.immortal.json' assert { type: 'json' };
|
|
18
|
+
import rpcMetadata from '@pezkuwi/types-support/metadata/static-substrate';
|
|
19
|
+
import { BN, bnToU8a, logger, u8aToHex } from '@pezkuwi/util';
|
|
20
|
+
import { randomAsU8a } from '@pezkuwi/util-crypto';
|
|
21
|
+
|
|
22
|
+
const INTERVAL = 1000;
|
|
23
|
+
const SUBSCRIPTIONS: string[] = Array.prototype.concat.apply(
|
|
24
|
+
[],
|
|
25
|
+
Object.values(jsonrpc).map((section): string[] =>
|
|
26
|
+
Object
|
|
27
|
+
.values(section)
|
|
28
|
+
.filter(({ isSubscription }) => isSubscription)
|
|
29
|
+
.map(({ jsonrpc }) => jsonrpc)
|
|
30
|
+
.concat('chain_subscribeNewHead')
|
|
31
|
+
)
|
|
32
|
+
) as string[];
|
|
33
|
+
|
|
34
|
+
const keyring = createTestKeyring({ type: 'ed25519' });
|
|
35
|
+
const l = logger('api-mock');
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A mock provider mainly used for testing.
|
|
39
|
+
* @return {ProviderInterface} The mock provider
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export class MockProvider implements ProviderInterface {
|
|
43
|
+
private db: MockStateDb = {};
|
|
44
|
+
|
|
45
|
+
private emitter = new EventEmitter();
|
|
46
|
+
|
|
47
|
+
private intervalId?: ReturnType<typeof setInterval> | null;
|
|
48
|
+
|
|
49
|
+
public isUpdating = true;
|
|
50
|
+
|
|
51
|
+
private registry: Registry;
|
|
52
|
+
|
|
53
|
+
private prevNumber = new BN(-1);
|
|
54
|
+
|
|
55
|
+
private requests: Record<string, (...params: any[]) => unknown> = {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
57
|
+
chain_getBlock: () => this.registry.createType('SignedBlock', rpcSignedBlock.result).toJSON(),
|
|
58
|
+
chain_getBlockHash: () => '0x1234000000000000000000000000000000000000000000000000000000000000',
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
60
|
+
chain_getFinalizedHead: () => this.registry.createType('Header', rpcHeader.result).hash,
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
62
|
+
chain_getHeader: () => this.registry.createType('Header', rpcHeader.result).toJSON(),
|
|
63
|
+
rpc_methods: () => this.registry.createType('RpcMethods').toJSON(),
|
|
64
|
+
state_getKeys: () => [],
|
|
65
|
+
state_getKeysPaged: () => [],
|
|
66
|
+
state_getMetadata: () => rpcMetadata,
|
|
67
|
+
state_getRuntimeVersion: () => this.registry.createType('RuntimeVersion').toHex(),
|
|
68
|
+
state_getStorage: (storage: MockStateDb, [key]: string[]) => u8aToHex(storage[key]),
|
|
69
|
+
system_chain: () => 'mockChain',
|
|
70
|
+
system_health: () => ({}),
|
|
71
|
+
system_name: () => 'mockClient',
|
|
72
|
+
system_properties: () => ({ ss58Format: 42 }),
|
|
73
|
+
system_upgradedToTripleRefCount: () => this.registry.createType('bool', true),
|
|
74
|
+
system_version: () => '9.8.7',
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, sort-keys
|
|
76
|
+
dev_echo: (_, params: any) => params
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
public subscriptions: MockStateSubscriptions = SUBSCRIPTIONS.reduce((subs, name): MockStateSubscriptions => {
|
|
80
|
+
subs[name] = {
|
|
81
|
+
callbacks: {},
|
|
82
|
+
lastValue: null
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return subs;
|
|
86
|
+
}, ({} as MockStateSubscriptions));
|
|
87
|
+
|
|
88
|
+
private subscriptionId = 0;
|
|
89
|
+
|
|
90
|
+
private subscriptionMap: Record<number, string> = {};
|
|
91
|
+
|
|
92
|
+
constructor (registry: Registry) {
|
|
93
|
+
this.registry = registry;
|
|
94
|
+
|
|
95
|
+
this.init();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public get hasSubscriptions (): boolean {
|
|
99
|
+
return !!true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public clone (): MockProvider {
|
|
103
|
+
throw new Error('Unimplemented');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public async connect (): Promise<void> {
|
|
107
|
+
// noop
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
111
|
+
public async disconnect (): Promise<void> {
|
|
112
|
+
if (this.intervalId) {
|
|
113
|
+
clearInterval(this.intervalId);
|
|
114
|
+
this.intervalId = null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public get isClonable (): boolean {
|
|
119
|
+
return !!false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public get isConnected (): boolean {
|
|
123
|
+
return !!true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public on (type: ProviderInterfaceEmitted, sub: ProviderInterfaceEmitCb): () => void {
|
|
127
|
+
this.emitter.on(type, sub);
|
|
128
|
+
|
|
129
|
+
return (): void => {
|
|
130
|
+
this.emitter.removeListener(type, sub);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
135
|
+
public async send <T = any> (method: string, params: unknown[]): Promise<T> {
|
|
136
|
+
l.debug(() => ['send', method, params]);
|
|
137
|
+
|
|
138
|
+
if (!this.requests[method]) {
|
|
139
|
+
throw new Error(`provider.send: Invalid method '${method}'`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return this.requests[method](this.db, params) as T;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
146
|
+
public async subscribe (_type: string, method: string, ...params: unknown[]): Promise<number> {
|
|
147
|
+
l.debug(() => ['subscribe', method, params]);
|
|
148
|
+
|
|
149
|
+
if (!this.subscriptions[method]) {
|
|
150
|
+
throw new Error(`provider.subscribe: Invalid method '${method}'`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const callback = params.pop() as MockStateSubscriptionCallback;
|
|
154
|
+
const id = ++this.subscriptionId;
|
|
155
|
+
|
|
156
|
+
this.subscriptions[method].callbacks[id] = callback;
|
|
157
|
+
this.subscriptionMap[id] = method;
|
|
158
|
+
|
|
159
|
+
if (this.subscriptions[method].lastValue !== null) {
|
|
160
|
+
callback(null, this.subscriptions[method].lastValue);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return id;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
167
|
+
public async unsubscribe (_type: string, _method: string, id: number): Promise<boolean> {
|
|
168
|
+
const sub = this.subscriptionMap[id];
|
|
169
|
+
|
|
170
|
+
l.debug(() => ['unsubscribe', id, sub]);
|
|
171
|
+
|
|
172
|
+
if (!sub) {
|
|
173
|
+
throw new Error(`Unable to find subscription for ${id}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
delete this.subscriptionMap[id];
|
|
177
|
+
delete this.subscriptions[sub].callbacks[id];
|
|
178
|
+
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private init (): void {
|
|
183
|
+
const emitEvents: ProviderInterfaceEmitted[] = ['connected', 'disconnected'];
|
|
184
|
+
let emitIndex = 0;
|
|
185
|
+
let newHead = this.makeBlockHeader();
|
|
186
|
+
let counter = -1;
|
|
187
|
+
|
|
188
|
+
const metadata = new Metadata(this.registry, rpcMetadata);
|
|
189
|
+
|
|
190
|
+
this.registry.setMetadata(metadata);
|
|
191
|
+
|
|
192
|
+
const query = decorateStorage(this.registry, metadata.asLatest, metadata.version);
|
|
193
|
+
|
|
194
|
+
// Do something every 1 seconds
|
|
195
|
+
this.intervalId = setInterval((): void => {
|
|
196
|
+
if (!this.isUpdating) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// create a new header (next block)
|
|
201
|
+
newHead = this.makeBlockHeader();
|
|
202
|
+
|
|
203
|
+
// increment the balances and nonce for each account
|
|
204
|
+
keyring.getPairs().forEach(({ publicKey }, index): void => {
|
|
205
|
+
this.setStateBn(query['system']['account'](publicKey), newHead.number.toBn().addn(index));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// set the timestamp for the current block
|
|
209
|
+
this.setStateBn(query['timestamp']['now'](), Math.floor(Date.now() / 1000));
|
|
210
|
+
this.updateSubs('chain_subscribeNewHead', newHead);
|
|
211
|
+
|
|
212
|
+
// We emit connected/disconnected at intervals
|
|
213
|
+
if (++counter % 2 === 1) {
|
|
214
|
+
if (++emitIndex === emitEvents.length) {
|
|
215
|
+
emitIndex = 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.emitter.emit(emitEvents[emitIndex]);
|
|
219
|
+
}
|
|
220
|
+
}, INTERVAL);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private makeBlockHeader (): Header {
|
|
224
|
+
const blockNumber = this.prevNumber.addn(1);
|
|
225
|
+
const header = this.registry.createType('Header', {
|
|
226
|
+
digest: {
|
|
227
|
+
logs: []
|
|
228
|
+
},
|
|
229
|
+
extrinsicsRoot: randomAsU8a(),
|
|
230
|
+
number: blockNumber,
|
|
231
|
+
parentHash: blockNumber.isZero()
|
|
232
|
+
? new Uint8Array(32)
|
|
233
|
+
: bnToU8a(this.prevNumber, { bitLength: 256, isLe: false }),
|
|
234
|
+
stateRoot: bnToU8a(blockNumber, { bitLength: 256, isLe: false })
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
this.prevNumber = blockNumber;
|
|
238
|
+
|
|
239
|
+
return header as unknown as Header;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private setStateBn (key: Uint8Array, value: BN | number): void {
|
|
243
|
+
this.db[u8aToHex(key)] = bnToU8a(value, { bitLength: 64, isLe: true });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private updateSubs (method: string, value: Codec): void {
|
|
247
|
+
this.subscriptions[method].lastValue = value;
|
|
248
|
+
|
|
249
|
+
Object
|
|
250
|
+
.values(this.subscriptions[method].callbacks)
|
|
251
|
+
.forEach((cb): void => {
|
|
252
|
+
try {
|
|
253
|
+
cb(null, value.toJSON());
|
|
254
|
+
} catch (error) {
|
|
255
|
+
l.error(`Error on '${method}' subscription`, error);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Mock } from './types.js';
|
|
5
|
+
|
|
6
|
+
import nock from 'nock';
|
|
7
|
+
|
|
8
|
+
interface Request {
|
|
9
|
+
code?: number;
|
|
10
|
+
method: string;
|
|
11
|
+
reply?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface HttpMock extends Mock {
|
|
15
|
+
post: (uri: string) => {
|
|
16
|
+
reply: (code: number, handler: (uri: string, body: { id: string }) => unknown) => HttpMock
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const TEST_HTTP_URL = 'http://localhost:9944';
|
|
21
|
+
|
|
22
|
+
export function mockHttp (requests: Request[]): Mock {
|
|
23
|
+
nock.cleanAll();
|
|
24
|
+
|
|
25
|
+
return requests.reduce((scope: HttpMock, request: Request) =>
|
|
26
|
+
scope
|
|
27
|
+
.post('/')
|
|
28
|
+
.reply(request.code || 200, (_uri: string, body: { id: string }) => {
|
|
29
|
+
scope.body = scope.body || {};
|
|
30
|
+
scope.body[request.method] = body;
|
|
31
|
+
|
|
32
|
+
return Object.assign({ id: body.id, jsonrpc: '2.0' }, request.reply || {}) as unknown;
|
|
33
|
+
}),
|
|
34
|
+
nock(TEST_HTTP_URL) as unknown as HttpMock);
|
|
35
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { Server, WebSocket } from 'mock-socket';
|
|
5
|
+
|
|
6
|
+
import { stringify } from '@pezkuwi/util';
|
|
7
|
+
|
|
8
|
+
interface Scope {
|
|
9
|
+
body: Record<string, Record<string, unknown>>;
|
|
10
|
+
requests: number;
|
|
11
|
+
server: Server;
|
|
12
|
+
done: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ErrorDef {
|
|
16
|
+
id: number;
|
|
17
|
+
error: {
|
|
18
|
+
code: number;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ReplyDef {
|
|
24
|
+
id: number;
|
|
25
|
+
reply: {
|
|
26
|
+
result: unknown;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface RpcBase {
|
|
31
|
+
id: number;
|
|
32
|
+
jsonrpc: '2.0';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type RpcError = RpcBase & ErrorDef;
|
|
36
|
+
type RpcReply = RpcBase & { result: unknown };
|
|
37
|
+
|
|
38
|
+
export type Request = { method: string } & (ErrorDef | ReplyDef);
|
|
39
|
+
|
|
40
|
+
global.WebSocket = WebSocket as typeof global.WebSocket;
|
|
41
|
+
|
|
42
|
+
export const TEST_WS_URL = 'ws://localhost:9955';
|
|
43
|
+
|
|
44
|
+
// should be JSONRPC def return
|
|
45
|
+
function createError ({ error: { code, message }, id }: ErrorDef): RpcError {
|
|
46
|
+
return {
|
|
47
|
+
error: {
|
|
48
|
+
code,
|
|
49
|
+
message
|
|
50
|
+
},
|
|
51
|
+
id,
|
|
52
|
+
jsonrpc: '2.0'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// should be JSONRPC def return
|
|
57
|
+
function createReply ({ id, reply: { result } }: ReplyDef): RpcReply {
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
jsonrpc: '2.0',
|
|
61
|
+
result
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// scope definition returned
|
|
66
|
+
export function mockWs (requests: Request[], wsUrl: string = TEST_WS_URL): Scope {
|
|
67
|
+
const server = new Server(wsUrl);
|
|
68
|
+
|
|
69
|
+
let requestCount = 0;
|
|
70
|
+
const scope: Scope = {
|
|
71
|
+
body: {},
|
|
72
|
+
done: () => new Promise<void>((resolve) => server.stop(resolve)),
|
|
73
|
+
requests: 0,
|
|
74
|
+
server
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
server.on('connection', (socket): void => {
|
|
78
|
+
socket.on('message', (body): void => {
|
|
79
|
+
const request = requests[requestCount];
|
|
80
|
+
const response = (request as ErrorDef).error
|
|
81
|
+
? createError(request as ErrorDef)
|
|
82
|
+
: createReply(request as ReplyDef);
|
|
83
|
+
|
|
84
|
+
scope.body[request.method] = body as unknown as Record<string, unknown>;
|
|
85
|
+
requestCount++;
|
|
86
|
+
|
|
87
|
+
socket.send(stringify(response));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return scope;
|
|
92
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import type { ProviderInterfaceEmitted } from '../types.js';
|
|
7
|
+
|
|
8
|
+
import { TypeRegistry } from '@pezkuwi/types/create';
|
|
9
|
+
|
|
10
|
+
import { MockProvider } from './index.js';
|
|
11
|
+
|
|
12
|
+
describe('on', (): void => {
|
|
13
|
+
const registry = new TypeRegistry();
|
|
14
|
+
let mock: MockProvider;
|
|
15
|
+
|
|
16
|
+
beforeEach((): void => {
|
|
17
|
+
mock = new MockProvider(registry);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await mock.disconnect();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line jest/expect-expect
|
|
25
|
+
it('emits both connected and disconnected events', async (): Promise<void> => {
|
|
26
|
+
const events: Record<string, boolean> = { connected: false, disconnected: false };
|
|
27
|
+
|
|
28
|
+
await new Promise<boolean>((resolve) => {
|
|
29
|
+
const handler = (type: ProviderInterfaceEmitted): void => {
|
|
30
|
+
mock.on(type, (): void => {
|
|
31
|
+
events[type] = true;
|
|
32
|
+
|
|
33
|
+
if (Object.values(events).filter((value): boolean => value).length === 2) {
|
|
34
|
+
resolve(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
handler('connected');
|
|
40
|
+
handler('disconnected');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import { TypeRegistry } from '@pezkuwi/types/create';
|
|
7
|
+
|
|
8
|
+
import { MockProvider } from './index.js';
|
|
9
|
+
|
|
10
|
+
describe('send', (): void => {
|
|
11
|
+
const registry = new TypeRegistry();
|
|
12
|
+
let mock: MockProvider;
|
|
13
|
+
|
|
14
|
+
beforeEach((): void => {
|
|
15
|
+
mock = new MockProvider(registry);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await mock.disconnect();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('fails on non-supported methods', (): Promise<any> => {
|
|
23
|
+
return mock
|
|
24
|
+
.send('something_invalid', [])
|
|
25
|
+
.catch((error): void => {
|
|
26
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
|
27
|
+
expect((error as Error).message).toMatch(/Invalid method/);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns values for mocked requests', (): Promise<void> => {
|
|
32
|
+
return mock
|
|
33
|
+
.send('system_name', [])
|
|
34
|
+
.then((result): void => {
|
|
35
|
+
expect(result).toBe('mockClient');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import { TypeRegistry } from '@pezkuwi/types/create';
|
|
7
|
+
|
|
8
|
+
import { MockProvider } from './index.js';
|
|
9
|
+
|
|
10
|
+
describe('subscribe', (): void => {
|
|
11
|
+
const registry = new TypeRegistry();
|
|
12
|
+
let mock: MockProvider;
|
|
13
|
+
|
|
14
|
+
beforeEach((): void => {
|
|
15
|
+
mock = new MockProvider(registry);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await mock.disconnect();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('fails on unknown methods', async (): Promise<void> => {
|
|
23
|
+
await mock
|
|
24
|
+
.subscribe('test', 'test_notFound')
|
|
25
|
+
.catch((error): void => {
|
|
26
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
|
27
|
+
expect((error as Error).message).toMatch(/Invalid method 'test_notFound'/);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns a subscription id', async (): Promise<void> => {
|
|
32
|
+
await mock
|
|
33
|
+
.subscribe('chain_newHead', 'chain_subscribeNewHead', (): void => undefined)
|
|
34
|
+
.then((id): void => {
|
|
35
|
+
expect(id).toEqual(1);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('calls back with the last known value', async (): Promise<void> => {
|
|
40
|
+
mock.isUpdating = false;
|
|
41
|
+
mock.subscriptions.chain_subscribeNewHead.lastValue = 'testValue';
|
|
42
|
+
|
|
43
|
+
await new Promise<boolean>((resolve) => {
|
|
44
|
+
mock.subscribe('chain_newHead', 'chain_subscribeNewHead', (_: any, value: string): void => {
|
|
45
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
|
46
|
+
expect(value).toEqual('testValue');
|
|
47
|
+
resolve(true);
|
|
48
|
+
}).catch(console.error);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// eslint-disable-next-line jest/expect-expect
|
|
53
|
+
it('calls back with new headers', async (): Promise<void> => {
|
|
54
|
+
await new Promise<boolean>((resolve) => {
|
|
55
|
+
mock.subscribe('chain_newHead', 'chain_subscribeNewHead', (_: any, header: { number: number }): void => {
|
|
56
|
+
if (header.number === 4) {
|
|
57
|
+
resolve(true);
|
|
58
|
+
}
|
|
59
|
+
}).catch(console.error);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line jest/expect-expect
|
|
64
|
+
it('handles errors within callbacks gracefully', async (): Promise<void> => {
|
|
65
|
+
let hasThrown = false;
|
|
66
|
+
|
|
67
|
+
await new Promise<boolean>((resolve) => {
|
|
68
|
+
mock.subscribe('chain_newHead', 'chain_subscribeNewHead', (_: any, header: { number: number }): void => {
|
|
69
|
+
if (!hasThrown) {
|
|
70
|
+
hasThrown = true;
|
|
71
|
+
|
|
72
|
+
throw new Error('testing');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (header.number === 3) {
|
|
76
|
+
resolve(true);
|
|
77
|
+
}
|
|
78
|
+
}).catch(console.error);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Server } from 'mock-socket';
|
|
5
|
+
|
|
6
|
+
export type Global = typeof globalThis & {
|
|
7
|
+
WebSocket: typeof WebSocket;
|
|
8
|
+
fetch: any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Mock {
|
|
12
|
+
body: Record<string, Record<string, unknown>>;
|
|
13
|
+
requests: number;
|
|
14
|
+
server: Server;
|
|
15
|
+
done: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type MockStateSubscriptionCallback = (error: Error | null, value: any) => void;
|
|
19
|
+
|
|
20
|
+
export interface MockStateSubscription {
|
|
21
|
+
callbacks: Record<number, MockStateSubscriptionCallback>;
|
|
22
|
+
lastValue: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MockStateSubscriptions {
|
|
26
|
+
// known
|
|
27
|
+
chain_subscribeNewHead: MockStateSubscription;
|
|
28
|
+
state_subscribeStorage: MockStateSubscription;
|
|
29
|
+
|
|
30
|
+
// others
|
|
31
|
+
[key: string]: MockStateSubscription;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type MockStateDb = Record<string, Uint8Array>;
|
|
35
|
+
|
|
36
|
+
export type MockStateRequests = Record<string, (db: MockStateDb, params: any[]) => string>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import { TypeRegistry } from '@pezkuwi/types/create';
|
|
7
|
+
|
|
8
|
+
import { MockProvider } from './index.js';
|
|
9
|
+
|
|
10
|
+
describe('unsubscribe', (): void => {
|
|
11
|
+
const registry = new TypeRegistry();
|
|
12
|
+
let mock: MockProvider;
|
|
13
|
+
let id: number;
|
|
14
|
+
|
|
15
|
+
beforeEach((): Promise<void> => {
|
|
16
|
+
mock = new MockProvider(registry);
|
|
17
|
+
|
|
18
|
+
return mock
|
|
19
|
+
.subscribe('chain_newHead', 'chain_subscribeNewHead', (): void => undefined)
|
|
20
|
+
.then((_id): void => {
|
|
21
|
+
id = _id;
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await mock.disconnect();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('fails on unknown ids', async (): Promise<void> => {
|
|
30
|
+
await mock
|
|
31
|
+
.unsubscribe('chain_newHead', 'chain_subscribeNewHead', 5)
|
|
32
|
+
.catch((error): boolean => {
|
|
33
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
|
34
|
+
expect((error as Error).message).toMatch(/Unable to find/);
|
|
35
|
+
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// eslint-disable-next-line jest/expect-expect
|
|
41
|
+
it('unsubscribes successfully', async (): Promise<void> => {
|
|
42
|
+
await mock.unsubscribe('chain_newHead', 'chain_subscribeNewHead', id);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('fails on double unsubscribe', async (): Promise<void> => {
|
|
46
|
+
await mock.unsubscribe('chain_newHead', 'chain_subscribeNewHead', id)
|
|
47
|
+
.then((): Promise<boolean> =>
|
|
48
|
+
mock.unsubscribe('chain_newHead', 'chain_subscribeNewHead', id)
|
|
49
|
+
)
|
|
50
|
+
.catch((error): boolean => {
|
|
51
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
|
52
|
+
expect((error as Error).message).toMatch(/Unable to find/);
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
package/src/mod.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Do not edit, auto-generated by @polkadot/dev
|
|
5
|
+
// (packageInfo imports will be kept as-is, user-editable)
|
|
6
|
+
|
|
7
|
+
import { packageInfo as typesInfo } from '@pezkuwi/types/packageInfo';
|
|
8
|
+
import { detectPackage } from '@pezkuwi/util';
|
|
9
|
+
|
|
10
|
+
import { packageInfo } from './packageInfo.js';
|
|
11
|
+
|
|
12
|
+
detectPackage(packageInfo, null, [typesInfo]);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/rpc-provider authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Do not edit, auto-generated by @polkadot/dev
|
|
5
|
+
|
|
6
|
+
export const packageInfo = { name: '@pezkuwi/rpc-provider', path: 'auto', type: 'auto', version: '16.5.5' };
|