@dedot/api 0.9.3 → 0.9.5-next.11b0ca8e.3

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.
@@ -80,11 +80,18 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
80
80
  const defer = (0, utils_1.deferred)();
81
81
  try {
82
82
  this.#unsub && this.#unsub().catch(utils_1.noop); // ensure unfollowed
83
+ let signals = 0;
83
84
  this.#unsub = await this.send('follow', true, (event, subscription) => {
84
85
  this.#followResponseQueue
85
86
  .enqueue(async () => {
86
87
  await this.#onFollowEvent(event, subscription);
87
- if (event.event == 'initialized') {
88
+ if (signals >= 2)
89
+ return;
90
+ signals += 1;
91
+ // Sometime smoldot send a `stop` event right after `initialized`
92
+ // So requests sending between `initialized` and `stop` will be on pruned hashes -> throwing out errors
93
+ // Here we make sure to receive at least the first 2 signals to resolve
94
+ if (signals >= 2) {
88
95
  defer.resolve();
89
96
  }
90
97
  })
@@ -167,28 +174,18 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
167
174
  return;
168
175
  this.#finalizedQueue.push(hash);
169
176
  });
170
- this.emit('finalizedBlock', this.findBlock(this.#finalizedHash));
171
- // TODO should we find all descendants of the pruned blocks and unpin them as well?
172
- // that's probably a premature optimization
173
- const finalizedBlockHeights = finalizedBlockHashes.map((hash) => this.findBlock(hash).number);
174
- const pinnedHashes = Object.keys(this.#pinnedBlocks);
175
- const hashesToUnpin = new Set([
176
- ...prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)),
177
- // Since we have the current finalized blocks,
178
- // we can mark all the other blocks at the same height as pruned and unpin all together with the reported pruned blocks
179
- ...Object.values(this.#pinnedBlocks)
180
- .filter((b) => finalizedBlockHeights.includes(b.number))
181
- .filter((b) => !finalizedBlockHashes.includes(b.hash))
182
- .map((b) => b.hash),
183
- ]);
177
+ const currentFinalizedBlock = this.findBlock(this.#finalizedHash);
178
+ this.emit('finalizedBlock', currentFinalizedBlock);
184
179
  // TODO account for operations that haven't received its operationId yet
185
180
  Object.values(this.#handlers).forEach(({ defer, hash, operationId }) => {
186
- if (hashesToUnpin.has(hash)) {
181
+ if (prunedBlockHashes.includes(hash)) {
187
182
  defer.reject(new error_js_1.ChainHeadBlockPrunedError());
188
183
  this.stopOperation(operationId).catch(utils_1.noop);
189
184
  delete this.#handlers[operationId];
190
185
  }
191
186
  });
187
+ const pinnedHashes = Object.keys(this.#pinnedBlocks);
188
+ const hashesToUnpin = new Set(prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)));
192
189
  // Unpin the oldest finalized pinned blocks to maintain the queue size
193
190
  if (this.#finalizedQueue.length > exports.MIN_FINALIZED_QUEUE_SIZE) {
194
191
  const finalizedQueue = this.#finalizedQueue.slice();
@@ -206,6 +203,17 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
206
203
  });
207
204
  this.#finalizedQueue = finalizedQueue;
208
205
  }
206
+ // Unpin all obsolete blocks with blockNumber < the latest finalized block number
207
+ // & not a finalized block & is not in use
208
+ pinnedHashes.forEach((hash) => {
209
+ if (this.#blockUsage.usage(hash) > 0)
210
+ return;
211
+ if (this.#finalizedQueue.includes(hash))
212
+ return;
213
+ if (this.findBlock(hash).number > currentFinalizedBlock.number)
214
+ return;
215
+ hashesToUnpin.add(hash);
216
+ });
209
217
  hashesToUnpin.forEach((hash) => {
210
218
  if (!this.isPinned(hash))
211
219
  return;
@@ -421,17 +429,15 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
421
429
  async #retryOperation(strategy, retry) {
422
430
  try {
423
431
  return await new Promise((resolve, reject) => {
424
- setTimeout(() => {
425
- if (strategy === error_js_1.RetryStrategy.NOW) {
426
- retry().then(resolve).catch(reject);
427
- }
428
- else if (strategy === error_js_1.RetryStrategy.QUEUED) {
429
- this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
430
- }
431
- else {
432
- throw new Error('Invalid retry strategy');
433
- }
434
- }); // retry again in the next tick
432
+ if (strategy === error_js_1.RetryStrategy.NOW) {
433
+ retry().then(resolve).catch(reject);
434
+ }
435
+ else if (strategy === error_js_1.RetryStrategy.QUEUED) {
436
+ this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
437
+ }
438
+ else {
439
+ throw new Error('Invalid retry strategy');
440
+ }
435
441
  });
436
442
  }
437
443
  catch (e) {
@@ -555,16 +561,13 @@ class ChainHead extends JsonRpcGroup_js_1.JsonRpcGroup {
555
561
  const isSmoldot = typeof this.client.provider['chain'] === 'function';
556
562
  if (isSmoldot) {
557
563
  const fetchItem = async (item) => {
558
- const [newBatch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
564
+ const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
559
565
  if (newDiscardedItems.length > 0) {
560
566
  return fetchItem(item);
561
567
  }
562
- if (newBatch.length === 0) {
563
- return { key: item.key, value: undefined };
564
- }
565
- return newBatch[0];
568
+ return batch;
566
569
  };
567
- results = await Promise.all(items.map((one) => fetchItem(one)));
570
+ results = (await Promise.all(items.map((one) => fetchItem(one)))).flat();
568
571
  }
569
572
  else {
570
573
  let queryItems = items;
@@ -77,11 +77,18 @@ export class ChainHead extends JsonRpcGroup {
77
77
  const defer = deferred();
78
78
  try {
79
79
  this.#unsub && this.#unsub().catch(noop); // ensure unfollowed
80
+ let signals = 0;
80
81
  this.#unsub = await this.send('follow', true, (event, subscription) => {
81
82
  this.#followResponseQueue
82
83
  .enqueue(async () => {
83
84
  await this.#onFollowEvent(event, subscription);
84
- if (event.event == 'initialized') {
85
+ if (signals >= 2)
86
+ return;
87
+ signals += 1;
88
+ // Sometime smoldot send a `stop` event right after `initialized`
89
+ // So requests sending between `initialized` and `stop` will be on pruned hashes -> throwing out errors
90
+ // Here we make sure to receive at least the first 2 signals to resolve
91
+ if (signals >= 2) {
85
92
  defer.resolve();
86
93
  }
87
94
  })
@@ -164,28 +171,18 @@ export class ChainHead extends JsonRpcGroup {
164
171
  return;
165
172
  this.#finalizedQueue.push(hash);
166
173
  });
167
- this.emit('finalizedBlock', this.findBlock(this.#finalizedHash));
168
- // TODO should we find all descendants of the pruned blocks and unpin them as well?
169
- // that's probably a premature optimization
170
- const finalizedBlockHeights = finalizedBlockHashes.map((hash) => this.findBlock(hash).number);
171
- const pinnedHashes = Object.keys(this.#pinnedBlocks);
172
- const hashesToUnpin = new Set([
173
- ...prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)),
174
- // Since we have the current finalized blocks,
175
- // we can mark all the other blocks at the same height as pruned and unpin all together with the reported pruned blocks
176
- ...Object.values(this.#pinnedBlocks)
177
- .filter((b) => finalizedBlockHeights.includes(b.number))
178
- .filter((b) => !finalizedBlockHashes.includes(b.hash))
179
- .map((b) => b.hash),
180
- ]);
174
+ const currentFinalizedBlock = this.findBlock(this.#finalizedHash);
175
+ this.emit('finalizedBlock', currentFinalizedBlock);
181
176
  // TODO account for operations that haven't received its operationId yet
182
177
  Object.values(this.#handlers).forEach(({ defer, hash, operationId }) => {
183
- if (hashesToUnpin.has(hash)) {
178
+ if (prunedBlockHashes.includes(hash)) {
184
179
  defer.reject(new ChainHeadBlockPrunedError());
185
180
  this.stopOperation(operationId).catch(noop);
186
181
  delete this.#handlers[operationId];
187
182
  }
188
183
  });
184
+ const pinnedHashes = Object.keys(this.#pinnedBlocks);
185
+ const hashesToUnpin = new Set(prunedBlockHashes.filter((hash) => pinnedHashes.includes(hash)));
189
186
  // Unpin the oldest finalized pinned blocks to maintain the queue size
190
187
  if (this.#finalizedQueue.length > MIN_FINALIZED_QUEUE_SIZE) {
191
188
  const finalizedQueue = this.#finalizedQueue.slice();
@@ -203,6 +200,17 @@ export class ChainHead extends JsonRpcGroup {
203
200
  });
204
201
  this.#finalizedQueue = finalizedQueue;
205
202
  }
203
+ // Unpin all obsolete blocks with blockNumber < the latest finalized block number
204
+ // & not a finalized block & is not in use
205
+ pinnedHashes.forEach((hash) => {
206
+ if (this.#blockUsage.usage(hash) > 0)
207
+ return;
208
+ if (this.#finalizedQueue.includes(hash))
209
+ return;
210
+ if (this.findBlock(hash).number > currentFinalizedBlock.number)
211
+ return;
212
+ hashesToUnpin.add(hash);
213
+ });
206
214
  hashesToUnpin.forEach((hash) => {
207
215
  if (!this.isPinned(hash))
208
216
  return;
@@ -418,17 +426,15 @@ export class ChainHead extends JsonRpcGroup {
418
426
  async #retryOperation(strategy, retry) {
419
427
  try {
420
428
  return await new Promise((resolve, reject) => {
421
- setTimeout(() => {
422
- if (strategy === RetryStrategy.NOW) {
423
- retry().then(resolve).catch(reject);
424
- }
425
- else if (strategy === RetryStrategy.QUEUED) {
426
- this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
427
- }
428
- else {
429
- throw new Error('Invalid retry strategy');
430
- }
431
- }); // retry again in the next tick
429
+ if (strategy === RetryStrategy.NOW) {
430
+ retry().then(resolve).catch(reject);
431
+ }
432
+ else if (strategy === RetryStrategy.QUEUED) {
433
+ this.#retryQueue.enqueue(retry).then(resolve).catch(reject);
434
+ }
435
+ else {
436
+ throw new Error('Invalid retry strategy');
437
+ }
432
438
  });
433
439
  }
434
440
  catch (e) {
@@ -552,16 +558,13 @@ export class ChainHead extends JsonRpcGroup {
552
558
  const isSmoldot = typeof this.client.provider['chain'] === 'function';
553
559
  if (isSmoldot) {
554
560
  const fetchItem = async (item) => {
555
- const [newBatch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
561
+ const [batch, newDiscardedItems] = await this.#getStorage([item], childTrie ?? null, hash);
556
562
  if (newDiscardedItems.length > 0) {
557
563
  return fetchItem(item);
558
564
  }
559
- if (newBatch.length === 0) {
560
- return { key: item.key, value: undefined };
561
- }
562
- return newBatch[0];
565
+ return batch;
563
566
  };
564
- results = await Promise.all(items.map((one) => fetchItem(one)));
567
+ results = (await Promise.all(items.map((one) => fetchItem(one)))).flat();
565
568
  }
566
569
  else {
567
570
  let queryItems = items;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dedot/api",
3
- "version": "0.9.3",
3
+ "version": "0.9.5-next.11b0ca8e.3+11b0ca8",
4
4
  "description": "A delightful JavaScript/TypeScript client for Polkadot & Substrate",
5
5
  "author": "Thang X. Vu <thang@dedot.dev>",
6
6
  "homepage": "https://dedot.dev",
@@ -13,13 +13,13 @@
13
13
  "type": "module",
14
14
  "sideEffects": false,
15
15
  "dependencies": {
16
- "@dedot/codecs": "0.9.3",
17
- "@dedot/providers": "0.9.3",
18
- "@dedot/runtime-specs": "0.9.3",
19
- "@dedot/shape": "0.9.3",
20
- "@dedot/storage": "0.9.3",
21
- "@dedot/types": "0.9.3",
22
- "@dedot/utils": "0.9.3"
16
+ "@dedot/codecs": "0.9.5-next.11b0ca8e.3+11b0ca8",
17
+ "@dedot/providers": "0.9.5-next.11b0ca8e.3+11b0ca8",
18
+ "@dedot/runtime-specs": "0.9.5-next.11b0ca8e.3+11b0ca8",
19
+ "@dedot/shape": "0.9.5-next.11b0ca8e.3+11b0ca8",
20
+ "@dedot/storage": "0.9.5-next.11b0ca8e.3+11b0ca8",
21
+ "@dedot/types": "0.9.5-next.11b0ca8e.3+11b0ca8",
22
+ "@dedot/utils": "0.9.5-next.11b0ca8e.3+11b0ca8"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.cjs.json",
@@ -48,7 +48,7 @@
48
48
  "node": ">=18"
49
49
  },
50
50
  "license": "Apache-2.0",
51
- "gitHead": "206c4999865cddbd1078bf5b059c7b49822e20d0",
51
+ "gitHead": "11b0ca8e2df2c59e12259dc2ba0c61ec91e181cf",
52
52
  "module": "./index.js",
53
53
  "types": "./index.d.ts"
54
54
  }