@ethanblaisalarms/utils 1.0.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,20 @@
1
+ export type TRecord<T = unknown> = Record<string, T>;
2
+ export type TNumRecord<T = unknown> = Record<number, T>;
3
+ export type TCondition = (iteration?: number) => boolean;
4
+ export declare function merge<T extends object>(target: T, ...sources: object[]): T;
5
+ export declare function decommentJSON(data: string): string;
6
+ export declare function checkPlain(data: unknown): data is TRecord;
7
+ export declare function pull<T>(target: T[], ...elements: T[]): T[];
8
+ export declare function pullAll<T>(target: T[], ...elements: T[]): T[];
9
+ export declare function replace<T>(target: T[], element: T, ...replace: T[]): T[];
10
+ export declare function replaceAll<T>(target: T[], element: T, ...replace: T[]): T[];
11
+ export declare function objectify<T>(source: T[]): TNumRecord<T>;
12
+ export declare function stringify(data: unknown): string;
13
+ export declare function stringifyArray(data: unknown[]): string[];
14
+ export declare function stringifyJSON(data: object, separator?: string, ignore?: string[]): string;
15
+ export declare function rand(base?: number, maxInput?: number): number;
16
+ export declare function rand<T>(base: T[]): T | null;
17
+ export declare function rand(base: TRecord): string | null;
18
+ export declare function randString(len?: number, charset?: string | string[]): string;
19
+ export declare function sleep(ms: number): Promise<void>;
20
+ export declare function until(condition: TCondition, interval?: number, attempts?: number | null): Promise<number>;
package/dist/index.js ADDED
@@ -0,0 +1,231 @@
1
+ //=======================================\\
2
+ // EBA UTILITIES 1.0.0 \\
3
+ // index.ts \\
4
+ // Copyright (c) EthanBlaisAlarms 2026 \\
5
+ //=======================================\\
6
+ //====================
7
+ // OBJECT UTILITIES
8
+ //====================
9
+ export function merge(target, ...sources) {
10
+ // Ignored node names.
11
+ // Prevents accidentally overwriting prototypes and constructors.
12
+ const blocklist = new Set(["__proto__", "prototype", "constructor"]);
13
+ const seen = new WeakSet();
14
+ const innerMerge = function (target, source) {
15
+ if (seen.has(source))
16
+ return;
17
+ seen.add(source);
18
+ for (const i of Object.keys(source)) {
19
+ // Skip ignored node names.
20
+ // Skip functions.
21
+ if (blocklist.has(i))
22
+ continue;
23
+ if (typeof source[i] === "function")
24
+ continue;
25
+ // Merge Arrays
26
+ if (Array.isArray(source[i]) && Array.isArray(target[i])) {
27
+ target[i] = target[i].concat(source[i]);
28
+ continue;
29
+ }
30
+ // Merge Non-Recursive
31
+ if (!checkPlain(target[i]) || !checkPlain(source[i])) {
32
+ target[i] = source[i];
33
+ continue;
34
+ }
35
+ // Merge Recursive
36
+ innerMerge(target[i], source[i]);
37
+ }
38
+ seen.delete(source);
39
+ };
40
+ for (const source of sources)
41
+ innerMerge(target, source);
42
+ return target;
43
+ }
44
+ export function decommentJSON(data) {
45
+ const linebreaks = ["\n", "\r"];
46
+ let inside = false;
47
+ let escape = false;
48
+ let single = false;
49
+ let multi = false;
50
+ let result = "";
51
+ for (let i = 0; i < data.length; i++) {
52
+ const char = data[i];
53
+ const nextChar = data[i + 1] ?? "";
54
+ const nextChars = char + nextChar;
55
+ if (inside) {
56
+ if (escape)
57
+ escape = false;
58
+ else if (char == "\\")
59
+ escape = true;
60
+ else if (char == "\"")
61
+ inside = false;
62
+ }
63
+ else if (single) {
64
+ if (linebreaks.includes(char))
65
+ single = false;
66
+ else
67
+ continue;
68
+ }
69
+ else if (multi) {
70
+ if (nextChars == "*/") {
71
+ multi = false;
72
+ i++;
73
+ }
74
+ continue;
75
+ }
76
+ else if (nextChars == "//") {
77
+ single = true;
78
+ i++;
79
+ continue;
80
+ }
81
+ else if (nextChars == "/*") {
82
+ multi = true;
83
+ i++;
84
+ continue;
85
+ }
86
+ else if (char == "\"")
87
+ inside = true;
88
+ result += char;
89
+ }
90
+ return result;
91
+ }
92
+ export function checkPlain(data) {
93
+ return Object.prototype.toString.call(data) === "[object Object]";
94
+ }
95
+ //===================
96
+ // ARRAY UTILITIES
97
+ //===================
98
+ export function pull(target, ...elements) {
99
+ for (const element of elements) {
100
+ const i = target.indexOf(element);
101
+ if (i >= 0)
102
+ target.splice(i, 1);
103
+ }
104
+ return target;
105
+ }
106
+ export function pullAll(target, ...elements) {
107
+ for (const element of elements) {
108
+ let i;
109
+ while ((i = target.indexOf(element)) !== -1)
110
+ target.splice(i, 1);
111
+ }
112
+ return target;
113
+ }
114
+ export function replace(target, element, ...replace) {
115
+ const i = target.indexOf(element);
116
+ if (i >= 0)
117
+ target.splice(i, 1, ...replace);
118
+ return target;
119
+ }
120
+ export function replaceAll(target, element, ...replace) {
121
+ let i;
122
+ while ((i = target.indexOf(element)) !== -1)
123
+ target.splice(i, 1, ...replace);
124
+ return target;
125
+ }
126
+ export function objectify(source) {
127
+ const output = {};
128
+ for (const i of source.keys())
129
+ output[i] = source[i];
130
+ return output;
131
+ }
132
+ //=============
133
+ // STRINGIFY
134
+ //=============
135
+ export function stringify(data) {
136
+ if (typeof data === "bigint")
137
+ return `${data}n`;
138
+ if (typeof data !== "object")
139
+ return String(data);
140
+ if (data === null)
141
+ return "null";
142
+ if (data instanceof Set)
143
+ return stringify(Array.from(data));
144
+ if (data instanceof Map)
145
+ return stringify(Array.from(data.entries()));
146
+ if (Array.isArray(data))
147
+ return stringifyJSON(data);
148
+ if (data.toString !== Object.prototype.toString
149
+ && typeof data.toString === "function")
150
+ return data.toString();
151
+ return stringifyJSON({ ...data });
152
+ }
153
+ export function stringifyArray(data) {
154
+ return data.map(stringify);
155
+ }
156
+ export function stringifyJSON(data, separator = " ", ignore = []) {
157
+ const seen = new WeakSet();
158
+ const paths = new WeakMap();
159
+ const ignoreSet = new Set(ignore);
160
+ const replacer = function (key, value) {
161
+ const parent = paths.get(this) ?? "";
162
+ const current = key
163
+ ? (parent ? parent + "." : "") + key
164
+ : "";
165
+ if (ignoreSet.has(current))
166
+ return undefined;
167
+ if (typeof value == "bigint")
168
+ return `${value}n`;
169
+ if (typeof value == "object" && value) {
170
+ if (seen.has(value))
171
+ return `[Circular: ${paths.get(value) || "~"}]`;
172
+ seen.add(value);
173
+ if (current != "")
174
+ paths.set(value, current);
175
+ }
176
+ if (value instanceof Set)
177
+ return Array.from(value);
178
+ if (value instanceof Map)
179
+ return Array.from(value.entries());
180
+ return value;
181
+ };
182
+ paths.set(data, "");
183
+ return JSON.stringify(data, replacer, separator);
184
+ }
185
+ export function rand(base, max) {
186
+ if (typeof base == "number") {
187
+ const hasMax = typeof max == "number";
188
+ const setBase = hasMax ? Math.min(base, max) : 0;
189
+ const setMax = hasMax ? Math.max(base, max) : base;
190
+ return Math.floor(Math.random() * (setMax - setBase + 1)) + setBase;
191
+ }
192
+ if (!base)
193
+ return Math.random();
194
+ if (Array.isArray(base)) {
195
+ if (base.length < 1)
196
+ return null;
197
+ return base[rand(base.length - 1)];
198
+ }
199
+ const keys = Object.keys(base);
200
+ if (keys.length < 1)
201
+ return null;
202
+ return rand(keys);
203
+ }
204
+ export function randString(len = 16, charset = "0123456789ABCDEF") {
205
+ if (typeof charset === "string")
206
+ charset = charset.split("");
207
+ if (charset.length < 1)
208
+ return "";
209
+ let output = "";
210
+ while (len > 0) {
211
+ output += rand(charset);
212
+ len--;
213
+ }
214
+ return output;
215
+ }
216
+ //===================
217
+ // ASYNC UTILITIES
218
+ //===================
219
+ export async function sleep(ms) {
220
+ return new Promise((resolve) => {
221
+ setTimeout(resolve, ms);
222
+ });
223
+ }
224
+ export async function until(condition, interval = 100, attempts = null) {
225
+ for (let i = 0; (i < (attempts ?? i + 1)); i++) {
226
+ if (condition(i + 1))
227
+ return i + 1;
228
+ await sleep(interval);
229
+ }
230
+ throw new RangeError("Maximum number of attempts reached.");
231
+ }
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,312 @@
1
+ //=======================================\\
2
+ // EBA UTILITIES 1.0.0 \\
3
+ // test.ts \\
4
+ // Copyright (c) EthanBlaisAlarms 2026 \\
5
+ //=======================================\\
6
+ import * as Assert from "node:assert";
7
+ import { checkPlain, decommentJSON, merge, objectify, pull, pullAll, replace, replaceAll, stringify, stringifyArray, stringifyJSON, } from "./index.js";
8
+ console.log("Starting tests...");
9
+ //====================
10
+ // OBJECT UTILITIES
11
+ //====================
12
+ console.log("\n\nCATEGORY: Object Utilities");
13
+ //-----------------
14
+ // Merge objects
15
+ //-----------------
16
+ console.log("\nFUNCTION: merge()");
17
+ console.log("TEST 1: General functionality and object mutation");
18
+ const mrg11 = {
19
+ A: 1,
20
+ B: 2,
21
+ C: 3,
22
+ };
23
+ const mrg12 = {
24
+ A: 10,
25
+ D: 40,
26
+ E: 50,
27
+ };
28
+ const mrg13 = {
29
+ A: 100,
30
+ E: 500,
31
+ F: 600,
32
+ };
33
+ merge(mrg11, mrg12, mrg13);
34
+ Assert.deepStrictEqual(mrg11, {
35
+ A: 100,
36
+ B: 2,
37
+ C: 3,
38
+ D: 40,
39
+ E: 500,
40
+ F: 600,
41
+ });
42
+ console.log("TEST 2: Exclusions and array concatenation");
43
+ const mrg21 = {
44
+ A: 100,
45
+ B: new Date(-1000),
46
+ C: [0, 1, 2],
47
+ prototype: "I'm a prototype!",
48
+ };
49
+ const mrg22 = {
50
+ A: () => {
51
+ return true;
52
+ },
53
+ B: new Date(1000),
54
+ C: [3, 4, 5],
55
+ prototype: "I'm overwriting a prototype!",
56
+ [Symbol("Symbol")]: "I'm a symbol!",
57
+ };
58
+ merge(mrg21, mrg22);
59
+ Assert.deepStrictEqual(mrg21, {
60
+ A: 100,
61
+ B: mrg22.B,
62
+ C: [0, 1, 2, 3, 4, 5],
63
+ prototype: "I'm a prototype!",
64
+ });
65
+ //-------------------
66
+ // De-Comment JSON
67
+ //-------------------
68
+ console.log("\nFUNCTION: decommentJSON()");
69
+ console.log("TEST 1: Everything");
70
+ const dcm1 = `{
71
+ // "A" holds the number of apples.
72
+ "A": 10,
73
+ /* "B" holds the number of bananas.
74
+ Must be greater than 0. */
75
+ "B": 20,
76
+ /*
77
+ "C" holds the number of carrots.
78
+ Must be:
79
+ - An even number
80
+ - Divisible by 3
81
+ - Not divisible by 60
82
+ - The digits must no add up to 6
83
+ */
84
+ "C": 30,
85
+ "D": "//This is not a JSON comment"
86
+ }`;
87
+ const dcm2 = JSON.parse(decommentJSON(dcm1));
88
+ Assert.deepStrictEqual(dcm2, {
89
+ A: 10,
90
+ B: 20,
91
+ C: 30,
92
+ D: "//This is not a JSON comment",
93
+ });
94
+ //---------------------------
95
+ // Check for plain objects
96
+ //---------------------------
97
+ console.log("\nFUNCTION: checkPlain()");
98
+ console.log("TEST 1: Everything");
99
+ const chk1 = [
100
+ checkPlain(new Date()),
101
+ checkPlain({}),
102
+ ];
103
+ Assert.deepStrictEqual(chk1, [false, true]);
104
+ //===================
105
+ // ARRAY UTILITIES
106
+ //===================
107
+ console.log("\n\nCATEGORY: Array Utilities");
108
+ //-------------------
109
+ // Pull from array
110
+ //-------------------
111
+ console.log("\nFUNCTION: pull()");
112
+ console.log("TEST 1: Everything");
113
+ const pul1 = [10, 20, 30, 40, 50, 20];
114
+ pull(pul1, 20, 50);
115
+ Assert.deepStrictEqual(pul1, [10, 30, 40, 20]);
116
+ //------------
117
+ // Pull all
118
+ //------------
119
+ console.log("\nFUNCTION: pullAll()");
120
+ console.log("TEST 1: Everything");
121
+ const pul2 = [10, 20, 30, 40, 50, 20];
122
+ pullAll(pul2, 20, 50);
123
+ Assert.deepStrictEqual(pul2, [10, 30, 40]);
124
+ //--------------------
125
+ // Pull and replace
126
+ //--------------------
127
+ console.log("\nFUNCTION: replace()");
128
+ console.log("TEST 1: Everything");
129
+ const pul3 = [10, 20, 30, 40, 50, 20];
130
+ replace(pul3, 20, 60, 70);
131
+ Assert.deepStrictEqual(pul3, [10, 60, 70, 30, 40, 50, 20]);
132
+ //------------------------
133
+ // Pull and replace all
134
+ //------------------------
135
+ console.log("\nFUNCTION: replaceAll()");
136
+ console.log("TEST 1: Everything");
137
+ const pul4 = [10, 20, 30, 40, 50, 20];
138
+ replaceAll(pul4, 20, 60, 70);
139
+ Assert.deepStrictEqual(pul4, [10, 60, 70, 30, 40, 50, 60, 70]);
140
+ //--------------------
141
+ // Objectify arrays
142
+ //--------------------
143
+ console.log("\nFUNCTION: objectify()");
144
+ console.log("TEST 1: Everything");
145
+ const obj1 = ["zero", "one", "two", "three"];
146
+ const obj2 = objectify(obj1);
147
+ Assert.deepStrictEqual(obj2, {
148
+ 0: "zero",
149
+ 1: "one",
150
+ 2: "two",
151
+ 3: "three",
152
+ });
153
+ //====================
154
+ // STRINGIFY VALUES
155
+ //====================
156
+ console.log("\n\nCATEGORY: Stringify Values");
157
+ //---------------------
158
+ // General stringify
159
+ //---------------------
160
+ console.log("\nFUNCTION: stringify()");
161
+ console.log("TEST 1: General functionality with primitives");
162
+ const str1 = [
163
+ stringify("hello"),
164
+ stringify(100),
165
+ stringify(500n),
166
+ stringify(false),
167
+ stringify(Symbol("str")),
168
+ stringify(null),
169
+ stringify(undefined),
170
+ ];
171
+ Assert.deepStrictEqual(str1, [
172
+ "hello",
173
+ "100",
174
+ "500n",
175
+ "false",
176
+ "Symbol(str)",
177
+ "null",
178
+ "undefined",
179
+ ]);
180
+ console.log("TEST 2: Plain objects");
181
+ const str2 = [
182
+ stringify({}),
183
+ stringify({
184
+ A: 100,
185
+ B: {
186
+ C: 500,
187
+ },
188
+ }),
189
+ stringify({
190
+ A: 100,
191
+ toString: () => {
192
+ return "A is 100";
193
+ },
194
+ }),
195
+ stringify({
196
+ A: 100,
197
+ toString: "Not callable",
198
+ }),
199
+ stringify([1, 2, 3, 4, 5]),
200
+ ];
201
+ const str3 = `{
202
+ "A": 100,
203
+ "B": {
204
+ "C": 500
205
+ }
206
+ }`;
207
+ const str4 = `{
208
+ "A": 100,
209
+ "toString": "Not callable"
210
+ }`;
211
+ const str5 = `[
212
+ 1,
213
+ 2,
214
+ 3,
215
+ 4,
216
+ 5
217
+ ]`;
218
+ Assert.deepStrictEqual(str2, [
219
+ "{}",
220
+ str3,
221
+ "A is 100",
222
+ str4,
223
+ str5,
224
+ ]);
225
+ console.log("TEST 3: Object instances");
226
+ const str6 = new Date(0);
227
+ const str7 = [
228
+ stringify(str6),
229
+ stringify(new Error("Something went wrong!")),
230
+ stringify((new Set()).add("One")),
231
+ stringify((new Map()).set("1", "One")),
232
+ ];
233
+ const str8 = `[
234
+ "One"
235
+ ]`;
236
+ const str9 = `[
237
+ [
238
+ "1",
239
+ "One"
240
+ ]
241
+ ]`;
242
+ Assert.deepStrictEqual(str7, [
243
+ str6.toString(),
244
+ "Error: Something went wrong!",
245
+ str8,
246
+ str9,
247
+ ]);
248
+ //------------------
249
+ // Stringify JSON
250
+ //------------------
251
+ console.log("\nFUNCTION: stringifyJSON()");
252
+ console.log("TEST 1: Everything");
253
+ const jsn1 = {
254
+ A: 1,
255
+ B: 2,
256
+ C: (new Set()).add("Ten"),
257
+ };
258
+ const jsn2 = {
259
+ A: 1,
260
+ B: 2,
261
+ C: {
262
+ A: 100n,
263
+ B: 2,
264
+ },
265
+ };
266
+ const jsn3 = {
267
+ D: jsn1,
268
+ E: jsn1,
269
+ };
270
+ jsn3.F = jsn3;
271
+ const jsn4 = {
272
+ jsn1: stringifyJSON(jsn1),
273
+ jsn2: stringifyJSON(jsn2, ">", ["A", "C.B"]),
274
+ jsn3: stringifyJSON(jsn3),
275
+ };
276
+ const jsn5 = `{
277
+ "A": 1,
278
+ "B": 2,
279
+ "C": [
280
+ "Ten"
281
+ ]
282
+ }`;
283
+ const jsn6 = `{
284
+ >"B": 2,
285
+ >"C": {
286
+ >>"A": "100n"
287
+ >}
288
+ }`;
289
+ const jsn7 = `{
290
+ "D": {
291
+ "A": 1,
292
+ "B": 2,
293
+ "C": [
294
+ "Ten"
295
+ ]
296
+ },
297
+ "E": "[Circular: D]",
298
+ "F": "[Circular: ~]"
299
+ }`;
300
+ Assert.deepStrictEqual(jsn4, {
301
+ jsn1: jsn5,
302
+ jsn2: jsn6,
303
+ jsn3: jsn7,
304
+ });
305
+ //----------------------------
306
+ // Stringify array elements
307
+ //----------------------------
308
+ console.log("\nFUNCTION: stringifyArray()");
309
+ console.log("TEST 1: Everything");
310
+ const arr1 = [null, 20, {}];
311
+ const arr2 = stringifyArray(arr1);
312
+ Assert.deepStrictEqual(arr2, ["null", "20", "{}"]);
@@ -0,0 +1,8 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": "."
5
+ }
6
+ ],
7
+ "settings": {}
8
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@ethanblaisalarms/utils",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight helpers, primarily focused on array and object manipulation and improved stringification.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "prestart": "tsc",
14
+ "start": "node .",
15
+ "test": "npm run build && node dist/test.js"
16
+ },
17
+ "author": "EthanBlaisAlarms <support@ethanblaisalarms.com>",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "@types/node": "^25.3.5",
21
+ "typescript": "^5.9.3"
22
+ }
23
+ }