@novasamatech/host-container 0.6.5 → 0.6.6-0

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/dist/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export { createWebviewProvider } from './createWebviewProvider.js';
2
2
  export { createIframeProvider } from './createIframeProvider.js';
3
3
  export { createContainer } from './createContainer.js';
4
4
  export type { Container } from './types.js';
5
+ export { createRateLimiter } from './rateLimiter.js';
6
+ export type { CreateRateLimiterConfig, RateLimiter, RateLimiterConfig, RateLimiterStrategy } from './rateLimiter.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { createWebviewProvider } from './createWebviewProvider.js';
2
2
  export { createIframeProvider } from './createIframeProvider.js';
3
3
  export { createContainer } from './createContainer.js';
4
+ export { createRateLimiter } from './rateLimiter.js';
@@ -0,0 +1,17 @@
1
+ export declare const RATE_LIMITED_MESSAGE = "Request rate limited";
2
+ export type RateLimiterConfig = {
3
+ maxRequestsPerInterval: number;
4
+ intervalMs: number;
5
+ maxQueuedRequests: number;
6
+ onDrop?(): unknown;
7
+ };
8
+ export type RateLimiterStrategy = 'queue' | 'drop';
9
+ export type CreateRateLimiterConfig = RateLimiterConfig & {
10
+ strategy: RateLimiterStrategy;
11
+ onDrop?(): unknown;
12
+ };
13
+ export type RateLimiter = {
14
+ schedule<T>(execute: () => T | Promise<T>): Promise<T>;
15
+ destroy(): void;
16
+ };
17
+ export declare function createRateLimiter(config: CreateRateLimiterConfig): RateLimiter;
@@ -0,0 +1,129 @@
1
+ import { GenericError } from '@novasamatech/host-api';
2
+ export const RATE_LIMITED_MESSAGE = 'Request rate limited';
3
+ function createQueueStrategy(config) {
4
+ const state = {
5
+ remainingTokens: config.maxRequestsPerInterval,
6
+ lastRefillTimestamp: Date.now(),
7
+ queue: [],
8
+ timerId: null,
9
+ };
10
+ function refillTokens() {
11
+ const now = Date.now();
12
+ const elapsed = now - state.lastRefillTimestamp;
13
+ if (elapsed <= 0)
14
+ return;
15
+ if (elapsed >= config.intervalMs) {
16
+ state.remainingTokens = config.maxRequestsPerInterval;
17
+ state.lastRefillTimestamp = now;
18
+ }
19
+ }
20
+ function processQueue() {
21
+ state.timerId = null;
22
+ refillTokens();
23
+ while (state.remainingTokens > 0 && state.queue.length > 0) {
24
+ const task = state.queue.shift();
25
+ state.remainingTokens -= 1;
26
+ try {
27
+ const result = task.execute();
28
+ if (result != null && typeof result.then === 'function') {
29
+ result.then(task.resolve).catch(task.reject);
30
+ }
31
+ else {
32
+ task.resolve(result);
33
+ }
34
+ }
35
+ catch (error) {
36
+ task.reject(error);
37
+ }
38
+ }
39
+ if (state.queue.length > 0) {
40
+ state.timerId = setTimeout(processQueue, Math.floor(config.intervalMs / 2));
41
+ }
42
+ }
43
+ function ensureProcessingScheduled() {
44
+ if (state.timerId !== null)
45
+ return;
46
+ state.timerId = setTimeout(processQueue, Math.floor(config.intervalMs / 2));
47
+ }
48
+ function schedule(execute) {
49
+ refillTokens();
50
+ if (state.remainingTokens > 0 && state.queue.length === 0) {
51
+ state.remainingTokens -= 1;
52
+ try {
53
+ const result = execute();
54
+ if (result != null && typeof result.then === 'function') {
55
+ return result;
56
+ }
57
+ return Promise.resolve(result);
58
+ }
59
+ catch (error) {
60
+ return Promise.reject(error);
61
+ }
62
+ }
63
+ if (state.queue.length >= config.maxQueuedRequests) {
64
+ return Promise.reject(config.onDrop?.() ?? new GenericError({ reason: RATE_LIMITED_MESSAGE }));
65
+ }
66
+ return new Promise((resolve, reject) => {
67
+ state.queue.push({
68
+ execute: execute,
69
+ resolve: resolve,
70
+ reject,
71
+ });
72
+ ensureProcessingScheduled();
73
+ });
74
+ }
75
+ function destroy() {
76
+ if (state.timerId !== null) {
77
+ clearTimeout(state.timerId);
78
+ state.timerId = null;
79
+ }
80
+ while (state.queue.length > 0) {
81
+ const task = state.queue.shift();
82
+ task.reject(config.onDrop?.() ?? new GenericError({ reason: RATE_LIMITED_MESSAGE }));
83
+ }
84
+ }
85
+ return { schedule, destroy };
86
+ }
87
+ function createDropStrategy(config) {
88
+ const state = {
89
+ remainingTokens: config.maxRequestsPerInterval,
90
+ lastRefillTimestamp: Date.now(),
91
+ };
92
+ const refillTokens = () => {
93
+ const now = Date.now();
94
+ const elapsed = now - state.lastRefillTimestamp;
95
+ if (elapsed >= config.intervalMs) {
96
+ state.remainingTokens = config.maxRequestsPerInterval;
97
+ state.lastRefillTimestamp = now;
98
+ }
99
+ };
100
+ const schedule = (execute) => {
101
+ refillTokens();
102
+ if (state.remainingTokens > 0) {
103
+ state.remainingTokens -= 1;
104
+ try {
105
+ const result = execute();
106
+ if (result != null && typeof result.then === 'function') {
107
+ return result;
108
+ }
109
+ return Promise.resolve(result);
110
+ }
111
+ catch (error) {
112
+ return Promise.reject(error);
113
+ }
114
+ }
115
+ return Promise.reject(config.onDrop?.() ?? new GenericError({ reason: RATE_LIMITED_MESSAGE }));
116
+ };
117
+ return {
118
+ schedule,
119
+ destroy: () => {
120
+ /* no-op: drop strategy has no timers or queue */
121
+ },
122
+ };
123
+ }
124
+ export function createRateLimiter(config) {
125
+ if (config.strategy === 'queue') {
126
+ return createQueueStrategy(config);
127
+ }
128
+ return createDropStrategy(config);
129
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-container",
3
3
  "type": "module",
4
- "version": "0.6.5",
4
+ "version": "0.6.6-0",
5
5
  "description": "Host container for hosting and managing products within the Polkadot ecosystem.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -9,9 +9,7 @@
9
9
  "url": "git+https://github.com/Polkadot-Community-Foundation/triangle-js-sdks.git"
10
10
  },
11
11
  "keywords": [
12
- "polkadot",
13
- "spektr",
14
- "dapp"
12
+ "polkadot"
15
13
  ],
16
14
  "main": "dist/index.js",
17
15
  "exports": {
@@ -28,7 +26,7 @@
28
26
  "dependencies": {
29
27
  "@polkadot-api/utils": "^0.2.0",
30
28
  "@polkadot-api/json-rpc-provider": "^0.0.4",
31
- "@novasamatech/host-api": "0.6.5",
29
+ "@novasamatech/host-api": "0.6.6-0",
32
30
  "nanoid": "5.1.6"
33
31
  },
34
32
  "devDependencies": {
@@ -1,2 +0,0 @@
1
- export declare const JSON_RPC_PROTOCOL_MAP: Readonly<Record<string, string>>;
2
- export declare const PROTOCOL_TO_RPC_MAP: Readonly<Record<string, string>>;
@@ -1,21 +0,0 @@
1
- // Maps JSON RPC method names to individual protocol keys
2
- export const JSON_RPC_PROTOCOL_MAP = {
3
- chainHead_v1_follow: 'host_chain_head_v1_follow',
4
- chainHead_v1_unfollow: 'host_chain_head_v1_unfollow',
5
- chainHead_v1_header: 'host_chain_head_v1_header',
6
- chainHead_v1_body: 'host_chain_head_v1_body',
7
- chainHead_v1_call: 'host_chain_head_v1_call',
8
- chainHead_v1_storage: 'host_chain_head_v1_storage',
9
- chainHead_v1_continue: 'host_chain_head_v1_continue',
10
- chainHead_v1_stopOperation: 'host_chain_head_v1_stop_operation',
11
- chainHead_v1_unpin: 'host_chain_head_v1_unpin',
12
- chainSpec_v1_chainName: 'host_chain_spec_v1_chain_name',
13
- chainSpec_v1_genesisHash: 'host_chain_spec_v1_genesis_hash',
14
- chainSpec_v1_properties: 'host_chain_spec_v1_properties',
15
- transaction_v1_broadcast: 'host_transaction_v1_broadcast',
16
- transaction_v1_stop: 'host_transaction_v1_stop',
17
- transactionWatch_v1_submitAndWatch: 'host_transaction_watch_v1_submit_and_watch',
18
- transactionWatch_v1_unwatch: 'host_transaction_watch_v1_unwatch',
19
- };
20
- // Inverse map: protocol key → expected JSON RPC method (for container validation)
21
- export const PROTOCOL_TO_RPC_MAP = Object.fromEntries(Object.entries(JSON_RPC_PROTOCOL_MAP).map(([rpc, protocol]) => [protocol, rpc]));