UNPKG

35.8 kBJavaScriptView Raw
1import { BehaviorSubject, combineLatest, from, map, of, switchMap, tap, toArray } from 'rxjs';
2import { getAvailableDerives } from '@polkadot/api-derive';
3import { memo, RpcCore } from '@polkadot/rpc-core';
4import { WsProvider } from '@polkadot/rpc-provider';
5import { expandMetadata, GenericExtrinsic, typeDefinitions, TypeRegistry } from '@polkadot/types';
6import { getSpecRuntime } from '@polkadot/types-known';
7import { arrayChunk, arrayFlatten, assertReturn, BN, compactStripLength, lazyMethod, lazyMethods, logger, nextTick, objectSpread, stringCamelCase, stringUpperFirst, u8aConcatStrict, u8aToHex } from '@polkadot/util';
8import { blake2AsHex } from '@polkadot/util-crypto';
9import { createSubmittable } from '../submittable/index.js';
10import { augmentObject } from '../util/augmentObject.js';
11import { decorateDeriveSections } from '../util/decorate.js';
12import { extractStorageArgs } from '../util/validate.js';
13import { Events } from './Events.js';
14import { findCall, findError } from './find.js';
15const PAGE_SIZE_K = 1000; // limit aligned with the 1k on the node (trie lookups are heavy)
16const PAGE_SIZE_V = 250; // limited since the data may be > 16MB (e.g. misfiring elections)
17const PAGE_SIZE_Q = 50; // queue of pending storage queries (mapped together, next tick)
18const l = logger('api/init');
19let instanceCounter = 0;
20function getAtQueryFn(api, { method, section }) {
21 return assertReturn(api.rx.query[section] && api.rx.query[section][method], () => `query.${section}.${method} is not available in this version of the metadata`);
22}
23export class Decorate extends Events {
24 __internal__instanceId;
25 __internal__runtimeLog = {};
26 __internal__registry;
27 __internal__storageGetQ = [];
28 __internal__storageSubQ = [];
29 // HACK Use BN import so decorateDerive works... yes, wtf.
30 __phantom = new BN(0);
31 _type;
32 _call = {};
33 _consts = {};
34 _derive;
35 _errors = {};
36 _events = {};
37 _extrinsics;
38 _extrinsicType = GenericExtrinsic.LATEST_EXTRINSIC_VERSION;
39 _genesisHash;
40 _isConnected;
41 _isReady = false;
42 _query = {};
43 _queryMulti;
44 _rpc;
45 _rpcCore;
46 _runtimeMap = {};
47 _runtimeChain;
48 _runtimeMetadata;
49 _runtimeVersion;
50 _rx = { call: {}, consts: {}, query: {}, tx: {} };
51 _options;
52 /**
53 * This is the one and only method concrete children classes need to implement.
54 * It's a higher-order function, which takes one argument
55 * `method: Method extends (...args: any[]) => Observable<any>`
56 * (and one optional `options`), and should return the user facing method.
57 * For example:
58 * - For ApiRx, `decorateMethod` should just be identity, because the input
59 * function is already an Observable
60 * - For ApiPromise, `decorateMethod` should return a function that takes all
61 * the parameters from `method`, adds an optional `callback` argument, and
62 * returns a Promise.
63 *
64 * We could easily imagine other user-facing interfaces, which are simply
65 * implemented by transforming the Observable to Stream/Iterator/Kefir/Bacon
66 * via `decorateMethod`.
67 */
68 _decorateMethod;
69 /**
70 * @description Create an instance of the class
71 *
72 * @param options Options object to create API instance or a Provider instance
73 *
74 * @example
75 * <BR>
76 *
77 * ```javascript
78 * import Api from '@polkadot/api/promise';
79 *
80 * const api = new Api().isReady();
81 *
82 * api.rpc.subscribeNewHeads((header) => {
83 * console.log(`new block #${header.number.toNumber()}`);
84 * });
85 * ```
86 */
87 constructor(options, type, decorateMethod) {
88 super();
89 this.__internal__instanceId = `${++instanceCounter}`;
90 this.__internal__registry = options.source?.registry || options.registry || new TypeRegistry();
91 this._rx.callAt = (blockHash, knownVersion) => from(this.at(blockHash, knownVersion)).pipe(map((a) => a.rx.call));
92 this._rx.queryAt = (blockHash, knownVersion) => from(this.at(blockHash, knownVersion)).pipe(map((a) => a.rx.query));
93 this._rx.registry = this.__internal__registry;
94 this._decorateMethod = decorateMethod;
95 this._options = options;
96 this._type = type;
97 const provider = options.source
98 ? options.source._rpcCore.provider.isClonable
99 ? options.source._rpcCore.provider.clone()
100 : options.source._rpcCore.provider
101 : (options.provider || new WsProvider());
102 // The RPC interface decorates the known interfaces on init
103 this._rpcCore = new RpcCore(this.__internal__instanceId, this.__internal__registry, {
104 isPedantic: this._options.isPedantic,
105 provider,
106 userRpc: this._options.rpc
107 });
108 this._isConnected = new BehaviorSubject(this._rpcCore.provider.isConnected);
109 this._rx.hasSubscriptions = this._rpcCore.provider.hasSubscriptions;
110 }
111 /**
112 * @description Return the current used registry
113 */
114 get registry() {
115 return this.__internal__registry;
116 }
117 /**
118 * @description Creates an instance of a type as registered
119 */
120 createType(type, ...params) {
121 return this.__internal__registry.createType(type, ...params);
122 }
123 /**
124 * @description Register additional user-defined of chain-specific types in the type registry
125 */
126 registerTypes(types) {
127 types && this.__internal__registry.register(types);
128 }
129 /**
130 * @returns `true` if the API operates with subscriptions
131 */
132 get hasSubscriptions() {
133 return this._rpcCore.provider.hasSubscriptions;
134 }
135 /**
136 * @returns `true` if the API decorate multi-key queries
137 */
138 get supportMulti() {
139 return this._rpcCore.provider.hasSubscriptions || !!this._rpcCore.state.queryStorageAt;
140 }
141 _emptyDecorated(registry, blockHash) {
142 return {
143 call: {},
144 consts: {},
145 errors: {},
146 events: {},
147 query: {},
148 registry,
149 rx: {
150 call: {},
151 query: {}
152 },
153 tx: createSubmittable(this._type, this._rx, this._decorateMethod, registry, blockHash)
154 };
155 }
156 _createDecorated(registry, fromEmpty, decoratedApi, blockHash) {
157 if (!decoratedApi) {
158 decoratedApi = this._emptyDecorated(registry.registry, blockHash);
159 }
160 if (fromEmpty || !registry.decoratedMeta) {
161 registry.decoratedMeta = expandMetadata(registry.registry, registry.metadata);
162 }
163 const runtime = this._decorateCalls(registry, this._decorateMethod, blockHash);
164 const runtimeRx = this._decorateCalls(registry, this._rxDecorateMethod, blockHash);
165 const storage = this._decorateStorage(registry.decoratedMeta, this._decorateMethod, blockHash);
166 const storageRx = this._decorateStorage(registry.decoratedMeta, this._rxDecorateMethod, blockHash);
167 augmentObject('consts', registry.decoratedMeta.consts, decoratedApi.consts, fromEmpty);
168 augmentObject('errors', registry.decoratedMeta.errors, decoratedApi.errors, fromEmpty);
169 augmentObject('events', registry.decoratedMeta.events, decoratedApi.events, fromEmpty);
170 augmentObject('query', storage, decoratedApi.query, fromEmpty);
171 augmentObject('query', storageRx, decoratedApi.rx.query, fromEmpty);
172 augmentObject('call', runtime, decoratedApi.call, fromEmpty);
173 augmentObject('call', runtimeRx, decoratedApi.rx.call, fromEmpty);
174 decoratedApi.findCall = (callIndex) => findCall(registry.registry, callIndex);
175 decoratedApi.findError = (errorIndex) => findError(registry.registry, errorIndex);
176 decoratedApi.queryMulti = blockHash
177 ? this._decorateMultiAt(decoratedApi, this._decorateMethod, blockHash)
178 : this._decorateMulti(this._decorateMethod);
179 decoratedApi.runtimeVersion = registry.runtimeVersion;
180 return {
181 createdAt: blockHash,
182 decoratedApi,
183 decoratedMeta: registry.decoratedMeta
184 };
185 }
186 _injectMetadata(registry, fromEmpty = false) {
187 // clear the decoration, we are redoing it here
188 if (fromEmpty || !registry.decoratedApi) {
189 registry.decoratedApi = this._emptyDecorated(registry.registry);
190 }
191 const { decoratedApi, decoratedMeta } = this._createDecorated(registry, fromEmpty, registry.decoratedApi);
192 this._call = decoratedApi.call;
193 this._consts = decoratedApi.consts;
194 this._errors = decoratedApi.errors;
195 this._events = decoratedApi.events;
196 this._query = decoratedApi.query;
197 this._rx.call = decoratedApi.rx.call;
198 this._rx.query = decoratedApi.rx.query;
199 const tx = this._decorateExtrinsics(decoratedMeta, this._decorateMethod);
200 const rxtx = this._decorateExtrinsics(decoratedMeta, this._rxDecorateMethod);
201 if (fromEmpty || !this._extrinsics) {
202 this._extrinsics = tx;
203 this._rx.tx = rxtx;
204 }
205 else {
206 augmentObject('tx', tx, this._extrinsics, false);
207 augmentObject(null, rxtx, this._rx.tx, false);
208 }
209 augmentObject(null, decoratedMeta.consts, this._rx.consts, fromEmpty);
210 this.emit('decorated');
211 }
212 /**
213 * @deprecated
214 * backwards compatible endpoint for metadata injection, may be removed in the future (However, it is still useful for testing injection)
215 */
216 injectMetadata(metadata, fromEmpty, registry) {
217 this._injectMetadata({ counter: 0, metadata, registry: registry || this.__internal__registry, runtimeVersion: this.__internal__registry.createType('RuntimeVersionPartial') }, fromEmpty);
218 }
219 _decorateFunctionMeta(input, output) {
220 output.meta = input.meta;
221 output.method = input.method;
222 output.section = input.section;
223 output.toJSON = input.toJSON;
224 if (input.callIndex) {
225 output.callIndex = input.callIndex;
226 }
227 return output;
228 }
229 // Filter all RPC methods based on the results of the rpc_methods call. We do this in the following
230 // manner to cater for both old and new:
231 // - when the number of entries are 0, only remove the ones with isOptional (account & contracts)
232 // - when non-zero, remove anything that is not in the array (we don't do this)
233 _filterRpc(methods, additional) {
234 // add any specific user-base RPCs
235 if (Object.keys(additional).length !== 0) {
236 this._rpcCore.addUserInterfaces(additional);
237 // re-decorate, only adding any new additional interfaces
238 this._decorateRpc(this._rpcCore, this._decorateMethod, this._rpc);
239 this._decorateRpc(this._rpcCore, this._rxDecorateMethod, this._rx.rpc);
240 }
241 // extract the actual sections from the methods (this is useful when
242 // we try and create mappings to runtime names via a hash mapping)
243 const sectionMap = {};
244 for (let i = 0, count = methods.length; i < count; i++) {
245 const [section] = methods[i].split('_');
246 sectionMap[section] = true;
247 }
248 // convert the actual section names into an easy name lookup
249 const sections = Object.keys(sectionMap);
250 for (let i = 0, count = sections.length; i < count; i++) {
251 const nameA = stringUpperFirst(sections[i]);
252 const nameB = `${nameA}Api`;
253 this._runtimeMap[blake2AsHex(nameA, 64)] = nameA;
254 this._runtimeMap[blake2AsHex(nameB, 64)] = nameB;
255 }
256 // finally we filter the actual methods to expose
257 this._filterRpcMethods(methods);
258 }
259 _filterRpcMethods(exposed) {
260 const hasResults = exposed.length !== 0;
261 const allKnown = [...this._rpcCore.mapping.entries()];
262 const allKeys = [];
263 const count = allKnown.length;
264 for (let i = 0; i < count; i++) {
265 const [, { alias, endpoint, method, pubsub, section }] = allKnown[i];
266 allKeys.push(`${section}_${method}`);
267 if (pubsub) {
268 allKeys.push(`${section}_${pubsub[1]}`);
269 allKeys.push(`${section}_${pubsub[2]}`);
270 }
271 if (alias) {
272 allKeys.push(...alias);
273 }
274 if (endpoint) {
275 allKeys.push(endpoint);
276 }
277 }
278 const unknown = exposed.filter((k) => !allKeys.includes(k) &&
279 !k.includes('_unstable_'));
280 if (unknown.length && !this._options.noInitWarn) {
281 l.warn(`RPC methods not decorated: ${unknown.join(', ')}`);
282 }
283 // loop through all entries we have (populated in decorate) and filter as required
284 // only remove when we have results and method missing, or with no results if optional
285 for (let i = 0; i < count; i++) {
286 const [k, { method, section }] = allKnown[i];
287 if (hasResults && !exposed.includes(k) && k !== 'rpc_methods') {
288 if (this._rpc[section]) {
289 delete this._rpc[section][method];
290 delete this._rx.rpc[section][method];
291 }
292 }
293 }
294 }
295 _rpcSubmitter(decorateMethod) {
296 const method = (method, ...params) => {
297 return from(this._rpcCore.provider.send(method, params));
298 };
299 return decorateMethod(method);
300 }
301 _decorateRpc(rpc, decorateMethod, input = this._rpcSubmitter(decorateMethod)) {
302 const out = input;
303 const decorateFn = (section, method) => {
304 const source = rpc[section][method];
305 const fn = decorateMethod(source, { methodName: method });
306 fn.meta = source.meta;
307 fn.raw = decorateMethod(source.raw, { methodName: method });
308 return fn;
309 };
310 for (let s = 0, scount = rpc.sections.length; s < scount; s++) {
311 const section = rpc.sections[s];
312 if (!Object.prototype.hasOwnProperty.call(out, section)) {
313 const methods = Object.keys(rpc[section]);
314 const decorateInternal = (method) => decorateFn(section, method);
315 for (let m = 0, mcount = methods.length; m < mcount; m++) {
316 const method = methods[m];
317 // skip subscriptions where we have a non-subscribe interface
318 if (this.hasSubscriptions || !(method.startsWith('subscribe') || method.startsWith('unsubscribe'))) {
319 if (!Object.prototype.hasOwnProperty.call(out, section)) {
320 out[section] = {};
321 }
322 lazyMethod(out[section], method, decorateInternal);
323 }
324 }
325 }
326 }
327 return out;
328 }
329 // add all definition entries
330 _addRuntimeDef(result, additional) {
331 if (!additional) {
332 return;
333 }
334 const entries = Object.entries(additional);
335 for (let j = 0, ecount = entries.length; j < ecount; j++) {
336 const [key, defs] = entries[j];
337 if (result[key]) {
338 // we have this one already, step through for new versions or
339 // new methods and add those as applicable
340 for (let k = 0, dcount = defs.length; k < dcount; k++) {
341 const def = defs[k];
342 const prev = result[key].find(({ version }) => def.version === version);
343 if (prev) {
344 // interleave the new methods with the old - last definition wins
345 objectSpread(prev.methods, def.methods);
346 }
347 else {
348 // we don't have this specific version, add it
349 result[key].push(def);
350 }
351 }
352 }
353 else {
354 // we don't have this runtime definition, add it as-is
355 result[key] = defs;
356 }
357 }
358 }
359 // extract all runtime definitions
360 _getRuntimeDefs(registry, specName, chain = '') {
361 const result = {};
362 const defValues = Object.values(typeDefinitions);
363 // options > chain/spec > built-in, apply in reverse order with
364 // methods overriding previous definitions (or interleave missing)
365 for (let i = 0, count = defValues.length; i < count; i++) {
366 this._addRuntimeDef(result, defValues[i].runtime);
367 }
368 this._addRuntimeDef(result, getSpecRuntime(registry, chain, specName));
369 this._addRuntimeDef(result, this._options.runtime);
370 return Object.entries(result);
371 }
372 // pre-metadata decoration
373 _decorateCalls({ registry, runtimeVersion: { apis, specName, specVersion } }, decorateMethod, blockHash) {
374 const result = {};
375 const named = {};
376 const hashes = {};
377 const sections = this._getRuntimeDefs(registry, specName, this._runtimeChain);
378 const older = [];
379 const implName = `${specName.toString()}/${specVersion.toString()}`;
380 const hasLogged = this.__internal__runtimeLog[implName] || false;
381 this.__internal__runtimeLog[implName] = true;
382 for (let i = 0, scount = sections.length; i < scount; i++) {
383 const [_section, secs] = sections[i];
384 const sectionHash = blake2AsHex(_section, 64);
385 const rtApi = apis.find(([a]) => a.eq(sectionHash));
386 hashes[sectionHash] = true;
387 if (rtApi) {
388 const all = secs.map(({ version }) => version).sort();
389 const sec = secs.find(({ version }) => rtApi[1].eq(version));
390 if (sec) {
391 const section = stringCamelCase(_section);
392 const methods = Object.entries(sec.methods);
393 if (methods.length) {
394 if (!named[section]) {
395 named[section] = {};
396 }
397 for (let m = 0, mcount = methods.length; m < mcount; m++) {
398 const [_method, def] = methods[m];
399 const method = stringCamelCase(_method);
400 named[section][method] = objectSpread({ method, name: `${_section}_${_method}`, section, sectionHash }, def);
401 }
402 }
403 }
404 else {
405 older.push(`${_section}/${rtApi[1].toString()} (${all.join('/')} known)`);
406 }
407 }
408 }
409 // find the runtimes that we don't have hashes for
410 const notFound = apis
411 .map(([a, v]) => [a.toHex(), v.toString()])
412 .filter(([a]) => !hashes[a])
413 .map(([a, v]) => `${this._runtimeMap[a] || a}/${v}`);
414 if (!this._options.noInitWarn && !hasLogged) {
415 if (older.length) {
416 l.warn(`${implName}: Not decorating runtime apis without matching versions: ${older.join(', ')}`);
417 }
418 if (notFound.length) {
419 l.warn(`${implName}: Not decorating unknown runtime apis: ${notFound.join(', ')}`);
420 }
421 }
422 const stateCall = blockHash
423 ? (name, bytes) => this._rpcCore.state.call(name, bytes, blockHash)
424 : (name, bytes) => this._rpcCore.state.call(name, bytes);
425 const lazySection = (section) => lazyMethods({}, Object.keys(named[section]), (method) => this._decorateCall(registry, named[section][method], stateCall, decorateMethod));
426 const modules = Object.keys(named);
427 for (let i = 0, count = modules.length; i < count; i++) {
428 lazyMethod(result, modules[i], lazySection);
429 }
430 return result;
431 }
432 _decorateCall(registry, def, stateCall, decorateMethod) {
433 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
434 const decorated = decorateMethod((...args) => {
435 if (args.length !== def.params.length) {
436 throw new Error(`${def.name}:: Expected ${def.params.length} arguments, found ${args.length}`);
437 }
438 const bytes = registry.createType('Raw', u8aConcatStrict(args.map((a, i) => registry.createTypeUnsafe(def.params[i].type, [a]).toU8a())));
439 return stateCall(def.name, bytes).pipe(map((r) => registry.createTypeUnsafe(def.type, [r])));
440 });
441 decorated.meta = def;
442 // eslint-disable-next-line @typescript-eslint/no-unsafe-return
443 return decorated;
444 }
445 // only be called if supportMulti is true
446 _decorateMulti(decorateMethod) {
447 // eslint-disable-next-line @typescript-eslint/no-unsafe-return
448 return decorateMethod((keys) => keys.length
449 ? (this.hasSubscriptions
450 ? this._rpcCore.state.subscribeStorage
451 : this._rpcCore.state.queryStorageAt)(keys.map((args) => Array.isArray(args)
452 ? args[0].creator.meta.type.isPlain
453 ? [args[0].creator]
454 : args[0].creator.meta.type.asMap.hashers.length === 1
455 ? [args[0].creator, args.slice(1)]
456 : [args[0].creator, ...args.slice(1)]
457 : [args.creator]))
458 : of([]));
459 }
460 _decorateMultiAt(atApi, decorateMethod, blockHash) {
461 // eslint-disable-next-line @typescript-eslint/no-unsafe-return
462 return decorateMethod((calls) => calls.length
463 ? this._rpcCore.state.queryStorageAt(calls.map((args) => {
464 if (Array.isArray(args)) {
465 const { creator } = getAtQueryFn(atApi, args[0].creator);
466 return creator.meta.type.isPlain
467 ? [creator]
468 : creator.meta.type.asMap.hashers.length === 1
469 ? [creator, args.slice(1)]
470 : [creator, ...args.slice(1)];
471 }
472 return [getAtQueryFn(atApi, args.creator).creator];
473 }), blockHash)
474 : of([]));
475 }
476 _decorateExtrinsics({ tx }, decorateMethod) {
477 const result = createSubmittable(this._type, this._rx, decorateMethod);
478 const lazySection = (section) => lazyMethods({}, Object.keys(tx[section]), (method) => method.startsWith('$')
479 ? tx[section][method]
480 : this._decorateExtrinsicEntry(tx[section][method], result));
481 const sections = Object.keys(tx);
482 for (let i = 0, count = sections.length; i < count; i++) {
483 lazyMethod(result, sections[i], lazySection);
484 }
485 return result;
486 }
487 _decorateExtrinsicEntry(method, creator) {
488 const decorated = (...params) => creator(method(...params));
489 // pass through the `.is`
490 decorated.is = (other) => method.is(other);
491 // eslint-disable-next-line @typescript-eslint/no-unsafe-return
492 return this._decorateFunctionMeta(method, decorated);
493 }
494 _decorateStorage({ query, registry }, decorateMethod, blockHash) {
495 const result = {};
496 const lazySection = (section) => lazyMethods({}, Object.keys(query[section]), (method) => blockHash
497 ? this._decorateStorageEntryAt(registry, query[section][method], decorateMethod, blockHash)
498 : this._decorateStorageEntry(query[section][method], decorateMethod));
499 const sections = Object.keys(query);
500 for (let i = 0, count = sections.length; i < count; i++) {
501 lazyMethod(result, sections[i], lazySection);
502 }
503 return result;
504 }
505 _decorateStorageEntry(creator, decorateMethod) {
506 const getArgs = (args, registry) => extractStorageArgs(registry || this.__internal__registry, creator, args);
507 const getQueryAt = (blockHash) => from(this.at(blockHash)).pipe(map((api) => getAtQueryFn(api, creator)));
508 // Disable this where it occurs for each field we are decorating
509 /* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
510 const decorated = this._decorateStorageCall(creator, decorateMethod);
511 decorated.creator = creator;
512 // eslint-disable-next-line deprecation/deprecation
513 decorated.at = decorateMethod((blockHash, ...args) => getQueryAt(blockHash).pipe(switchMap((q) => q(...args))));
514 decorated.hash = decorateMethod((...args) => this._rpcCore.state.getStorageHash(getArgs(args)));
515 decorated.is = (key) => key.section === creator.section &&
516 key.method === creator.method;
517 decorated.key = (...args) => u8aToHex(compactStripLength(creator(...args))[1]);
518 decorated.keyPrefix = (...args) => u8aToHex(creator.keyPrefix(...args));
519 decorated.size = decorateMethod((...args) => this._rpcCore.state.getStorageSize(getArgs(args)));
520 // eslint-disable-next-line deprecation/deprecation
521 decorated.sizeAt = decorateMethod((blockHash, ...args) => getQueryAt(blockHash).pipe(switchMap((q) => this._rpcCore.state.getStorageSize(getArgs(args, q.creator.meta.registry), blockHash))));
522 // .keys() & .entries() only available on map types
523 if (creator.iterKey && creator.meta.type.isMap) {
524 decorated.entries = decorateMethod(memo(this.__internal__instanceId, (...args) => this._retrieveMapEntries(creator, null, args)));
525 // eslint-disable-next-line deprecation/deprecation
526 decorated.entriesAt = decorateMethod(memo(this.__internal__instanceId, (blockHash, ...args) => getQueryAt(blockHash).pipe(switchMap((q) => this._retrieveMapEntries(q.creator, blockHash, args)))));
527 decorated.entriesPaged = decorateMethod(memo(this.__internal__instanceId, (opts) => this._retrieveMapEntriesPaged(creator, undefined, opts)));
528 decorated.keys = decorateMethod(memo(this.__internal__instanceId, (...args) => this._retrieveMapKeys(creator, null, args)));
529 // eslint-disable-next-line deprecation/deprecation
530 decorated.keysAt = decorateMethod(memo(this.__internal__instanceId, (blockHash, ...args) => getQueryAt(blockHash).pipe(switchMap((q) => this._retrieveMapKeys(q.creator, blockHash, args)))));
531 decorated.keysPaged = decorateMethod(memo(this.__internal__instanceId, (opts) => this._retrieveMapKeysPaged(creator, undefined, opts)));
532 }
533 if (this.supportMulti && creator.meta.type.isMap) {
534 // When using double map storage function, user need to pass double map key as an array
535 decorated.multi = decorateMethod((args) => creator.meta.type.asMap.hashers.length === 1
536 ? this._retrieveMulti(args.map((a) => [creator, [a]]))
537 : this._retrieveMulti(args.map((a) => [creator, a])));
538 }
539 /* eslint-enable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
540 return this._decorateFunctionMeta(creator, decorated);
541 }
542 _decorateStorageEntryAt(registry, creator, decorateMethod, blockHash) {
543 const getArgs = (args) => extractStorageArgs(registry, creator, args);
544 // Disable this where it occurs for each field we are decorating
545 /* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
546 const decorated = decorateMethod((...args) => this._rpcCore.state.getStorage(getArgs(args), blockHash));
547 decorated.creator = creator;
548 decorated.hash = decorateMethod((...args) => this._rpcCore.state.getStorageHash(getArgs(args), blockHash));
549 decorated.is = (key) => key.section === creator.section &&
550 key.method === creator.method;
551 decorated.key = (...args) => u8aToHex(compactStripLength(creator(...args))[1]);
552 decorated.keyPrefix = (...keys) => u8aToHex(creator.keyPrefix(...keys));
553 decorated.size = decorateMethod((...args) => this._rpcCore.state.getStorageSize(getArgs(args), blockHash));
554 // .keys() & .entries() only available on map types
555 if (creator.iterKey && creator.meta.type.isMap) {
556 decorated.entries = decorateMethod(memo(this.__internal__instanceId, (...args) => this._retrieveMapEntries(creator, blockHash, args)));
557 decorated.entriesPaged = decorateMethod(memo(this.__internal__instanceId, (opts) => this._retrieveMapEntriesPaged(creator, blockHash, opts)));
558 decorated.keys = decorateMethod(memo(this.__internal__instanceId, (...args) => this._retrieveMapKeys(creator, blockHash, args)));
559 decorated.keysPaged = decorateMethod(memo(this.__internal__instanceId, (opts) => this._retrieveMapKeysPaged(creator, blockHash, opts)));
560 }
561 if (this.supportMulti && creator.meta.type.isMap) {
562 // When using double map storage function, user need to pass double map key as an array
563 decorated.multi = decorateMethod((args) => creator.meta.type.asMap.hashers.length === 1
564 ? this._retrieveMulti(args.map((a) => [creator, [a]]), blockHash)
565 : this._retrieveMulti(args.map((a) => [creator, a]), blockHash));
566 }
567 /* eslint-enable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
568 return this._decorateFunctionMeta(creator, decorated);
569 }
570 _queueStorage(call, queue) {
571 const query = queue === this.__internal__storageSubQ
572 ? this._rpcCore.state.subscribeStorage
573 : this._rpcCore.state.queryStorageAt;
574 let queueIdx = queue.length - 1;
575 let valueIdx = 0;
576 let valueObs;
577 // if we don't have queue entries yet,
578 // or the current queue has fired (see from below),
579 // or the current queue has the max entries,
580 // then we create a new queue
581 if (queueIdx === -1 || !queue[queueIdx] || queue[queueIdx][1].length === PAGE_SIZE_Q) {
582 queueIdx++;
583 valueObs = from(
584 // we delay the execution until the next tick, this allows
585 // any queries made in this timeframe to be added to the same
586 // queue for a single query
587 new Promise((resolve) => {
588 nextTick(() => {
589 // get all the calls in this instance, resolve with it
590 // and then clear the queue so we don't add more
591 // (anything after this will be added to a new queue)
592 const calls = queue[queueIdx][1];
593 delete queue[queueIdx];
594 resolve(calls);
595 });
596 })).pipe(switchMap((calls) => query(calls)));
597 queue.push([valueObs, [call]]);
598 }
599 else {
600 valueObs = queue[queueIdx][0];
601 valueIdx = queue[queueIdx][1].length;
602 queue[queueIdx][1].push(call);
603 }
604 return valueObs.pipe(
605 // return the single value at this index
606 map((values) => values[valueIdx]));
607 }
608 // Decorate the base storage call. In the case or rxjs or promise-without-callback (await)
609 // we make a subscription, alternatively we push this through a single-shot query
610 _decorateStorageCall(creator, decorateMethod) {
611 const memoed = memo(this.__internal__instanceId, (...args) => {
612 const call = extractStorageArgs(this.__internal__registry, creator, args);
613 if (!this.hasSubscriptions) {
614 return this._rpcCore.state.getStorage(call);
615 }
616 return this._queueStorage(call, this.__internal__storageSubQ);
617 });
618 return decorateMethod(memoed, {
619 methodName: creator.method,
620 overrideNoSub: (...args) => this._queueStorage(extractStorageArgs(this.__internal__registry, creator, args), this.__internal__storageGetQ)
621 });
622 }
623 // retrieve a set of values for a specific set of keys - here we chunk the keys into PAGE_SIZE sizes
624 _retrieveMulti(keys, blockHash) {
625 if (!keys.length) {
626 return of([]);
627 }
628 const query = this.hasSubscriptions && !blockHash
629 ? this._rpcCore.state.subscribeStorage
630 : this._rpcCore.state.queryStorageAt;
631 if (keys.length <= PAGE_SIZE_V) {
632 return blockHash
633 ? query(keys, blockHash)
634 : query(keys);
635 }
636 return combineLatest(arrayChunk(keys, PAGE_SIZE_V).map((k) => blockHash
637 ? query(k, blockHash)
638 : query(k))).pipe(map(arrayFlatten));
639 }
640 _retrieveMapKeys({ iterKey, meta, method, section }, at, args) {
641 if (!iterKey || !meta.type.isMap) {
642 throw new Error('keys can only be retrieved on maps');
643 }
644 const headKey = iterKey(...args).toHex();
645 const startSubject = new BehaviorSubject(headKey);
646 const query = at
647 ? (startKey) => this._rpcCore.state.getKeysPaged(headKey, PAGE_SIZE_K, startKey, at)
648 : (startKey) => this._rpcCore.state.getKeysPaged(headKey, PAGE_SIZE_K, startKey);
649 const setMeta = (key) => key.setMeta(meta, section, method);
650 return startSubject.pipe(switchMap(query), map((keys) => keys.map(setMeta)), tap((keys) => nextTick(() => {
651 keys.length === PAGE_SIZE_K
652 ? startSubject.next(keys[PAGE_SIZE_K - 1].toHex())
653 : startSubject.complete();
654 })), toArray(), // toArray since we want to startSubject to be completed
655 map(arrayFlatten));
656 }
657 _retrieveMapKeysPaged({ iterKey, meta, method, section }, at, opts) {
658 if (!iterKey || !meta.type.isMap) {
659 throw new Error('keys can only be retrieved on maps');
660 }
661 const setMeta = (key) => key.setMeta(meta, section, method);
662 const query = at
663 ? (headKey) => this._rpcCore.state.getKeysPaged(headKey, opts.pageSize, opts.startKey || headKey, at)
664 : (headKey) => this._rpcCore.state.getKeysPaged(headKey, opts.pageSize, opts.startKey || headKey);
665 return query(iterKey(...opts.args).toHex()).pipe(map((keys) => keys.map(setMeta)));
666 }
667 _retrieveMapEntries(entry, at, args) {
668 const query = at
669 ? (keys) => this._rpcCore.state.queryStorageAt(keys, at)
670 : (keys) => this._rpcCore.state.queryStorageAt(keys);
671 return this._retrieveMapKeys(entry, at, args).pipe(switchMap((keys) => keys.length
672 ? combineLatest(arrayChunk(keys, PAGE_SIZE_V).map(query)).pipe(map((valsArr) => arrayFlatten(valsArr).map((value, index) => [keys[index], value])))
673 : of([])));
674 }
675 _retrieveMapEntriesPaged(entry, at, opts) {
676 const query = at
677 ? (keys) => this._rpcCore.state.queryStorageAt(keys, at)
678 : (keys) => this._rpcCore.state.queryStorageAt(keys);
679 return this._retrieveMapKeysPaged(entry, at, opts).pipe(switchMap((keys) => keys.length
680 ? query(keys).pipe(map((valsArr) => valsArr.map((value, index) => [keys[index], value])))
681 : of([])));
682 }
683 _decorateDeriveRx(decorateMethod) {
684 const specName = this._runtimeVersion?.specName.toString();
685 // Pull in derive from api-derive
686 const available = getAvailableDerives(this.__internal__instanceId, this._rx, objectSpread({}, this._options.derives, this._options.typesBundle?.spec?.[specName || '']?.derives));
687 return decorateDeriveSections(decorateMethod, available);
688 }
689 _decorateDerive(decorateMethod) {
690 return decorateDeriveSections(decorateMethod, this._rx.derive);
691 }
692 /**
693 * Put the `this.onCall` function of ApiRx here, because it is needed by
694 * `api._rx`.
695 */
696 _rxDecorateMethod = (method) => {
697 return method;
698 };
699}