tiny-essentials 1.8.5 โ†’ 1.9.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.
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Class representing a flexible rate limiter per user.
5
+ *
6
+ * This rate limiter can be configured by maximum number of hits,
7
+ * time interval, or a combination of both. It supports automatic
8
+ * cleanup of inactive users to optimize memory usage.
9
+ *
10
+ * @class
11
+ */
12
+ class TinyRateLimiter {
13
+ /**
14
+ * @param {Object} options
15
+ * @param {number} [options.maxHits] - Max interactions allowed
16
+ * @param {number} [options.interval] - Time window in milliseconds
17
+ * @param {number} [options.cleanupInterval] - Interval for automatic cleanup (ms)
18
+ * @param {number} [options.maxIdle=300000] - Max idle time for a user before being cleaned (ms)
19
+ */
20
+ constructor({ maxHits, interval, cleanupInterval, maxIdle = 300000 }) {
21
+ /** @param {number|undefined} val */
22
+ const isPositiveInteger = (val) =>
23
+ typeof val === 'number' && Number.isFinite(val) && val >= 1 && Number.isInteger(val);
24
+
25
+ const isMaxHitsValid = isPositiveInteger(maxHits);
26
+ const isIntervalValid = isPositiveInteger(interval);
27
+ const isCleanupValid = isPositiveInteger(cleanupInterval);
28
+ const isMaxIdleValid = isPositiveInteger(maxIdle);
29
+
30
+ if (!isMaxHitsValid && !isIntervalValid)
31
+ throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");
32
+ if (maxHits !== undefined && !isMaxHitsValid)
33
+ throw new Error("'maxHits' must be a positive integer if defined.");
34
+ if (interval !== undefined && !isIntervalValid)
35
+ throw new Error("'interval' must be a positive integer in milliseconds if defined.");
36
+ if (cleanupInterval !== undefined && !isCleanupValid)
37
+ throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");
38
+ if (!isMaxIdleValid) throw new Error("'maxIdle' must be a positive integer in milliseconds.");
39
+
40
+ this.maxHits = isMaxHitsValid ? maxHits : null;
41
+ this.interval = isIntervalValid ? interval : null;
42
+ this.cleanupInterval = isCleanupValid ? cleanupInterval : null;
43
+ this.maxIdle = maxIdle;
44
+
45
+ /** @type {Map<string, number[]>} */
46
+ this.userData = new Map();
47
+
48
+ /** @type {Map<string, number>} */
49
+ this.lastSeen = new Map();
50
+
51
+ // Start automatic cleanup only if cleanupInterval is valid
52
+ if (this.cleanupInterval !== null)
53
+ this._cleanupTimer = setInterval(() => this._cleanup(), this.cleanupInterval);
54
+ }
55
+
56
+ /**
57
+ * Get the interval window in milliseconds.
58
+ *
59
+ * @returns {number} The interval value.
60
+ * @throws {Error} If interval is not a valid finite number.
61
+ */
62
+ getInterval() {
63
+ if (typeof this.interval !== 'number' || !Number.isFinite(this.interval))
64
+ throw new Error("'interval' is not a valid finite number.");
65
+ return this.interval;
66
+ }
67
+
68
+ /**
69
+ * Get the maximum number of allowed hits.
70
+ *
71
+ * @returns {number} The maxHits value.
72
+ * @throws {Error} If maxHits is not a valid finite number.
73
+ */
74
+ getMaxHits() {
75
+ if (typeof this.maxHits !== 'number' || !Number.isFinite(this.maxHits)) {
76
+ throw new Error("'maxHits' is not a valid finite number.");
77
+ }
78
+ return this.maxHits;
79
+ }
80
+
81
+ /**
82
+ * Register a hit for a specific user
83
+ * @param {string} userId
84
+ */
85
+ hit(userId) {
86
+ const now = Date.now();
87
+
88
+ if (!this.userData.has(userId)) {
89
+ this.userData.set(userId, []);
90
+ }
91
+
92
+ const history = this.userData.get(userId);
93
+ if (!history) throw new Error(`No data found for userId: ${userId}`);
94
+
95
+ history.push(now);
96
+ this.lastSeen.set(userId, now);
97
+
98
+ // Clean up old entries
99
+ if (this.interval !== null) {
100
+ const interval = this.getInterval();
101
+ const cutoff = now - interval;
102
+ while (history.length && history[0] < cutoff) {
103
+ history.shift();
104
+ }
105
+ }
106
+
107
+ // Optional: keep only the last N entries for memory optimization
108
+ if (this.maxHits !== null) {
109
+ const maxHits = this.getMaxHits();
110
+ if (history.length > maxHits) {
111
+ history.splice(0, history.length - maxHits);
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Check if the user is currently rate limited
118
+ * @param {string} userId
119
+ * @returns {boolean}
120
+ */
121
+ isRateLimited(userId) {
122
+ const now = Date.now();
123
+
124
+ if (!this.userData.has(userId)) return false;
125
+
126
+ const history = this.userData.get(userId);
127
+ if (!history) throw new Error(`No data found for userId: ${userId}`);
128
+
129
+ if (this.interval !== null) {
130
+ const interval = this.getInterval();
131
+ const recent = history.filter((t) => t > now - interval);
132
+ if (this.maxHits !== null) {
133
+ return recent.length >= this.getMaxHits();
134
+ }
135
+ return recent.length > 0;
136
+ }
137
+
138
+ if (this.maxHits !== null) {
139
+ return history.length >= this.getMaxHits();
140
+ }
141
+
142
+ return false;
143
+ }
144
+
145
+ /**
146
+ * Manually reset user data
147
+ * @param {string} userId
148
+ */
149
+ reset(userId) {
150
+ this.userData.delete(userId);
151
+ this.lastSeen.delete(userId);
152
+ }
153
+
154
+ /**
155
+ * Set hit timestamps for a user
156
+ * @param {string} userId
157
+ * @param {number[]} timestamps
158
+ */
159
+ setData(userId, timestamps) {
160
+ this.userData.set(userId, timestamps);
161
+ this.lastSeen.set(userId, Date.now());
162
+ }
163
+
164
+ /**
165
+ * Get timestamps from user
166
+ * @param {string} userId
167
+ * @returns {number[]}
168
+ */
169
+ getData(userId) {
170
+ return this.userData.get(userId) || [];
171
+ }
172
+
173
+ /**
174
+ * Cleanup old/inactive users
175
+ * @private
176
+ */
177
+ _cleanup() {
178
+ const now = Date.now();
179
+ for (const [userId, last] of this.lastSeen.entries()) {
180
+ if (now - last > this.maxIdle) {
181
+ this.userData.delete(userId);
182
+ this.lastSeen.delete(userId);
183
+ }
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Destroy the rate limiter, stopping all intervals
189
+ */
190
+ destroy() {
191
+ if (this._cleanupTimer) clearInterval(this._cleanupTimer);
192
+ this.userData.clear();
193
+ this.lastSeen.clear();
194
+ }
195
+ }
196
+
197
+ module.exports = TinyRateLimiter;
@@ -0,0 +1,86 @@
1
+ export default TinyRateLimiter;
2
+ /**
3
+ * Class representing a flexible rate limiter per user.
4
+ *
5
+ * This rate limiter can be configured by maximum number of hits,
6
+ * time interval, or a combination of both. It supports automatic
7
+ * cleanup of inactive users to optimize memory usage.
8
+ *
9
+ * @class
10
+ */
11
+ declare class TinyRateLimiter {
12
+ /**
13
+ * @param {Object} options
14
+ * @param {number} [options.maxHits] - Max interactions allowed
15
+ * @param {number} [options.interval] - Time window in milliseconds
16
+ * @param {number} [options.cleanupInterval] - Interval for automatic cleanup (ms)
17
+ * @param {number} [options.maxIdle=300000] - Max idle time for a user before being cleaned (ms)
18
+ */
19
+ constructor({ maxHits, interval, cleanupInterval, maxIdle }: {
20
+ maxHits?: number | undefined;
21
+ interval?: number | undefined;
22
+ cleanupInterval?: number | undefined;
23
+ maxIdle?: number | undefined;
24
+ });
25
+ maxHits: number | null | undefined;
26
+ interval: number | null | undefined;
27
+ cleanupInterval: number | null | undefined;
28
+ maxIdle: number;
29
+ /** @type {Map<string, number[]>} */
30
+ userData: Map<string, number[]>;
31
+ /** @type {Map<string, number>} */
32
+ lastSeen: Map<string, number>;
33
+ _cleanupTimer: NodeJS.Timeout | undefined;
34
+ /**
35
+ * Get the interval window in milliseconds.
36
+ *
37
+ * @returns {number} The interval value.
38
+ * @throws {Error} If interval is not a valid finite number.
39
+ */
40
+ getInterval(): number;
41
+ /**
42
+ * Get the maximum number of allowed hits.
43
+ *
44
+ * @returns {number} The maxHits value.
45
+ * @throws {Error} If maxHits is not a valid finite number.
46
+ */
47
+ getMaxHits(): number;
48
+ /**
49
+ * Register a hit for a specific user
50
+ * @param {string} userId
51
+ */
52
+ hit(userId: string): void;
53
+ /**
54
+ * Check if the user is currently rate limited
55
+ * @param {string} userId
56
+ * @returns {boolean}
57
+ */
58
+ isRateLimited(userId: string): boolean;
59
+ /**
60
+ * Manually reset user data
61
+ * @param {string} userId
62
+ */
63
+ reset(userId: string): void;
64
+ /**
65
+ * Set hit timestamps for a user
66
+ * @param {string} userId
67
+ * @param {number[]} timestamps
68
+ */
69
+ setData(userId: string, timestamps: number[]): void;
70
+ /**
71
+ * Get timestamps from user
72
+ * @param {string} userId
73
+ * @returns {number[]}
74
+ */
75
+ getData(userId: string): number[];
76
+ /**
77
+ * Cleanup old/inactive users
78
+ * @private
79
+ */
80
+ private _cleanup;
81
+ /**
82
+ * Destroy the rate limiter, stopping all intervals
83
+ */
84
+ destroy(): void;
85
+ }
86
+ //# sourceMappingURL=TinyRateLimiter.d.mts.map
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Class representing a flexible rate limiter per user.
3
+ *
4
+ * This rate limiter can be configured by maximum number of hits,
5
+ * time interval, or a combination of both. It supports automatic
6
+ * cleanup of inactive users to optimize memory usage.
7
+ *
8
+ * @class
9
+ */
10
+ class TinyRateLimiter {
11
+ /**
12
+ * @param {Object} options
13
+ * @param {number} [options.maxHits] - Max interactions allowed
14
+ * @param {number} [options.interval] - Time window in milliseconds
15
+ * @param {number} [options.cleanupInterval] - Interval for automatic cleanup (ms)
16
+ * @param {number} [options.maxIdle=300000] - Max idle time for a user before being cleaned (ms)
17
+ */
18
+ constructor({ maxHits, interval, cleanupInterval, maxIdle = 300000 }) {
19
+ /** @param {number|undefined} val */
20
+ const isPositiveInteger = (val) => typeof val === 'number' && Number.isFinite(val) && val >= 1 && Number.isInteger(val);
21
+ const isMaxHitsValid = isPositiveInteger(maxHits);
22
+ const isIntervalValid = isPositiveInteger(interval);
23
+ const isCleanupValid = isPositiveInteger(cleanupInterval);
24
+ const isMaxIdleValid = isPositiveInteger(maxIdle);
25
+ if (!isMaxHitsValid && !isIntervalValid)
26
+ throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");
27
+ if (maxHits !== undefined && !isMaxHitsValid)
28
+ throw new Error("'maxHits' must be a positive integer if defined.");
29
+ if (interval !== undefined && !isIntervalValid)
30
+ throw new Error("'interval' must be a positive integer in milliseconds if defined.");
31
+ if (cleanupInterval !== undefined && !isCleanupValid)
32
+ throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");
33
+ if (!isMaxIdleValid)
34
+ throw new Error("'maxIdle' must be a positive integer in milliseconds.");
35
+ this.maxHits = isMaxHitsValid ? maxHits : null;
36
+ this.interval = isIntervalValid ? interval : null;
37
+ this.cleanupInterval = isCleanupValid ? cleanupInterval : null;
38
+ this.maxIdle = maxIdle;
39
+ /** @type {Map<string, number[]>} */
40
+ this.userData = new Map();
41
+ /** @type {Map<string, number>} */
42
+ this.lastSeen = new Map();
43
+ // Start automatic cleanup only if cleanupInterval is valid
44
+ if (this.cleanupInterval !== null)
45
+ this._cleanupTimer = setInterval(() => this._cleanup(), this.cleanupInterval);
46
+ }
47
+ /**
48
+ * Get the interval window in milliseconds.
49
+ *
50
+ * @returns {number} The interval value.
51
+ * @throws {Error} If interval is not a valid finite number.
52
+ */
53
+ getInterval() {
54
+ if (typeof this.interval !== 'number' || !Number.isFinite(this.interval))
55
+ throw new Error("'interval' is not a valid finite number.");
56
+ return this.interval;
57
+ }
58
+ /**
59
+ * Get the maximum number of allowed hits.
60
+ *
61
+ * @returns {number} The maxHits value.
62
+ * @throws {Error} If maxHits is not a valid finite number.
63
+ */
64
+ getMaxHits() {
65
+ if (typeof this.maxHits !== 'number' || !Number.isFinite(this.maxHits)) {
66
+ throw new Error("'maxHits' is not a valid finite number.");
67
+ }
68
+ return this.maxHits;
69
+ }
70
+ /**
71
+ * Register a hit for a specific user
72
+ * @param {string} userId
73
+ */
74
+ hit(userId) {
75
+ const now = Date.now();
76
+ if (!this.userData.has(userId)) {
77
+ this.userData.set(userId, []);
78
+ }
79
+ const history = this.userData.get(userId);
80
+ if (!history)
81
+ throw new Error(`No data found for userId: ${userId}`);
82
+ history.push(now);
83
+ this.lastSeen.set(userId, now);
84
+ // Clean up old entries
85
+ if (this.interval !== null) {
86
+ const interval = this.getInterval();
87
+ const cutoff = now - interval;
88
+ while (history.length && history[0] < cutoff) {
89
+ history.shift();
90
+ }
91
+ }
92
+ // Optional: keep only the last N entries for memory optimization
93
+ if (this.maxHits !== null) {
94
+ const maxHits = this.getMaxHits();
95
+ if (history.length > maxHits) {
96
+ history.splice(0, history.length - maxHits);
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Check if the user is currently rate limited
102
+ * @param {string} userId
103
+ * @returns {boolean}
104
+ */
105
+ isRateLimited(userId) {
106
+ const now = Date.now();
107
+ if (!this.userData.has(userId))
108
+ return false;
109
+ const history = this.userData.get(userId);
110
+ if (!history)
111
+ throw new Error(`No data found for userId: ${userId}`);
112
+ if (this.interval !== null) {
113
+ const interval = this.getInterval();
114
+ const recent = history.filter((t) => t > now - interval);
115
+ if (this.maxHits !== null) {
116
+ return recent.length >= this.getMaxHits();
117
+ }
118
+ return recent.length > 0;
119
+ }
120
+ if (this.maxHits !== null) {
121
+ return history.length >= this.getMaxHits();
122
+ }
123
+ return false;
124
+ }
125
+ /**
126
+ * Manually reset user data
127
+ * @param {string} userId
128
+ */
129
+ reset(userId) {
130
+ this.userData.delete(userId);
131
+ this.lastSeen.delete(userId);
132
+ }
133
+ /**
134
+ * Set hit timestamps for a user
135
+ * @param {string} userId
136
+ * @param {number[]} timestamps
137
+ */
138
+ setData(userId, timestamps) {
139
+ this.userData.set(userId, timestamps);
140
+ this.lastSeen.set(userId, Date.now());
141
+ }
142
+ /**
143
+ * Get timestamps from user
144
+ * @param {string} userId
145
+ * @returns {number[]}
146
+ */
147
+ getData(userId) {
148
+ return this.userData.get(userId) || [];
149
+ }
150
+ /**
151
+ * Cleanup old/inactive users
152
+ * @private
153
+ */
154
+ _cleanup() {
155
+ const now = Date.now();
156
+ for (const [userId, last] of this.lastSeen.entries()) {
157
+ if (now - last > this.maxIdle) {
158
+ this.userData.delete(userId);
159
+ this.lastSeen.delete(userId);
160
+ }
161
+ }
162
+ }
163
+ /**
164
+ * Destroy the rate limiter, stopping all intervals
165
+ */
166
+ destroy() {
167
+ if (this._cleanupTimer)
168
+ clearInterval(this._cleanupTimer);
169
+ this.userData.clear();
170
+ this.lastSeen.clear();
171
+ }
172
+ }
173
+ export default TinyRateLimiter;
package/docs/README.md CHANGED
@@ -23,6 +23,7 @@ This folder contains the core scripts we have worked on so far. Each file is a m
23
23
  - ๐Ÿ—‚๏ธ **[TinyPromiseQueue](./libs/TinyPromiseQueue.md)** โ€” A class that allows sequential execution of asynchronous tasks, supporting task delays, cancellation, and queue management.
24
24
  - ๐Ÿ… **[TinyLevelUp](./libs/TinyLevelUp.md)** โ€” A class to manage user level-up logic based on experience points, providing methods for experience validation, addition, removal, and calculation.
25
25
  - ๐ŸŽจ **[ColorSafeStringify](./libs/ColorSafeStringify.md)** โ€” A utility for applying customizable ANSI colors to JSON strings in terminal outputs, supporting presets and fine-grained type-based highlighting.
26
+ - ๐Ÿšฆ **[TinyRateLimiter](./libs/TinyRateLimiter.md)** โ€” A flexible per-user rate limiter supporting time windows, hit caps, and automatic cleanup of inactive users.
26
27
 
27
28
  ---
28
29
 
@@ -0,0 +1,156 @@
1
+ # ๐Ÿง  `TinyRateLimiter` โ€“ A Simple Per-User Rate Limiter
2
+
3
+ The `TinyRateLimiter` is a flexible and lightweight JavaScript class designed to help you limit actions (like requests or commands) per user. You can define rules like **maximum hits**, **time windows**, and benefit from automatic memory cleanup. โœจ
4
+
5
+ ---
6
+
7
+ ## ๐Ÿ“ฆ Constructor
8
+
9
+ ```js
10
+ new TinyRateLimiter(options)
11
+ ```
12
+
13
+ ### ๐Ÿ”ง Options
14
+
15
+ | Option | Type | Default | Description |
16
+ | ----------------- | -------- | ----------- | ---------------------------------------------- |
17
+ | `maxHits` | `number` | `undefined` | Max number of hits per user before blocking ๐Ÿšง |
18
+ | `interval` | `number` | `undefined` | Time window in milliseconds โฑ๏ธ |
19
+ | `cleanupInterval` | `number` | `undefined` | Interval to auto-clean inactive users ๐Ÿงน |
20
+ | `maxIdle` | `number` | `300000` | Max idle time per user before cleanup ๐Ÿ’ค |
21
+
22
+ > โš ๏ธ At least one of `maxHits` or `interval` must be defined.
23
+
24
+ ---
25
+
26
+ ## ๐Ÿ“‹ Methods
27
+
28
+ ### ๐Ÿš€ `hit(userId: string): void`
29
+
30
+ Registers a hit for the given `userId`.
31
+
32
+ ```js
33
+ rateLimiter.hit("user123");
34
+ ```
35
+
36
+ It tracks timestamps internally and automatically cleans old entries based on `interval` and `maxHits`.
37
+
38
+ ---
39
+
40
+ ### โŒ `isRateLimited(userId: string): boolean`
41
+
42
+ Checks if the given `userId` is currently rate limited.
43
+
44
+ ```js
45
+ if (rateLimiter.isRateLimited("user123")) {
46
+ console.log("Too many actions! Please slow down.");
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ### ๐Ÿ” `reset(userId: string): void`
53
+
54
+ Resets all stored data for the given user.
55
+
56
+ ```js
57
+ rateLimiter.reset("user123");
58
+ ```
59
+
60
+ ---
61
+
62
+ ### ๐Ÿ› ๏ธ `setData(userId: string, timestamps: number[]): void`
63
+
64
+ Sets the hit timestamps for a specific user. Useful for restoring or mocking state.
65
+
66
+ ```js
67
+ rateLimiter.setData("user123", [Date.now() - 1000, Date.now()]);
68
+ ```
69
+
70
+ ---
71
+
72
+ ### ๐Ÿ“ฅ `getData(userId: string): number[]`
73
+
74
+ Returns all hit timestamps for a given user.
75
+
76
+ ```js
77
+ const hits = rateLimiter.getData("user123");
78
+ console.log(hits); // [timestamp1, timestamp2, ...]
79
+ ```
80
+
81
+ ---
82
+
83
+ ### ๐Ÿง  `getMaxHits(): number`
84
+
85
+ Returns the configured `maxHits` value, or throws if invalid.
86
+
87
+ ---
88
+
89
+ ### โณ `getInterval(): number`
90
+
91
+ Returns the configured `interval` value, or throws if invalid.
92
+
93
+ ---
94
+
95
+ ### ๐Ÿงผ `destroy(): void`
96
+
97
+ Stops internal timers and clears all stored data. Ideal for cleanup in tests or long-running apps.
98
+
99
+ ```js
100
+ rateLimiter.destroy();
101
+ ```
102
+
103
+ ---
104
+
105
+ ## ๐Ÿงฝ Automatic Cleanup
106
+
107
+ Inactive users are automatically removed every `cleanupInterval` milliseconds if they haven't had any hits for longer than `maxIdle`.
108
+
109
+ This helps reduce memory usage and keeps your limiter lean and clean! ๐Ÿชถ
110
+
111
+ ---
112
+
113
+ ## ๐Ÿ’ฅ Errors
114
+
115
+ The constructor will throw if:
116
+
117
+ * Neither `maxHits` nor `interval` are provided.
118
+ * Any of the values are not positive integers.
119
+
120
+ ---
121
+
122
+ ## ๐Ÿงช Example Usage
123
+
124
+ ```js
125
+ const limiter = new TinyRateLimiter({
126
+ maxHits: 5,
127
+ interval: 10000,
128
+ cleanupInterval: 60000,
129
+ maxIdle: 120000
130
+ });
131
+
132
+ limiter.hit("user42");
133
+
134
+ if (limiter.isRateLimited("user42")) {
135
+ console.log("Rate limited! ๐Ÿ›‘");
136
+ } else {
137
+ console.log("Action allowed โœ…");
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## ๐ŸŒˆ Summary
144
+
145
+ | Feature | Support |
146
+ | -------------------- | ------- |
147
+ | Per-user tracking | โœ… |
148
+ | Max hits | โœ… |
149
+ | Time window interval | โœ… |
150
+ | Automatic cleanup | โœ… |
151
+ | Manual data control | โœ… |
152
+ | Lightweight + fast | โœ… |
153
+
154
+ ---
155
+
156
+ Made with ๐Ÿ’œ to protect your app from spammers and abusers โ€” in a cute and efficient way! ๐Ÿ˜„
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-essentials",
3
- "version": "1.8.5",
3
+ "version": "1.9.0",
4
4
  "description": "Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.",
5
5
  "scripts": {
6
6
  "test": "npm run test:mjs && npm run test:cjs && npm run test:js",
@@ -10,6 +10,7 @@
10
10
  "test:mjs:promisequeue": "node test/index.mjs promiseQueue",
11
11
  "test:mjs:objtype": "node test/index.mjs objType",
12
12
  "test:mjs:jsoncolor": "node test/index.mjs colorStringify",
13
+ "test:mjs:ratelimit": "node test/index.mjs rateLimit",
13
14
  "fix:prettier": "npm run fix:prettier:src && npm run fix:prettier:test && npm run fix:prettier:rollup.config && npm run fix:prettier:webpack.config",
14
15
  "fix:prettier:src": "prettier --write ./src/*",
15
16
  "fix:prettier:test": "prettier --write ./test/*",