@api3/commons 0.10.0 → 0.12.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/README.md +2 -0
- package/dist/run-in-loop/index.d.ts +46 -0
- package/dist/run-in-loop/index.d.ts.map +1 -0
- package/dist/run-in-loop/index.js +53 -0
- package/dist/run-in-loop/index.js.map +1 -0
- package/dist/universal-index.d.ts +2 -0
- package/dist/universal-index.d.ts.map +1 -1
- package/dist/universal-index.js +2 -0
- package/dist/universal-index.js.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +10 -10
- package/src/run-in-loop/README.md +3 -0
- package/src/run-in-loop/index.test.ts +23 -0
- package/src/run-in-loop/index.ts +110 -0
- package/src/universal-index.ts +2 -0
- package/src/utils/README.md +3 -0
- package/src/utils/index.ts +7 -0
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Read the documentation and sources of each module how to use it in the project.
|
|
|
25
25
|
- [http](./src/http/README.md)
|
|
26
26
|
- [logger](./src/logger/README.md)
|
|
27
27
|
- [processing](./src/processing/README.md)
|
|
28
|
+
- [run-in-loop](./src/run-in-loop/README.md)
|
|
29
|
+
- [utils](./src/utils/README.md)
|
|
28
30
|
|
|
29
31
|
## Using the package in universal context
|
|
30
32
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Logger } from '../logger';
|
|
2
|
+
export interface RunInLoopOptions {
|
|
3
|
+
/** An API3 logger instance required to execute the callback with context. */
|
|
4
|
+
logger: Logger;
|
|
5
|
+
/** Part of every message logged by the logger. */
|
|
6
|
+
logLabel?: Lowercase<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Minimum time to wait between executions. E.g. value 500 means that the next execution will be started only after
|
|
9
|
+
* 500ms from the start of the previous one (even if the previous one ended after 150ms). Default is 0.
|
|
10
|
+
*/
|
|
11
|
+
frequencyMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Minimum time to wait between executions. E.g. value 50 means that next execution will start 50ms after the previous
|
|
14
|
+
* one ended. Default is 0.
|
|
15
|
+
*/
|
|
16
|
+
minWaitTimeMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum time to wait between executions. E.g. value 100 means that the next execution will be started at most after
|
|
19
|
+
* 100ms from the end of the previous one. The maxWaitTime has higher precedence than minWaitTime and frequencyMs.
|
|
20
|
+
*/
|
|
21
|
+
maxWaitTimeMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum time to wait for the execution to finish. If the execution exceeds this time a warning is logged.
|
|
24
|
+
*/
|
|
25
|
+
softTimeoutMs?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum time to wait for the callback execution to finish. If the execution exceeds this time an error is logged and the
|
|
28
|
+
* execution is force-stopped.
|
|
29
|
+
*/
|
|
30
|
+
hardTimeoutMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* If false, the execution will not run. This is useful for temporarily disabling the execution (e.g. change
|
|
33
|
+
* environment variable and redeploy). Note that, there is no way to stop the loop execution once started. Default is
|
|
34
|
+
* true.
|
|
35
|
+
*/
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The initial delay to to wait before executing the callback for the first time. Default is 0, which means the
|
|
39
|
+
* callback is executed immediately.
|
|
40
|
+
*/
|
|
41
|
+
initialDelayMs?: number;
|
|
42
|
+
}
|
|
43
|
+
export declare const runInLoop: (fn: () => Promise<{
|
|
44
|
+
shouldContinueRunning: boolean;
|
|
45
|
+
} | void>, options: RunInLoopOptions) => Promise<void>;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/run-in-loop/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,SAAS,OAChB,MAAM,OAAO,CAAC;IAAE,qBAAqB,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,WACnD,gBAAgB,kBA4D1B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInLoop = void 0;
|
|
4
|
+
const promise_utils_1 = require("@api3/promise-utils");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const runInLoop = async (fn, options) => {
|
|
7
|
+
const { logger, logLabel, frequencyMs = 0, minWaitTimeMs = 0, maxWaitTimeMs, softTimeoutMs, hardTimeoutMs, enabled = true, initialDelayMs, } = options;
|
|
8
|
+
if (softTimeoutMs && hardTimeoutMs && hardTimeoutMs < softTimeoutMs) {
|
|
9
|
+
throw new Error('hardTimeoutMs must not be smaller than softTimeoutMs');
|
|
10
|
+
}
|
|
11
|
+
if (minWaitTimeMs && maxWaitTimeMs && maxWaitTimeMs < minWaitTimeMs) {
|
|
12
|
+
throw new Error('maxWaitTimeMs must not be smaller than minWaitTimeMs');
|
|
13
|
+
}
|
|
14
|
+
if (initialDelayMs)
|
|
15
|
+
await (0, utils_1.sleep)(initialDelayMs);
|
|
16
|
+
while (true) {
|
|
17
|
+
const executionStart = performance.now();
|
|
18
|
+
const executionId = (0, utils_1.generateRandomBytes32)();
|
|
19
|
+
if (enabled) {
|
|
20
|
+
const context = logLabel ? { executionId, label: logLabel } : { executionId };
|
|
21
|
+
const shouldContinueRunning = await logger.runWithContext(context, async () => {
|
|
22
|
+
const goRes = await (0, promise_utils_1.go)(fn, hardTimeoutMs ? { totalTimeoutMs: hardTimeoutMs } : {}); // NOTE: This is a safety net to prevent the loop from hanging
|
|
23
|
+
if (!goRes.success) {
|
|
24
|
+
logger.error(`Unexpected runInLoop error`, goRes.error);
|
|
25
|
+
}
|
|
26
|
+
const executionTimeMs = performance.now() - executionStart;
|
|
27
|
+
if (executionTimeMs >= softTimeoutMs) {
|
|
28
|
+
logger.warn(`Execution took longer than the interval`, { executionTimeMs });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
logger.info(`Execution finished`, { executionTimeMs });
|
|
32
|
+
}
|
|
33
|
+
return goRes.data?.shouldContinueRunning === false ? false : true;
|
|
34
|
+
});
|
|
35
|
+
// Stop loop execution if the callback return value indicates it should stop
|
|
36
|
+
if (!shouldContinueRunning) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// If the bot is disabled, we still want to run the loop to prevent the process from hanging. We also want to
|
|
42
|
+
// sleep according to the wait time logic.
|
|
43
|
+
logger.info('Loop execution is disabled.');
|
|
44
|
+
}
|
|
45
|
+
const remainingWaitTime = Math.max(frequencyMs - (performance.now() - executionStart), 0);
|
|
46
|
+
const waitTime = Math.max(minWaitTimeMs, remainingWaitTime);
|
|
47
|
+
const actualWaitTime = maxWaitTimeMs ? Math.min(waitTime, maxWaitTimeMs) : waitTime;
|
|
48
|
+
if (actualWaitTime > 0)
|
|
49
|
+
await (0, utils_1.sleep)(actualWaitTime);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.runInLoop = runInLoop;
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/run-in-loop/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"universal-index.d.ts","sourceRoot":"","sources":["../src/universal-index.ts"],"names":[],"mappings":"AACA,cAAc,wBAAwB,CAAC;AACvC,cAAc,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"universal-index.d.ts","sourceRoot":"","sources":["../src/universal-index.ts"],"names":[],"mappings":"AACA,cAAc,wBAAwB,CAAC;AACvC,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"}
|
package/dist/universal-index.js
CHANGED
|
@@ -17,4 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
// NOTE: Only export modules which work in both Node.js and browser environments.
|
|
18
18
|
__exportStar(require("./blockchain-utilities"), exports);
|
|
19
19
|
__exportStar(require("./http"), exports);
|
|
20
|
+
__exportStar(require("./run-in-loop"), exports);
|
|
21
|
+
__exportStar(require("./utils"), exports);
|
|
20
22
|
//# sourceMappingURL=universal-index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"universal-index.js","sourceRoot":"","sources":["../src/universal-index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iFAAiF;AACjF,yDAAuC;AACvC,yCAAuB"}
|
|
1
|
+
{"version":3,"file":"universal-index.js","sourceRoot":"","sources":["../src/universal-index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iFAAiF;AACjF,yDAAuC;AACvC,yCAAuB;AACvB,gDAA8B;AAC9B,0CAAwB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK,OAAc,MAAM,qBAAsD,CAAC;AAE7F,eAAO,MAAM,qBAAqB,cAEjC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRandomBytes32 = exports.sleep = void 0;
|
|
4
|
+
const ethers_1 = require("ethers");
|
|
5
|
+
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
exports.sleep = sleep;
|
|
7
|
+
const generateRandomBytes32 = () => {
|
|
8
|
+
return ethers_1.ethers.utils.hexlify(ethers_1.ethers.utils.randomBytes(32));
|
|
9
|
+
};
|
|
10
|
+
exports.generateRandomBytes32 = generateRandomBytes32;
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;AAAA,mCAAgC;AAEzB,MAAM,KAAK,GAAG,KAAK,EAAE,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAAhF,QAAA,KAAK,SAA2E;AAEtF,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACxC,OAAO,eAAM,CAAC,KAAK,CAAC,OAAO,CAAC,eAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC;AAFW,QAAA,qBAAqB,yBAEhC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api3/commons",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -26,25 +26,25 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@api3/ois": "^2.3.2",
|
|
28
28
|
"@api3/promise-utils": "^0.4.0",
|
|
29
|
-
"axios": "^1.7.
|
|
29
|
+
"axios": "^1.7.3",
|
|
30
30
|
"dotenv": "^16.4.5",
|
|
31
31
|
"ethers": "^5.7.2",
|
|
32
32
|
"lodash": "^4.17.21",
|
|
33
|
-
"winston": "^3.13.
|
|
33
|
+
"winston": "^3.13.1",
|
|
34
34
|
"winston-console-format": "^1.0.8",
|
|
35
35
|
"zod": "^3.23.8"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@api3/eslint-plugin-commons": "^
|
|
38
|
+
"@api3/eslint-plugin-commons": "^2.0.1",
|
|
39
39
|
"@types/jest": "^29.5.12",
|
|
40
|
-
"@types/lodash": "^4.17.
|
|
41
|
-
"@types/node": "^20.14.
|
|
40
|
+
"@types/lodash": "^4.17.7",
|
|
41
|
+
"@types/node": "^20.14.14",
|
|
42
42
|
"eslint": "^8.57.0",
|
|
43
|
-
"husky": "^9.
|
|
43
|
+
"husky": "^9.1.4",
|
|
44
44
|
"jest": "^29.7.0",
|
|
45
|
-
"prettier": "^3.3.
|
|
46
|
-
"ts-jest": "^29.
|
|
47
|
-
"typescript": "^5.5.
|
|
45
|
+
"prettier": "^3.3.3",
|
|
46
|
+
"ts-jest": "^29.2.4",
|
|
47
|
+
"typescript": "^5.5.4"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsc --project tsconfig.build.json",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createLogger } from '../logger';
|
|
2
|
+
|
|
3
|
+
import { runInLoop } from './index';
|
|
4
|
+
|
|
5
|
+
describe(runInLoop.name, () => {
|
|
6
|
+
const logger = createLogger({
|
|
7
|
+
colorize: true,
|
|
8
|
+
enabled: true,
|
|
9
|
+
minLevel: 'info',
|
|
10
|
+
format: 'json',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('stops the loop after getting the stop signal', async () => {
|
|
14
|
+
const fn = async () => ({ shouldContinueRunning: false });
|
|
15
|
+
const fnSpy = jest
|
|
16
|
+
.spyOn({ fn }, 'fn')
|
|
17
|
+
.mockImplementationOnce(async () => ({ shouldContinueRunning: true }))
|
|
18
|
+
.mockImplementationOnce(async () => ({ shouldContinueRunning: true }))
|
|
19
|
+
.mockImplementationOnce(async () => ({ shouldContinueRunning: false }));
|
|
20
|
+
await runInLoop(fnSpy as any, { logger });
|
|
21
|
+
expect(fnSpy).toHaveBeenCalledTimes(3);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { go } from '@api3/promise-utils';
|
|
2
|
+
|
|
3
|
+
import { type Logger } from '../logger';
|
|
4
|
+
import { generateRandomBytes32, sleep } from '../utils';
|
|
5
|
+
|
|
6
|
+
export interface RunInLoopOptions {
|
|
7
|
+
/** An API3 logger instance required to execute the callback with context. */
|
|
8
|
+
logger: Logger;
|
|
9
|
+
/** Part of every message logged by the logger. */
|
|
10
|
+
logLabel?: Lowercase<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Minimum time to wait between executions. E.g. value 500 means that the next execution will be started only after
|
|
13
|
+
* 500ms from the start of the previous one (even if the previous one ended after 150ms). Default is 0.
|
|
14
|
+
*/
|
|
15
|
+
frequencyMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Minimum time to wait between executions. E.g. value 50 means that next execution will start 50ms after the previous
|
|
18
|
+
* one ended. Default is 0.
|
|
19
|
+
*/
|
|
20
|
+
minWaitTimeMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum time to wait between executions. E.g. value 100 means that the next execution will be started at most after
|
|
23
|
+
* 100ms from the end of the previous one. The maxWaitTime has higher precedence than minWaitTime and frequencyMs.
|
|
24
|
+
*/
|
|
25
|
+
maxWaitTimeMs?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum time to wait for the execution to finish. If the execution exceeds this time a warning is logged.
|
|
28
|
+
*/
|
|
29
|
+
softTimeoutMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum time to wait for the callback execution to finish. If the execution exceeds this time an error is logged and the
|
|
32
|
+
* execution is force-stopped.
|
|
33
|
+
*/
|
|
34
|
+
hardTimeoutMs?: number;
|
|
35
|
+
/**
|
|
36
|
+
* If false, the execution will not run. This is useful for temporarily disabling the execution (e.g. change
|
|
37
|
+
* environment variable and redeploy). Note that, there is no way to stop the loop execution once started. Default is
|
|
38
|
+
* true.
|
|
39
|
+
*/
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The initial delay to to wait before executing the callback for the first time. Default is 0, which means the
|
|
43
|
+
* callback is executed immediately.
|
|
44
|
+
*/
|
|
45
|
+
initialDelayMs?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const runInLoop = async (
|
|
49
|
+
fn: () => Promise<{ shouldContinueRunning: boolean } | void>,
|
|
50
|
+
options: RunInLoopOptions
|
|
51
|
+
) => {
|
|
52
|
+
const {
|
|
53
|
+
logger,
|
|
54
|
+
logLabel,
|
|
55
|
+
frequencyMs = 0,
|
|
56
|
+
minWaitTimeMs = 0,
|
|
57
|
+
maxWaitTimeMs,
|
|
58
|
+
softTimeoutMs,
|
|
59
|
+
hardTimeoutMs,
|
|
60
|
+
enabled = true,
|
|
61
|
+
initialDelayMs,
|
|
62
|
+
} = options;
|
|
63
|
+
|
|
64
|
+
if (softTimeoutMs && hardTimeoutMs && hardTimeoutMs < softTimeoutMs) {
|
|
65
|
+
throw new Error('hardTimeoutMs must not be smaller than softTimeoutMs');
|
|
66
|
+
}
|
|
67
|
+
if (minWaitTimeMs && maxWaitTimeMs && maxWaitTimeMs < minWaitTimeMs) {
|
|
68
|
+
throw new Error('maxWaitTimeMs must not be smaller than minWaitTimeMs');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (initialDelayMs) await sleep(initialDelayMs);
|
|
72
|
+
|
|
73
|
+
while (true) {
|
|
74
|
+
const executionStart = performance.now();
|
|
75
|
+
const executionId = generateRandomBytes32();
|
|
76
|
+
|
|
77
|
+
if (enabled) {
|
|
78
|
+
const context = logLabel ? { executionId, label: logLabel } : { executionId };
|
|
79
|
+
const shouldContinueRunning = await logger.runWithContext(context, async () => {
|
|
80
|
+
const goRes = await go(fn, hardTimeoutMs ? { totalTimeoutMs: hardTimeoutMs } : {}); // NOTE: This is a safety net to prevent the loop from hanging
|
|
81
|
+
if (!goRes.success) {
|
|
82
|
+
logger.error(`Unexpected runInLoop error`, goRes.error);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const executionTimeMs = performance.now() - executionStart;
|
|
86
|
+
if (executionTimeMs >= softTimeoutMs!) {
|
|
87
|
+
logger.warn(`Execution took longer than the interval`, { executionTimeMs });
|
|
88
|
+
} else {
|
|
89
|
+
logger.info(`Execution finished`, { executionTimeMs });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return goRes.data?.shouldContinueRunning === false ? false : true;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Stop loop execution if the callback return value indicates it should stop
|
|
96
|
+
if (!shouldContinueRunning) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// If the bot is disabled, we still want to run the loop to prevent the process from hanging. We also want to
|
|
101
|
+
// sleep according to the wait time logic.
|
|
102
|
+
logger.info('Loop execution is disabled.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const remainingWaitTime = Math.max(frequencyMs - (performance.now() - executionStart), 0);
|
|
106
|
+
const waitTime = Math.max(minWaitTimeMs, remainingWaitTime);
|
|
107
|
+
const actualWaitTime = maxWaitTimeMs ? Math.min(waitTime, maxWaitTimeMs) : waitTime;
|
|
108
|
+
if (actualWaitTime > 0) await sleep(actualWaitTime);
|
|
109
|
+
}
|
|
110
|
+
};
|
package/src/universal-index.ts
CHANGED