@bquery/bquery 1.0.1 → 1.1.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.
Files changed (41) hide show
  1. package/README.md +79 -25
  2. package/dist/component/index.d.ts +8 -0
  3. package/dist/component/index.d.ts.map +1 -1
  4. package/dist/component.es.mjs +80 -53
  5. package/dist/component.es.mjs.map +1 -1
  6. package/dist/core/collection.d.ts +46 -0
  7. package/dist/core/collection.d.ts.map +1 -1
  8. package/dist/core/element.d.ts +124 -22
  9. package/dist/core/element.d.ts.map +1 -1
  10. package/dist/core/utils.d.ts +13 -0
  11. package/dist/core/utils.d.ts.map +1 -1
  12. package/dist/core.es.mjs +298 -55
  13. package/dist/core.es.mjs.map +1 -1
  14. package/dist/full.d.ts +2 -2
  15. package/dist/full.d.ts.map +1 -1
  16. package/dist/full.es.mjs +38 -33
  17. package/dist/full.iife.js +1 -1
  18. package/dist/full.iife.js.map +1 -1
  19. package/dist/full.umd.js +1 -1
  20. package/dist/full.umd.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.es.mjs +38 -33
  23. package/dist/reactive/index.d.ts +2 -2
  24. package/dist/reactive/index.d.ts.map +1 -1
  25. package/dist/reactive/signal.d.ts +107 -0
  26. package/dist/reactive/signal.d.ts.map +1 -1
  27. package/dist/reactive.es.mjs +92 -55
  28. package/dist/reactive.es.mjs.map +1 -1
  29. package/dist/security/sanitize.d.ts.map +1 -1
  30. package/dist/security.es.mjs +136 -66
  31. package/dist/security.es.mjs.map +1 -1
  32. package/package.json +120 -120
  33. package/src/component/index.ts +414 -360
  34. package/src/core/collection.ts +454 -339
  35. package/src/core/element.ts +740 -493
  36. package/src/core/utils.ts +444 -425
  37. package/src/full.ts +106 -101
  38. package/src/index.ts +27 -27
  39. package/src/reactive/index.ts +22 -9
  40. package/src/reactive/signal.ts +506 -347
  41. package/src/security/sanitize.ts +553 -446
package/src/core/utils.ts CHANGED
@@ -1,425 +1,444 @@
1
- /**
2
- * Utility helpers used across the framework.
3
- * These are intentionally small and framework-agnostic to keep the core tiny.
4
- *
5
- * @module bquery/core/utils
6
- */
7
-
8
- /**
9
- * Utility object containing common helper functions.
10
- * All utilities are designed to be tree-shakeable and have zero dependencies.
11
- */
12
- export const utils = {
13
- /**
14
- * Creates a deep clone using structuredClone if available, otherwise fallback to JSON.
15
- *
16
- * @template T - The type of value being cloned
17
- * @param value - The value to clone
18
- * @returns A deep copy of the value
19
- *
20
- * @example
21
- * ```ts
22
- * const original = { nested: { value: 1 } };
23
- * const copy = utils.clone(original);
24
- * copy.nested.value = 2;
25
- * console.log(original.nested.value); // 1
26
- * ```
27
- */
28
- clone<T>(value: T): T {
29
- if (typeof structuredClone === 'function') {
30
- return structuredClone(value);
31
- }
32
- return JSON.parse(JSON.stringify(value)) as T;
33
- },
34
-
35
- /**
36
- * Deep-merges plain objects into a new object.
37
- * Later sources override earlier ones for primitive values.
38
- * Objects are recursively merged.
39
- *
40
- * @template T - The type of the merged object
41
- * @param sources - Objects to merge
42
- * @returns A new object with all sources merged
43
- *
44
- * @example
45
- * ```ts
46
- * const result = utils.merge(
47
- * { a: 1, nested: { x: 1 } },
48
- * { b: 2, nested: { y: 2 } }
49
- * );
50
- * // Result: { a: 1, b: 2, nested: { x: 1, y: 2 } }
51
- * ```
52
- */
53
- merge<T extends Record<string, unknown>>(...sources: T[]): T {
54
- const result: Record<string, unknown> = {};
55
- for (const source of sources) {
56
- for (const [key, value] of Object.entries(source)) {
57
- if (utils.isPlainObject(value) && utils.isPlainObject(result[key])) {
58
- result[key] = utils.merge(
59
- result[key] as Record<string, unknown>,
60
- value as Record<string, unknown>
61
- );
62
- } else {
63
- result[key] = value;
64
- }
65
- }
66
- }
67
- return result as T;
68
- },
69
-
70
- /**
71
- * Creates a debounced function that delays execution until after
72
- * the specified delay has elapsed since the last call.
73
- *
74
- * @template TArgs - The argument types of the function
75
- * @param fn - The function to debounce
76
- * @param delayMs - Delay in milliseconds
77
- * @returns A debounced version of the function
78
- *
79
- * @example
80
- * ```ts
81
- * const search = utils.debounce((query: string) => {
82
- * console.log('Searching:', query);
83
- * }, 300);
84
- *
85
- * search('h');
86
- * search('he');
87
- * search('hello'); // Only this call executes after 300ms
88
- * ```
89
- */
90
- debounce<TArgs extends unknown[]>(
91
- fn: (...args: TArgs) => void,
92
- delayMs: number
93
- ): (...args: TArgs) => void {
94
- let timeoutId: ReturnType<typeof setTimeout> | undefined;
95
- return (...args: TArgs) => {
96
- if (timeoutId) {
97
- clearTimeout(timeoutId);
98
- }
99
- timeoutId = setTimeout(() => fn(...args), delayMs);
100
- };
101
- },
102
-
103
- /**
104
- * Creates a throttled function that runs at most once per interval.
105
- *
106
- * @template TArgs - The argument types of the function
107
- * @param fn - The function to throttle
108
- * @param intervalMs - Minimum interval between calls in milliseconds
109
- * @returns A throttled version of the function
110
- *
111
- * @example
112
- * ```ts
113
- * const handleScroll = utils.throttle(() => {
114
- * console.log('Scroll position:', window.scrollY);
115
- * }, 100);
116
- *
117
- * window.addEventListener('scroll', handleScroll);
118
- * ```
119
- */
120
- throttle<TArgs extends unknown[]>(
121
- fn: (...args: TArgs) => void,
122
- intervalMs: number
123
- ): (...args: TArgs) => void {
124
- let lastRun = 0;
125
- return (...args: TArgs) => {
126
- const now = Date.now();
127
- if (now - lastRun >= intervalMs) {
128
- lastRun = now;
129
- fn(...args);
130
- }
131
- };
132
- },
133
-
134
- /**
135
- * Creates a stable unique ID for DOM usage.
136
- *
137
- * @param prefix - Optional prefix for the ID (default: 'bQuery')
138
- * @returns A unique identifier string
139
- *
140
- * @example
141
- * ```ts
142
- * const id = utils.uid('modal'); // 'modal_x7k2m9p'
143
- * ```
144
- */
145
- uid(prefix = 'bQuery'): string {
146
- return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
147
- },
148
-
149
- /**
150
- * Checks if a value is a DOM Element.
151
- *
152
- * @param value - The value to check
153
- * @returns True if the value is an Element
154
- */
155
- isElement(value: unknown): value is Element {
156
- return value instanceof Element;
157
- },
158
-
159
- /**
160
- * Checks if a value is a BQueryCollection-like object.
161
- *
162
- * @param value - The value to check
163
- * @returns True if the value has an elements array property
164
- */
165
- isCollection(value: unknown): value is { elements: Element[] } {
166
- return Boolean(value && typeof value === 'object' && 'elements' in (value as object));
167
- },
168
-
169
- /**
170
- * Checks for emptiness across common value types.
171
- *
172
- * @param value - The value to check
173
- * @returns True if the value is empty (null, undefined, empty string, empty array, or empty object)
174
- *
175
- * @example
176
- * ```ts
177
- * utils.isEmpty(''); // true
178
- * utils.isEmpty([]); // true
179
- * utils.isEmpty({}); // true
180
- * utils.isEmpty(null); // true
181
- * utils.isEmpty('hello'); // false
182
- * utils.isEmpty([1, 2]); // false
183
- * ```
184
- */
185
- isEmpty(value: unknown): boolean {
186
- if (value == null) return true;
187
- if (typeof value === 'string') return value.trim().length === 0;
188
- if (Array.isArray(value)) return value.length === 0;
189
- if (typeof value === 'object') return Object.keys(value as object).length === 0;
190
- return false;
191
- },
192
-
193
- /**
194
- * Checks if a value is a plain object (not null, array, or class instance).
195
- *
196
- * @param value - The value to check
197
- * @returns True if the value is a plain object
198
- */
199
- isPlainObject(value: unknown): value is Record<string, unknown> {
200
- return Object.prototype.toString.call(value) === '[object Object]';
201
- },
202
-
203
- /**
204
- * Checks if a value is a function.
205
- *
206
- * @param value - The value to check
207
- * @returns True if the value is a function
208
- */
209
- isFunction(value: unknown): value is (...args: unknown[]) => unknown {
210
- return typeof value === 'function';
211
- },
212
-
213
- /**
214
- * Checks if a value is a string.
215
- *
216
- * @param value - The value to check
217
- * @returns True if the value is a string
218
- */
219
- isString(value: unknown): value is string {
220
- return typeof value === 'string';
221
- },
222
-
223
- /**
224
- * Checks if a value is a number (excluding NaN).
225
- *
226
- * @param value - The value to check
227
- * @returns True if the value is a valid number
228
- */
229
- isNumber(value: unknown): value is number {
230
- return typeof value === 'number' && !Number.isNaN(value);
231
- },
232
-
233
- /**
234
- * Checks if a value is a boolean.
235
- *
236
- * @param value - The value to check
237
- * @returns True if the value is a boolean
238
- */
239
- isBoolean(value: unknown): value is boolean {
240
- return typeof value === 'boolean';
241
- },
242
-
243
- /**
244
- * Checks if a value is an array.
245
- *
246
- * @template T - The type of array elements
247
- * @param value - The value to check
248
- * @returns True if the value is an array
249
- */
250
- isArray<T = unknown>(value: unknown): value is T[] {
251
- return Array.isArray(value);
252
- },
253
-
254
- /**
255
- * Safely parses a JSON string, returning a default value on error.
256
- *
257
- * @template T - The expected type of the parsed value
258
- * @param json - The JSON string to parse
259
- * @param fallback - The default value if parsing fails
260
- * @returns The parsed value or the fallback
261
- *
262
- * @example
263
- * ```ts
264
- * utils.parseJson('{"name":"bQuery"}', {}); // { name: 'bQuery' }
265
- * utils.parseJson('invalid', {}); // {}
266
- * ```
267
- */
268
- parseJson<T>(json: string, fallback: T): T {
269
- try {
270
- return JSON.parse(json) as T;
271
- } catch {
272
- return fallback;
273
- }
274
- },
275
-
276
- /**
277
- * Picks specified keys from an object.
278
- *
279
- * @template T - The object type
280
- * @template K - The key type
281
- * @param obj - The source object
282
- * @param keys - Keys to pick
283
- * @returns A new object with only the specified keys
284
- *
285
- * @example
286
- * ```ts
287
- * const user = { name: 'John', age: 30, email: 'john@example.com' };
288
- * utils.pick(user, ['name', 'email']); // { name: 'John', email: 'john@example.com' }
289
- * ```
290
- */
291
- pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
292
- const result = {} as Pick<T, K>;
293
- for (const key of keys) {
294
- if (key in obj) {
295
- result[key] = obj[key];
296
- }
297
- }
298
- return result;
299
- },
300
-
301
- /**
302
- * Omits specified keys from an object.
303
- *
304
- * @template T - The object type
305
- * @template K - The key type
306
- * @param obj - The source object
307
- * @param keys - Keys to omit
308
- * @returns A new object without the specified keys
309
- *
310
- * @example
311
- * ```ts
312
- * const user = { name: 'John', age: 30, password: 'secret' };
313
- * utils.omit(user, ['password']); // { name: 'John', age: 30 }
314
- * ```
315
- */
316
- omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
317
- const result = { ...obj };
318
- for (const key of keys) {
319
- delete result[key];
320
- }
321
- return result as Omit<T, K>;
322
- },
323
-
324
- /**
325
- * Delays execution for a specified number of milliseconds.
326
- *
327
- * @param ms - Milliseconds to delay
328
- * @returns A promise that resolves after the delay
329
- *
330
- * @example
331
- * ```ts
332
- * await utils.sleep(1000); // Wait 1 second
333
- * console.log('Done!');
334
- * ```
335
- */
336
- sleep(ms: number): Promise<void> {
337
- return new Promise((resolve) => setTimeout(resolve, ms));
338
- },
339
-
340
- /**
341
- * Generates a random integer between min and max (inclusive).
342
- *
343
- * @param min - Minimum value
344
- * @param max - Maximum value
345
- * @returns A random integer in the range [min, max]
346
- *
347
- * @example
348
- * ```ts
349
- * const roll = utils.randomInt(1, 6); // Random dice roll
350
- * ```
351
- */
352
- randomInt(min: number, max: number): number {
353
- return Math.floor(Math.random() * (max - min + 1)) + min;
354
- },
355
-
356
- /**
357
- * Clamps a number between a minimum and maximum value.
358
- *
359
- * @param value - The value to clamp
360
- * @param min - Minimum value
361
- * @param max - Maximum value
362
- * @returns The clamped value
363
- *
364
- * @example
365
- * ```ts
366
- * utils.clamp(150, 0, 100); // 100
367
- * utils.clamp(-10, 0, 100); // 0
368
- * utils.clamp(50, 0, 100); // 50
369
- * ```
370
- */
371
- clamp(value: number, min: number, max: number): number {
372
- return Math.min(Math.max(value, min), max);
373
- },
374
-
375
- /**
376
- * Capitalizes the first letter of a string.
377
- *
378
- * @param str - The string to capitalize
379
- * @returns The capitalized string
380
- *
381
- * @example
382
- * ```ts
383
- * utils.capitalize('hello'); // 'Hello'
384
- * ```
385
- */
386
- capitalize(str: string): string {
387
- if (!str) return str;
388
- return str.charAt(0).toUpperCase() + str.slice(1);
389
- },
390
-
391
- /**
392
- * Converts a string to kebab-case.
393
- *
394
- * @param str - The string to convert
395
- * @returns The kebab-cased string
396
- *
397
- * @example
398
- * ```ts
399
- * utils.toKebabCase('myVariableName'); // 'my-variable-name'
400
- * ```
401
- */
402
- toKebabCase(str: string): string {
403
- return str
404
- .replace(/([a-z])([A-Z])/g, '$1-$2')
405
- .replace(/[\s_]+/g, '-')
406
- .toLowerCase();
407
- },
408
-
409
- /**
410
- * Converts a string to camelCase.
411
- *
412
- * @param str - The string to convert
413
- * @returns The camelCased string
414
- *
415
- * @example
416
- * ```ts
417
- * utils.toCamelCase('my-variable-name'); // 'myVariableName'
418
- * ```
419
- */
420
- toCamelCase(str: string): string {
421
- return str
422
- .replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
423
- .replace(/^[A-Z]/, (char) => char.toLowerCase());
424
- },
425
- };
1
+ /**
2
+ * Utility helpers used across the framework.
3
+ * These are intentionally small and framework-agnostic to keep the core tiny.
4
+ *
5
+ * @module bquery/core/utils
6
+ */
7
+
8
+ /**
9
+ * Utility object containing common helper functions.
10
+ * All utilities are designed to be tree-shakeable and have zero dependencies.
11
+ */
12
+ export const utils = {
13
+ /**
14
+ * Creates a deep clone using structuredClone if available, otherwise fallback to JSON.
15
+ *
16
+ * @template T - The type of value being cloned
17
+ * @param value - The value to clone
18
+ * @returns A deep copy of the value
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const original = { nested: { value: 1 } };
23
+ * const copy = utils.clone(original);
24
+ * copy.nested.value = 2;
25
+ * console.log(original.nested.value); // 1
26
+ * ```
27
+ */
28
+ clone<T>(value: T): T {
29
+ if (typeof structuredClone === 'function') {
30
+ return structuredClone(value);
31
+ }
32
+ return JSON.parse(JSON.stringify(value)) as T;
33
+ },
34
+
35
+ /**
36
+ * Deep-merges plain objects into a new object.
37
+ * Later sources override earlier ones for primitive values.
38
+ * Objects are recursively merged.
39
+ *
40
+ * @template T - The type of the merged object
41
+ * @param sources - Objects to merge
42
+ * @returns A new object with all sources merged
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const result = utils.merge(
47
+ * { a: 1, nested: { x: 1 } },
48
+ * { b: 2, nested: { y: 2 } }
49
+ * );
50
+ * // Result: { a: 1, b: 2, nested: { x: 1, y: 2 } }
51
+ * ```
52
+ *
53
+ * @security This method is protected against prototype pollution attacks.
54
+ * Keys like `__proto__`, `constructor`, and `prototype` are ignored.
55
+ */
56
+ merge<T extends Record<string, unknown>>(...sources: T[]): T {
57
+ const result: Record<string, unknown> = {};
58
+ for (const source of sources) {
59
+ for (const [key, value] of Object.entries(source)) {
60
+ // Prevent prototype pollution attacks
61
+ if (utils.isPrototypePollutionKey(key)) continue;
62
+
63
+ if (utils.isPlainObject(value) && utils.isPlainObject(result[key])) {
64
+ result[key] = utils.merge(
65
+ result[key] as Record<string, unknown>,
66
+ value as Record<string, unknown>
67
+ );
68
+ } else {
69
+ result[key] = value;
70
+ }
71
+ }
72
+ }
73
+ return result as T;
74
+ },
75
+
76
+ /**
77
+ * Checks if a key could cause prototype pollution.
78
+ * These keys are dangerous when used in object merging operations.
79
+ *
80
+ * @param key - The key to check
81
+ * @returns True if the key is a prototype pollution vector
82
+ *
83
+ * @internal
84
+ */
85
+ isPrototypePollutionKey(key: string): boolean {
86
+ return key === '__proto__' || key === 'constructor' || key === 'prototype';
87
+ },
88
+
89
+ /**
90
+ * Creates a debounced function that delays execution until after
91
+ * the specified delay has elapsed since the last call.
92
+ *
93
+ * @template TArgs - The argument types of the function
94
+ * @param fn - The function to debounce
95
+ * @param delayMs - Delay in milliseconds
96
+ * @returns A debounced version of the function
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const search = utils.debounce((query: string) => {
101
+ * console.log('Searching:', query);
102
+ * }, 300);
103
+ *
104
+ * search('h');
105
+ * search('he');
106
+ * search('hello'); // Only this call executes after 300ms
107
+ * ```
108
+ */
109
+ debounce<TArgs extends unknown[]>(
110
+ fn: (...args: TArgs) => void,
111
+ delayMs: number
112
+ ): (...args: TArgs) => void {
113
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
114
+ return (...args: TArgs) => {
115
+ if (timeoutId) {
116
+ clearTimeout(timeoutId);
117
+ }
118
+ timeoutId = setTimeout(() => fn(...args), delayMs);
119
+ };
120
+ },
121
+
122
+ /**
123
+ * Creates a throttled function that runs at most once per interval.
124
+ *
125
+ * @template TArgs - The argument types of the function
126
+ * @param fn - The function to throttle
127
+ * @param intervalMs - Minimum interval between calls in milliseconds
128
+ * @returns A throttled version of the function
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * const handleScroll = utils.throttle(() => {
133
+ * console.log('Scroll position:', window.scrollY);
134
+ * }, 100);
135
+ *
136
+ * window.addEventListener('scroll', handleScroll);
137
+ * ```
138
+ */
139
+ throttle<TArgs extends unknown[]>(
140
+ fn: (...args: TArgs) => void,
141
+ intervalMs: number
142
+ ): (...args: TArgs) => void {
143
+ let lastRun = 0;
144
+ return (...args: TArgs) => {
145
+ const now = Date.now();
146
+ if (now - lastRun >= intervalMs) {
147
+ lastRun = now;
148
+ fn(...args);
149
+ }
150
+ };
151
+ },
152
+
153
+ /**
154
+ * Creates a stable unique ID for DOM usage.
155
+ *
156
+ * @param prefix - Optional prefix for the ID (default: 'bQuery')
157
+ * @returns A unique identifier string
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const id = utils.uid('modal'); // 'modal_x7k2m9p'
162
+ * ```
163
+ */
164
+ uid(prefix = 'bQuery'): string {
165
+ return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
166
+ },
167
+
168
+ /**
169
+ * Checks if a value is a DOM Element.
170
+ *
171
+ * @param value - The value to check
172
+ * @returns True if the value is an Element
173
+ */
174
+ isElement(value: unknown): value is Element {
175
+ return value instanceof Element;
176
+ },
177
+
178
+ /**
179
+ * Checks if a value is a BQueryCollection-like object.
180
+ *
181
+ * @param value - The value to check
182
+ * @returns True if the value has an elements array property
183
+ */
184
+ isCollection(value: unknown): value is { elements: Element[] } {
185
+ return Boolean(value && typeof value === 'object' && 'elements' in (value as object));
186
+ },
187
+
188
+ /**
189
+ * Checks for emptiness across common value types.
190
+ *
191
+ * @param value - The value to check
192
+ * @returns True if the value is empty (null, undefined, empty string, empty array, or empty object)
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * utils.isEmpty(''); // true
197
+ * utils.isEmpty([]); // true
198
+ * utils.isEmpty({}); // true
199
+ * utils.isEmpty(null); // true
200
+ * utils.isEmpty('hello'); // false
201
+ * utils.isEmpty([1, 2]); // false
202
+ * ```
203
+ */
204
+ isEmpty(value: unknown): boolean {
205
+ if (value == null) return true;
206
+ if (typeof value === 'string') return value.trim().length === 0;
207
+ if (Array.isArray(value)) return value.length === 0;
208
+ if (typeof value === 'object') return Object.keys(value as object).length === 0;
209
+ return false;
210
+ },
211
+
212
+ /**
213
+ * Checks if a value is a plain object (not null, array, or class instance).
214
+ *
215
+ * @param value - The value to check
216
+ * @returns True if the value is a plain object
217
+ */
218
+ isPlainObject(value: unknown): value is Record<string, unknown> {
219
+ return Object.prototype.toString.call(value) === '[object Object]';
220
+ },
221
+
222
+ /**
223
+ * Checks if a value is a function.
224
+ *
225
+ * @param value - The value to check
226
+ * @returns True if the value is a function
227
+ */
228
+ isFunction(value: unknown): value is (...args: unknown[]) => unknown {
229
+ return typeof value === 'function';
230
+ },
231
+
232
+ /**
233
+ * Checks if a value is a string.
234
+ *
235
+ * @param value - The value to check
236
+ * @returns True if the value is a string
237
+ */
238
+ isString(value: unknown): value is string {
239
+ return typeof value === 'string';
240
+ },
241
+
242
+ /**
243
+ * Checks if a value is a number (excluding NaN).
244
+ *
245
+ * @param value - The value to check
246
+ * @returns True if the value is a valid number
247
+ */
248
+ isNumber(value: unknown): value is number {
249
+ return typeof value === 'number' && !Number.isNaN(value);
250
+ },
251
+
252
+ /**
253
+ * Checks if a value is a boolean.
254
+ *
255
+ * @param value - The value to check
256
+ * @returns True if the value is a boolean
257
+ */
258
+ isBoolean(value: unknown): value is boolean {
259
+ return typeof value === 'boolean';
260
+ },
261
+
262
+ /**
263
+ * Checks if a value is an array.
264
+ *
265
+ * @template T - The type of array elements
266
+ * @param value - The value to check
267
+ * @returns True if the value is an array
268
+ */
269
+ isArray<T = unknown>(value: unknown): value is T[] {
270
+ return Array.isArray(value);
271
+ },
272
+
273
+ /**
274
+ * Safely parses a JSON string, returning a default value on error.
275
+ *
276
+ * @template T - The expected type of the parsed value
277
+ * @param json - The JSON string to parse
278
+ * @param fallback - The default value if parsing fails
279
+ * @returns The parsed value or the fallback
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * utils.parseJson('{"name":"bQuery"}', {}); // { name: 'bQuery' }
284
+ * utils.parseJson('invalid', {}); // {}
285
+ * ```
286
+ */
287
+ parseJson<T>(json: string, fallback: T): T {
288
+ try {
289
+ return JSON.parse(json) as T;
290
+ } catch {
291
+ return fallback;
292
+ }
293
+ },
294
+
295
+ /**
296
+ * Picks specified keys from an object.
297
+ *
298
+ * @template T - The object type
299
+ * @template K - The key type
300
+ * @param obj - The source object
301
+ * @param keys - Keys to pick
302
+ * @returns A new object with only the specified keys
303
+ *
304
+ * @example
305
+ * ```ts
306
+ * const user = { name: 'John', age: 30, email: 'john@example.com' };
307
+ * utils.pick(user, ['name', 'email']); // { name: 'John', email: 'john@example.com' }
308
+ * ```
309
+ */
310
+ pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
311
+ const result = {} as Pick<T, K>;
312
+ for (const key of keys) {
313
+ if (key in obj) {
314
+ result[key] = obj[key];
315
+ }
316
+ }
317
+ return result;
318
+ },
319
+
320
+ /**
321
+ * Omits specified keys from an object.
322
+ *
323
+ * @template T - The object type
324
+ * @template K - The key type
325
+ * @param obj - The source object
326
+ * @param keys - Keys to omit
327
+ * @returns A new object without the specified keys
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const user = { name: 'John', age: 30, password: 'secret' };
332
+ * utils.omit(user, ['password']); // { name: 'John', age: 30 }
333
+ * ```
334
+ */
335
+ omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
336
+ const result = { ...obj };
337
+ for (const key of keys) {
338
+ delete result[key];
339
+ }
340
+ return result as Omit<T, K>;
341
+ },
342
+
343
+ /**
344
+ * Delays execution for a specified number of milliseconds.
345
+ *
346
+ * @param ms - Milliseconds to delay
347
+ * @returns A promise that resolves after the delay
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * await utils.sleep(1000); // Wait 1 second
352
+ * console.log('Done!');
353
+ * ```
354
+ */
355
+ sleep(ms: number): Promise<void> {
356
+ return new Promise((resolve) => setTimeout(resolve, ms));
357
+ },
358
+
359
+ /**
360
+ * Generates a random integer between min and max (inclusive).
361
+ *
362
+ * @param min - Minimum value
363
+ * @param max - Maximum value
364
+ * @returns A random integer in the range [min, max]
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * const roll = utils.randomInt(1, 6); // Random dice roll
369
+ * ```
370
+ */
371
+ randomInt(min: number, max: number): number {
372
+ return Math.floor(Math.random() * (max - min + 1)) + min;
373
+ },
374
+
375
+ /**
376
+ * Clamps a number between a minimum and maximum value.
377
+ *
378
+ * @param value - The value to clamp
379
+ * @param min - Minimum value
380
+ * @param max - Maximum value
381
+ * @returns The clamped value
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * utils.clamp(150, 0, 100); // 100
386
+ * utils.clamp(-10, 0, 100); // 0
387
+ * utils.clamp(50, 0, 100); // 50
388
+ * ```
389
+ */
390
+ clamp(value: number, min: number, max: number): number {
391
+ return Math.min(Math.max(value, min), max);
392
+ },
393
+
394
+ /**
395
+ * Capitalizes the first letter of a string.
396
+ *
397
+ * @param str - The string to capitalize
398
+ * @returns The capitalized string
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * utils.capitalize('hello'); // 'Hello'
403
+ * ```
404
+ */
405
+ capitalize(str: string): string {
406
+ if (!str) return str;
407
+ return str.charAt(0).toUpperCase() + str.slice(1);
408
+ },
409
+
410
+ /**
411
+ * Converts a string to kebab-case.
412
+ *
413
+ * @param str - The string to convert
414
+ * @returns The kebab-cased string
415
+ *
416
+ * @example
417
+ * ```ts
418
+ * utils.toKebabCase('myVariableName'); // 'my-variable-name'
419
+ * ```
420
+ */
421
+ toKebabCase(str: string): string {
422
+ return str
423
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
424
+ .replace(/[\s_]+/g, '-')
425
+ .toLowerCase();
426
+ },
427
+
428
+ /**
429
+ * Converts a string to camelCase.
430
+ *
431
+ * @param str - The string to convert
432
+ * @returns The camelCased string
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * utils.toCamelCase('my-variable-name'); // 'myVariableName'
437
+ * ```
438
+ */
439
+ toCamelCase(str: string): string {
440
+ return str
441
+ .replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
442
+ .replace(/^[A-Z]/, (char) => char.toLowerCase());
443
+ },
444
+ };