@ocavue/utils 1.0.0 → 1.2.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
@@ -18,10 +18,10 @@ declare function isSet(value: unknown): value is Set<unknown>;
18
18
  *
19
19
  * Similar to Python's [defaultdict](https://docs.python.org/3.13/library/collections.html#collections.defaultdict).
20
20
  */
21
- declare class DefaultMap<K, V> extends Map<K, V> {
21
+ declare class DefaultMap<K$1, V> extends Map<K$1, V> {
22
22
  private readonly defaultFactory;
23
- constructor(defaultFactory: () => V, iterable?: Iterable<readonly [K, V]>);
24
- get(key: K): V;
23
+ constructor(defaultFactory: () => V, iterable?: Iterable<readonly [K$1, V]>);
24
+ get(key: K$1): V;
25
25
  }
26
26
  //#endregion
27
27
  //#region src/dom.d.ts
@@ -98,6 +98,119 @@ declare function formatBytes(bytes: number): string;
98
98
  */
99
99
  declare function getId(): number;
100
100
  //#endregion
101
+ //#region src/is-deep-equal.d.ts
102
+ /**
103
+ * Whether two values are deeply equal.
104
+ */
105
+ declare function isDeepEqual(a: unknown, b: unknown): boolean;
106
+ //#endregion
107
+ //#region src/map-group-by.d.ts
108
+ /**
109
+ * A polyfill for the `Map.groupBy` static method.
110
+ *
111
+ * @public
112
+ */
113
+ declare const mapGroupBy: <K$1, T>(items: Iterable<T>, keySelector: (item: T, index: number) => K$1) => Map<K$1, T[]>;
114
+ //#endregion
115
+ //#region src/map-values.d.ts
116
+ /**
117
+ * Creates a new object with the same keys as the input object, but with values
118
+ * transformed by the provided callback function. Similar to `Array.prototype.map()`
119
+ * but for object values.
120
+
121
+ * @param object - The object whose values will be transformed.
122
+ * @param callback - A function that transforms each value. Receives the value and
123
+ * its corresponding key as arguments.
124
+ * @returns A new object with the same keys but transformed values.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const prices = { apple: 1, banana: 2, orange: 3 }
129
+ * const doubled = mapValues(prices, (price) => price * 2)
130
+ * // Result: { apple: 2, banana: 4, orange: 6 }
131
+ * ```
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const users = { john: 25, jane: 30, bob: 35 }
136
+ * const greetings = mapValues(users, (age, name) => `${name} is ${age} years old`)
137
+ * // Result: { john: 'john is 25 years old', jane: 'jane is 30 years old', bob: 'bob is 35 years old' }
138
+ * ```
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const data = { a: '1', b: '2', c: '3' }
143
+ * const numbers = mapValues(data, (str) => parseInt(str, 10))
144
+ * // Result: { a: 1, b: 2, c: 3 }
145
+ * ```
146
+ *
147
+ * @public
148
+ */
149
+ declare function mapValues<ValueIn, ValueOut>(object: Record<string, ValueIn>, callback: (value: ValueIn, key: string) => ValueOut): Record<string, ValueOut>;
150
+ //#endregion
151
+ //#region src/object-entries.d.ts
152
+ /**
153
+ * A TypeScript utility type that represents the entries of an object as a union of tuple types.
154
+ * Each tuple contains a key-value pair where the key and value types are precisely typed
155
+ * according to the input object type.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * type MyObject = { a: 1; b: 'B' }
160
+ * type MyEntries = ObjectEntries<MyObject>
161
+ * // ^ ["a", 1] | ["b", "B"]
162
+ * ```
163
+ *
164
+ * @public
165
+ */
166
+ type ObjectEntries<T extends Record<string, any>> = { [K in keyof T]: [K, T[K]] }[keyof T];
167
+ /**
168
+ * A type-safe wrapper around `Object.entries()` that preserves the exact types of object keys
169
+ * and values. Unlike the standard `Object.entries()` which returns `[string, any][]`, this
170
+ * function returns an array of tuples where each tuple is precisely typed according to the
171
+ * input object's structure.
172
+ *
173
+ * This is particularly useful when working with objects that have known, fixed property types
174
+ * and you want to maintain type safety when iterating over entries.
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const myObject = { a: 1, b: 'hello', c: true } as const
179
+ * const entries = objectEntries(myObject)
180
+ * // Type: (["a", 1] | ["b", "hello"] | ["c", true])[]
181
+ *
182
+ * for (const [key, value] of entries) {
183
+ * // key is typed as "a" | "b" | "c"
184
+ * // value is typed as 1 | "hello" | true
185
+ * console.log(`${key}: ${value}`)
186
+ * }
187
+ * ```
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * interface User {
192
+ * name: string
193
+ * age: number
194
+ * active: boolean
195
+ * }
196
+ *
197
+ * const user: User = { name: 'Alice', age: 30, active: true }
198
+ * const entries = objectEntries(user)
199
+ * // Type: (["name", string] | ["age", number] | ["active", boolean])[]
200
+ * ```
201
+ *
202
+ * @public
203
+ */
204
+ declare const objectEntries: <T extends Record<string, any>>(obj: T) => ObjectEntries<T>[];
205
+ //#endregion
206
+ //#region src/object-group-by.d.ts
207
+ /**
208
+ * A polyfill for the `Object.groupBy` static method.
209
+ *
210
+ * @public
211
+ */
212
+ declare const objectGroupBy: <K$1 extends PropertyKey, T>(items: Iterable<T>, keySelector: (item: T, index: number) => K$1) => Partial<Record<K$1, T[]>>;
213
+ //#endregion
101
214
  //#region src/once.d.ts
102
215
  /**
103
216
  * Creates a function that will only execute the provided function once.
@@ -120,11 +233,5 @@ declare function once<T>(fn: () => T): () => T;
120
233
  */
121
234
  declare function sleep(ms: number): Promise<void>;
122
235
  //#endregion
123
- //#region src/is-deep-equal.d.ts
124
- /**
125
- * Whether two values are deeply equal.
126
- */
127
- declare function isDeepEqual(a: unknown, b: unknown): boolean;
128
- //#endregion
129
- export { DefaultMap, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, once, sleep };
236
+ export { DefaultMap, type ObjectEntries, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, mapGroupBy, mapValues, objectEntries, objectGroupBy, once, sleep };
130
237
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/once.ts","../src/sleep.ts","../src/is-deep-equal.ts"],"sourcesContent":[],"mappings":";;AAGA;AASA;AAOgB,iBAhBA,QAAA,CAgBmC,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IAdvC,MAcuC,CAAA,MAAA,GAAA,MAAA,GAAA,MAAA,EAAA,OAAA,CAAA;;;;ACdtC,iBDOG,KAAA,CCPO,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IDOyB,GCPzB,CAAA,OAAA,EAAA,OAAA,CAAA;;;;AAG8C,iBDWrD,KAAA,CCXqD,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IDWrB,GCXqB,CAAA,OAAA,CAAA;;;;ADLrE;AASA;AAOA;;cCda,yBAAyB,IAAI,GAAG;;EAAhC,WAAA,CAAA,cAAU,EAAA,GAAA,GAGa,CAHb,EAAA,QAAA,CAAA,EAG2B,QAH3B,CAAA,SAAA,CAG8C,CAH9C,EAGiD,CAHjD,CAAA,CAAA;EAAmB,GAAA,CAAA,GAAA,EAQtB,CARsB,CAAA,EAQlB,CARkB;;;;;ADF1C;AASA;AAOgB,iBEbA,SAAA,CFamC,IAAA,EEbnB,IFamB,CAAA,EAAA,IAAA,IEbJ,OFaI;;;;ACdtC,iBCQG,UAAA,CDRO,IAAA,ECQU,IDRV,CAAA,EAAA,IAAA,ICQyB,IDRzB;;;;AAG8C,iBCYrD,aAAA,CDZqD,IAAA,ECYjC,IDZiC,CAAA,EAAA,IAAA,ICYlB,WDZkB;;;;AAK7C,iBCcR,YAAA,CDdQ,IAAA,ECcW,IDdX,CAAA,EAAA,IAAA,ICc0B,UDd1B;;;;iBCqBR,eAAA,OAAsB,eAAe;;AA5BrD;AAOA;AAOgB,iBAwBA,UAAA,CAxBoB,IAAe,EAwBlB,IAxBkB,CAAA,EAAA,IAAW,IAwBd,QAxBc;AAO9D;AAOA;AAUA;AAOgB,iBAAA,kBAAA,CAAwC,IAAA,EAAf,IAAe,CAAA,EAAA,IAAgB,IAAhB,gBAAgB;AAOxE;AAOA;AAOA;AAWgB,iBAzBA,YAAA,CAyBuC,IAAM,EAzB1B,IAyB0B,CAAA,EAAA,IAAA,IAzBX,UAyBW;AAQ7D;;;AAC+B,iBA3Bf,UAAA,CA2Be,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IA3BsB,IA2BtB;;;;AAoBf,iBAxCA,aAAA,CAwCW,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IAxC6B,OAwC7B;;;;AACU,iBA9BrB,YAAA,CA8BqB,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IA9BkB,MA8BlB;;;AAiBrC;;AACqB,iBAxCL,SAAA,CAwCK,MAAA,CAAA,EAvCV,IAuCU,GAvCH,UAuCG,GAvCU,QAuCV,GAAA,IAAA,CAAA,EAtClB,MAsCkB,GAAA,OAtCF,UAsCE;;;;;iBAnBL,WAAA,UACL,UAAU,SAAS,OAAO,kBAClC;;;AC7GH;iBD6HgB,kBAAA,UACL,UAAU,OAAO,SAAS,kBAClC;;;;AFjIH;AASA;AAOA;;iBGdgB,WAAA;;;;AHFhB;AASA;AAOgB,iBIdA,KAAA,CAAA,CJcmC,EAAA,MAAA;;;;AAhBnD;AASA;AAOA;;;;ACdA;;;;;;AAGkD,iBIKlC,IJLkC,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,GAAA,GIKhB,CJLgB,CAAA,EAAA,GAAA,GIKN,CJLM;;;;ADLlD;AASA;AAOgB,iBMhBA,KAAA,CNgBmC,EAAA,EAAA,MAAA,CAAA,EMhBhB,ONgBgB,CAAA,IAAA,CAAA;;;;AAhBnD;AASA;AAOgB,iBOdA,WAAA,CPcmC,CAAA,EAAA,OAAA,EAAA,CAAA,EAAA,OAAA,CAAA,EAAA,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/is-deep-equal.ts","../src/map-group-by.ts","../src/map-values.ts","../src/object-entries.ts","../src/object-group-by.ts","../src/once.ts","../src/sleep.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -179,43 +179,6 @@ function getId() {
179
179
  return id;
180
180
  }
181
181
 
182
- //#endregion
183
- //#region src/once.ts
184
- /**
185
- * Creates a function that will only execute the provided function once.
186
- * Subsequent calls will return the cached result from the first execution.
187
- *
188
- * @param fn The function to execute once
189
- * @returns A function that will only execute the provided function once
190
- * @example
191
- * ```ts
192
- * const getValue = once(() => expensiveOperation())
193
- * getValue() // executes expensiveOperation
194
- * getValue() // returns cached result
195
- * ```
196
- */
197
- function once(fn) {
198
- let called = false;
199
- let result;
200
- return () => {
201
- if (!called) {
202
- result = fn();
203
- called = true;
204
- fn = void 0;
205
- }
206
- return result;
207
- };
208
- }
209
-
210
- //#endregion
211
- //#region src/sleep.ts
212
- /**
213
- * Sleep for a given number of milliseconds.
214
- */
215
- function sleep(ms) {
216
- return new Promise((resolve) => setTimeout(resolve, ms));
217
- }
218
-
219
182
  //#endregion
220
183
  //#region src/is-deep-equal.ts
221
184
  /**
@@ -260,5 +223,184 @@ function isDeepEqual(a, b) {
260
223
  }
261
224
 
262
225
  //#endregion
263
- export { DefaultMap, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, once, sleep };
226
+ //#region src/map-group-by.ts
227
+ /**
228
+ * @internal
229
+ */
230
+ function mapGroupByPolyfill(items, keySelector) {
231
+ const map = /* @__PURE__ */ new Map();
232
+ let index = 0;
233
+ for (const item of items) {
234
+ const key = keySelector(item, index);
235
+ const group = map.get(key);
236
+ if (group) group.push(item);
237
+ else map.set(key, [item]);
238
+ index++;
239
+ }
240
+ return map;
241
+ }
242
+ /**
243
+ * @internal
244
+ */
245
+ function mapGroupByNative(items, keySelector) {
246
+ return Map.groupBy(items, keySelector);
247
+ }
248
+ /**
249
+ * A polyfill for the `Map.groupBy` static method.
250
+ *
251
+ * @public
252
+ */
253
+ const mapGroupBy = !!Map.groupBy ? mapGroupByNative : mapGroupByPolyfill;
254
+
255
+ //#endregion
256
+ //#region src/map-values.ts
257
+ /**
258
+ * Creates a new object with the same keys as the input object, but with values
259
+ * transformed by the provided callback function. Similar to `Array.prototype.map()`
260
+ * but for object values.
261
+
262
+ * @param object - The object whose values will be transformed.
263
+ * @param callback - A function that transforms each value. Receives the value and
264
+ * its corresponding key as arguments.
265
+ * @returns A new object with the same keys but transformed values.
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * const prices = { apple: 1, banana: 2, orange: 3 }
270
+ * const doubled = mapValues(prices, (price) => price * 2)
271
+ * // Result: { apple: 2, banana: 4, orange: 6 }
272
+ * ```
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * const users = { john: 25, jane: 30, bob: 35 }
277
+ * const greetings = mapValues(users, (age, name) => `${name} is ${age} years old`)
278
+ * // Result: { john: 'john is 25 years old', jane: 'jane is 30 years old', bob: 'bob is 35 years old' }
279
+ * ```
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * const data = { a: '1', b: '2', c: '3' }
284
+ * const numbers = mapValues(data, (str) => parseInt(str, 10))
285
+ * // Result: { a: 1, b: 2, c: 3 }
286
+ * ```
287
+ *
288
+ * @public
289
+ */
290
+ function mapValues(object, callback) {
291
+ let result = {};
292
+ for (const [key, value] of Object.entries(object)) result[key] = callback(value, key);
293
+ return result;
294
+ }
295
+
296
+ //#endregion
297
+ //#region src/object-entries.ts
298
+ /**
299
+ * A type-safe wrapper around `Object.entries()` that preserves the exact types of object keys
300
+ * and values. Unlike the standard `Object.entries()` which returns `[string, any][]`, this
301
+ * function returns an array of tuples where each tuple is precisely typed according to the
302
+ * input object's structure.
303
+ *
304
+ * This is particularly useful when working with objects that have known, fixed property types
305
+ * and you want to maintain type safety when iterating over entries.
306
+ *
307
+ * @example
308
+ * ```typescript
309
+ * const myObject = { a: 1, b: 'hello', c: true } as const
310
+ * const entries = objectEntries(myObject)
311
+ * // Type: (["a", 1] | ["b", "hello"] | ["c", true])[]
312
+ *
313
+ * for (const [key, value] of entries) {
314
+ * // key is typed as "a" | "b" | "c"
315
+ * // value is typed as 1 | "hello" | true
316
+ * console.log(`${key}: ${value}`)
317
+ * }
318
+ * ```
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * interface User {
323
+ * name: string
324
+ * age: number
325
+ * active: boolean
326
+ * }
327
+ *
328
+ * const user: User = { name: 'Alice', age: 30, active: true }
329
+ * const entries = objectEntries(user)
330
+ * // Type: (["name", string] | ["age", number] | ["active", boolean])[]
331
+ * ```
332
+ *
333
+ * @public
334
+ */
335
+ const objectEntries = Object.entries;
336
+
337
+ //#endregion
338
+ //#region src/object-group-by.ts
339
+ /**
340
+ * @internal
341
+ */
342
+ function objectGroupByPolyfill(items, keySelector) {
343
+ const result = {};
344
+ let index = 0;
345
+ for (const item of items) {
346
+ const key = keySelector(item, index);
347
+ const group = result[key];
348
+ if (group) group.push(item);
349
+ else result[key] = [item];
350
+ index++;
351
+ }
352
+ return result;
353
+ }
354
+ /**
355
+ * @internal
356
+ */
357
+ function objectGroupByNative(items, keySelector) {
358
+ return Object.groupBy(items, keySelector);
359
+ }
360
+ /**
361
+ * A polyfill for the `Object.groupBy` static method.
362
+ *
363
+ * @public
364
+ */
365
+ const objectGroupBy = !!Object.groupBy ? objectGroupByNative : objectGroupByPolyfill;
366
+
367
+ //#endregion
368
+ //#region src/once.ts
369
+ /**
370
+ * Creates a function that will only execute the provided function once.
371
+ * Subsequent calls will return the cached result from the first execution.
372
+ *
373
+ * @param fn The function to execute once
374
+ * @returns A function that will only execute the provided function once
375
+ * @example
376
+ * ```ts
377
+ * const getValue = once(() => expensiveOperation())
378
+ * getValue() // executes expensiveOperation
379
+ * getValue() // returns cached result
380
+ * ```
381
+ */
382
+ function once(fn) {
383
+ let called = false;
384
+ let result;
385
+ return () => {
386
+ if (!called) {
387
+ result = fn();
388
+ called = true;
389
+ fn = void 0;
390
+ }
391
+ return result;
392
+ };
393
+ }
394
+
395
+ //#endregion
396
+ //#region src/sleep.ts
397
+ /**
398
+ * Sleep for a given number of milliseconds.
399
+ */
400
+ function sleep(ms) {
401
+ return new Promise((resolve) => setTimeout(resolve, ms));
402
+ }
403
+
404
+ //#endregion
405
+ export { DefaultMap, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, mapGroupBy, mapValues, objectEntries, objectGroupBy, once, sleep };
264
406
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["NodeType.ELEMENT_NODE","NodeType.TEXT_NODE","NodeType.DOCUMENT_NODE","NodeType.DOCUMENT_FRAGMENT_NODE","result: T"],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom-node-type.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/once.ts","../src/sleep.ts","../src/is-deep-equal.ts"],"sourcesContent":["/**\n * Checks if the given value is an object.\n */\nexport function isObject(\n value: unknown,\n): value is Record<string | symbol | number, unknown> {\n return value != null && typeof value === 'object'\n}\n\n/**\n * Checks if the given value is a Map.\n */\nexport function isMap(value: unknown): value is Map<unknown, unknown> {\n return value instanceof Map\n}\n\n/**\n * Checks if the given value is a Set.\n */\nexport function isSet(value: unknown): value is Set<unknown> {\n return value instanceof Set\n}\n","/**\n * A map that automatically creates values for missing keys using a factory function.\n *\n * Similar to Python's [defaultdict](https://docs.python.org/3.13/library/collections.html#collections.defaultdict).\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n private readonly defaultFactory: () => V\n\n constructor(defaultFactory: () => V, iterable?: Iterable<readonly [K, V]>) {\n super(iterable)\n this.defaultFactory = defaultFactory\n }\n\n override get(key: K): V {\n if (this.has(key)) {\n return super.get(key)!\n }\n const value = this.defaultFactory()\n this.set(key, value)\n return value\n }\n}\n","// prettier-ignore\nexport const ELEMENT_NODE = 1 satisfies typeof Node.ELEMENT_NODE;\n// prettier-ignore\nexport const ATTRIBUTE_NODE = 2 satisfies typeof Node.ATTRIBUTE_NODE;\n// prettier-ignore\nexport const TEXT_NODE = 3 satisfies typeof Node.TEXT_NODE;\n// prettier-ignore\nexport const CDATA_SECTION_NODE = 4 satisfies typeof Node.CDATA_SECTION_NODE;\n// prettier-ignore\nexport const ENTITY_REFERENCE_NODE = 5 satisfies typeof Node.ENTITY_REFERENCE_NODE;\n// prettier-ignore\nexport const ENTITY_NODE = 6 satisfies typeof Node.ENTITY_NODE;\n// prettier-ignore\nexport const PROCESSING_INSTRUCTION_NODE = 7 satisfies typeof Node.PROCESSING_INSTRUCTION_NODE;\n// prettier-ignore\nexport const COMMENT_NODE = 8 satisfies typeof Node.COMMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_NODE = 9 satisfies typeof Node.DOCUMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_TYPE_NODE = 10 satisfies typeof Node.DOCUMENT_TYPE_NODE;\n// prettier-ignore\nexport const DOCUMENT_FRAGMENT_NODE = 11 satisfies typeof Node.DOCUMENT_FRAGMENT_NODE;\n// prettier-ignore\nexport const NOTATION_NODE = 12 satisfies typeof Node.NOTATION_NODE;\n","import { isObject } from './checker'\nimport * as NodeType from './dom-node-type'\n\n/**\n * Checks if the given DOM node is an Element.\n */\nexport function isElement(node: Node): node is Element {\n return node.nodeType === NodeType.ELEMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node.nodeType === NodeType.TEXT_NODE\n}\n\n/**\n * Checks if the given DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/1999/xhtml'\n}\n\n/**\n * Checks if the given DOM node is an SVGElement.\n */\nexport function isSVGElement(node: Node): node is SVGElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/2000/svg'\n}\n\n/**\n * Checks if the given DOM node is an MathMLElement.\n */\nexport function isMathMLElement(node: Node): node is MathMLElement {\n return (\n isElement(node) &&\n node.namespaceURI === 'http://www.w3.org/1998/Math/MathML'\n )\n}\n\n/**\n * Checks if the given DOM node is a Document.\n */\nexport function isDocument(node: Node): node is Document {\n return node.nodeType === NodeType.DOCUMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a DocumentFragment.\n */\nexport function isDocumentFragment(node: Node): node is DocumentFragment {\n return node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a ShadowRoot.\n */\nexport function isShadowRoot(node: Node): node is ShadowRoot {\n return isDocumentFragment(node) && 'host' in node && isElementLike(node.host)\n}\n\n/**\n * Checks if an unknown value is likely a DOM node.\n */\nexport function isNodeLike(value: unknown): value is Node {\n return isObject(value) && value.nodeType !== undefined\n}\n\n/**\n * Checks if an unknown value is likely a DOM element.\n */\nexport function isElementLike(value: unknown): value is Element {\n return (\n isObject(value) &&\n value.nodeType === NodeType.ELEMENT_NODE &&\n typeof value.nodeName === 'string'\n )\n}\n\n/**\n * Checks if the given value is likely a Window object.\n */\nexport function isWindowLike(value: unknown): value is Window {\n return isObject(value) && value.window === value\n}\n\n/**\n * Gets the window object for the given target or the global window object if no\n * target is provided.\n */\nexport function getWindow(\n target?: Node | ShadowRoot | Document | null,\n): Window & typeof globalThis {\n if (target) {\n if (isShadowRoot(target)) {\n return getWindow(target.host)\n }\n if (isDocument(target)) {\n return target.defaultView || window\n }\n if (isElement(target)) {\n return target.ownerDocument?.defaultView || window\n }\n }\n return window\n}\n\n/**\n * Gets the document for the given target or the global document if no target is\n * provided.\n */\nexport function getDocument(\n target?: Element | Window | Node | Document | null,\n): Document {\n if (target) {\n if (isWindowLike(target)) {\n return target.document\n }\n if (isDocument(target)) {\n return target\n }\n return target.ownerDocument || document\n }\n return document\n}\n\n/**\n * Gets a reference to the root node of the document based on the given target.\n */\nexport function getDocumentElement(\n target?: Element | Node | Window | Document | null,\n): HTMLElement {\n return getDocument(target).documentElement\n}\n","/**\n * Formats a number of bytes into a human-readable string.\n * @param bytes - The number of bytes to format.\n * @returns A string representing the number of bytes in a human-readable format.\n */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB']\n let unitIndex = 0\n let num = bytes\n while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {\n num /= 1024\n unitIndex++\n }\n const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1\n return `${num.toFixed(fraction)}${units[unitIndex]}`\n}\n","let id = 0\n\n/**\n * Generates a unique positive integer.\n */\nexport function getId(): number {\n id = (id % Number.MAX_SAFE_INTEGER) + 1\n return id\n}\n","/**\n * Creates a function that will only execute the provided function once.\n * Subsequent calls will return the cached result from the first execution.\n *\n * @param fn The function to execute once\n * @returns A function that will only execute the provided function once\n * @example\n * ```ts\n * const getValue = once(() => expensiveOperation())\n * getValue() // executes expensiveOperation\n * getValue() // returns cached result\n * ```\n */\nexport function once<T>(fn: () => T): () => T {\n let called = false\n let result: T\n return () => {\n if (!called) {\n result = fn()\n called = true\n // @ts-expect-error - micro-optimization for memory management\n fn = undefined\n }\n return result\n }\n}\n","/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n","import { isMap, isSet } from './checker'\n\n/**\n * Whether two values are deeply equal.\n */\nexport function isDeepEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true\n }\n\n // Handle null and undefined early\n if (a == null || b == null) {\n return false\n }\n\n const aType = typeof a\n const bType = typeof b\n if (aType !== bType) {\n return false\n }\n\n if (aType === 'number' && Number.isNaN(a) && Number.isNaN(b)) {\n return true\n }\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) {\n return false\n }\n if (a.length !== b.length) {\n return false\n }\n const size = a.length\n for (let i = 0; i < size; i++) {\n if (!isDeepEqual(a[i], b[i])) {\n return false\n }\n }\n return true\n }\n\n if (isSet(a)) {\n if (!isSet(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const value of a) {\n if (!b.has(value)) {\n return false\n }\n }\n return true\n }\n\n if (isMap(a)) {\n if (!isMap(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const key of a.keys()) {\n if (!b.has(key)) {\n return false\n }\n if (!isDeepEqual(a.get(key), b.get(key))) {\n return false\n }\n }\n return true\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) {\n return false\n }\n for (const key of aKeys) {\n if (\n !isDeepEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false\n }\n }\n return true\n }\n\n return false\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["NodeType.ELEMENT_NODE","NodeType.TEXT_NODE","NodeType.DOCUMENT_NODE","NodeType.DOCUMENT_FRAGMENT_NODE","mapGroupBy: <K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Map<K, T[]>","objectEntries: <T extends Record<string, any>>(\n obj: T,\n) => ObjectEntries<T>[]","result: Partial<Record<K, T[]>>","objectGroupBy: <K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Partial<Record<K, T[]>>","result: T"],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom-node-type.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/is-deep-equal.ts","../src/map-group-by.ts","../src/map-values.ts","../src/object-entries.ts","../src/object-group-by.ts","../src/once.ts","../src/sleep.ts"],"sourcesContent":["/**\n * Checks if the given value is an object.\n */\nexport function isObject(\n value: unknown,\n): value is Record<string | symbol | number, unknown> {\n return value != null && typeof value === 'object'\n}\n\n/**\n * Checks if the given value is a Map.\n */\nexport function isMap(value: unknown): value is Map<unknown, unknown> {\n return value instanceof Map\n}\n\n/**\n * Checks if the given value is a Set.\n */\nexport function isSet(value: unknown): value is Set<unknown> {\n return value instanceof Set\n}\n","/**\n * A map that automatically creates values for missing keys using a factory function.\n *\n * Similar to Python's [defaultdict](https://docs.python.org/3.13/library/collections.html#collections.defaultdict).\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n private readonly defaultFactory: () => V\n\n constructor(defaultFactory: () => V, iterable?: Iterable<readonly [K, V]>) {\n super(iterable)\n this.defaultFactory = defaultFactory\n }\n\n override get(key: K): V {\n if (this.has(key)) {\n return super.get(key)!\n }\n const value = this.defaultFactory()\n this.set(key, value)\n return value\n }\n}\n","// prettier-ignore\nexport const ELEMENT_NODE = 1 satisfies typeof Node.ELEMENT_NODE;\n// prettier-ignore\nexport const ATTRIBUTE_NODE = 2 satisfies typeof Node.ATTRIBUTE_NODE;\n// prettier-ignore\nexport const TEXT_NODE = 3 satisfies typeof Node.TEXT_NODE;\n// prettier-ignore\nexport const CDATA_SECTION_NODE = 4 satisfies typeof Node.CDATA_SECTION_NODE;\n// prettier-ignore\nexport const ENTITY_REFERENCE_NODE = 5 satisfies typeof Node.ENTITY_REFERENCE_NODE;\n// prettier-ignore\nexport const ENTITY_NODE = 6 satisfies typeof Node.ENTITY_NODE;\n// prettier-ignore\nexport const PROCESSING_INSTRUCTION_NODE = 7 satisfies typeof Node.PROCESSING_INSTRUCTION_NODE;\n// prettier-ignore\nexport const COMMENT_NODE = 8 satisfies typeof Node.COMMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_NODE = 9 satisfies typeof Node.DOCUMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_TYPE_NODE = 10 satisfies typeof Node.DOCUMENT_TYPE_NODE;\n// prettier-ignore\nexport const DOCUMENT_FRAGMENT_NODE = 11 satisfies typeof Node.DOCUMENT_FRAGMENT_NODE;\n// prettier-ignore\nexport const NOTATION_NODE = 12 satisfies typeof Node.NOTATION_NODE;\n","import { isObject } from './checker'\nimport * as NodeType from './dom-node-type'\n\n/**\n * Checks if the given DOM node is an Element.\n */\nexport function isElement(node: Node): node is Element {\n return node.nodeType === NodeType.ELEMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node.nodeType === NodeType.TEXT_NODE\n}\n\n/**\n * Checks if the given DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/1999/xhtml'\n}\n\n/**\n * Checks if the given DOM node is an SVGElement.\n */\nexport function isSVGElement(node: Node): node is SVGElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/2000/svg'\n}\n\n/**\n * Checks if the given DOM node is an MathMLElement.\n */\nexport function isMathMLElement(node: Node): node is MathMLElement {\n return (\n isElement(node) &&\n node.namespaceURI === 'http://www.w3.org/1998/Math/MathML'\n )\n}\n\n/**\n * Checks if the given DOM node is a Document.\n */\nexport function isDocument(node: Node): node is Document {\n return node.nodeType === NodeType.DOCUMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a DocumentFragment.\n */\nexport function isDocumentFragment(node: Node): node is DocumentFragment {\n return node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a ShadowRoot.\n */\nexport function isShadowRoot(node: Node): node is ShadowRoot {\n return isDocumentFragment(node) && 'host' in node && isElementLike(node.host)\n}\n\n/**\n * Checks if an unknown value is likely a DOM node.\n */\nexport function isNodeLike(value: unknown): value is Node {\n return isObject(value) && value.nodeType !== undefined\n}\n\n/**\n * Checks if an unknown value is likely a DOM element.\n */\nexport function isElementLike(value: unknown): value is Element {\n return (\n isObject(value) &&\n value.nodeType === NodeType.ELEMENT_NODE &&\n typeof value.nodeName === 'string'\n )\n}\n\n/**\n * Checks if the given value is likely a Window object.\n */\nexport function isWindowLike(value: unknown): value is Window {\n return isObject(value) && value.window === value\n}\n\n/**\n * Gets the window object for the given target or the global window object if no\n * target is provided.\n */\nexport function getWindow(\n target?: Node | ShadowRoot | Document | null,\n): Window & typeof globalThis {\n if (target) {\n if (isShadowRoot(target)) {\n return getWindow(target.host)\n }\n if (isDocument(target)) {\n return target.defaultView || window\n }\n if (isElement(target)) {\n return target.ownerDocument?.defaultView || window\n }\n }\n return window\n}\n\n/**\n * Gets the document for the given target or the global document if no target is\n * provided.\n */\nexport function getDocument(\n target?: Element | Window | Node | Document | null,\n): Document {\n if (target) {\n if (isWindowLike(target)) {\n return target.document\n }\n if (isDocument(target)) {\n return target\n }\n return target.ownerDocument || document\n }\n return document\n}\n\n/**\n * Gets a reference to the root node of the document based on the given target.\n */\nexport function getDocumentElement(\n target?: Element | Node | Window | Document | null,\n): HTMLElement {\n return getDocument(target).documentElement\n}\n","/**\n * Formats a number of bytes into a human-readable string.\n * @param bytes - The number of bytes to format.\n * @returns A string representing the number of bytes in a human-readable format.\n */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB']\n let unitIndex = 0\n let num = bytes\n while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {\n num /= 1024\n unitIndex++\n }\n const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1\n return `${num.toFixed(fraction)}${units[unitIndex]}`\n}\n","let id = 0\n\n/**\n * Generates a unique positive integer.\n */\nexport function getId(): number {\n id = (id % Number.MAX_SAFE_INTEGER) + 1\n return id\n}\n","import { isMap, isSet } from './checker'\n\n/**\n * Whether two values are deeply equal.\n */\nexport function isDeepEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true\n }\n\n // Handle null and undefined early\n if (a == null || b == null) {\n return false\n }\n\n const aType = typeof a\n const bType = typeof b\n if (aType !== bType) {\n return false\n }\n\n if (aType === 'number' && Number.isNaN(a) && Number.isNaN(b)) {\n return true\n }\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) {\n return false\n }\n if (a.length !== b.length) {\n return false\n }\n const size = a.length\n for (let i = 0; i < size; i++) {\n if (!isDeepEqual(a[i], b[i])) {\n return false\n }\n }\n return true\n }\n\n if (isSet(a)) {\n if (!isSet(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const value of a) {\n if (!b.has(value)) {\n return false\n }\n }\n return true\n }\n\n if (isMap(a)) {\n if (!isMap(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const key of a.keys()) {\n if (!b.has(key)) {\n return false\n }\n if (!isDeepEqual(a.get(key), b.get(key))) {\n return false\n }\n }\n return true\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) {\n return false\n }\n for (const key of aKeys) {\n if (\n !isDeepEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false\n }\n }\n return true\n }\n\n return false\n}\n","/**\n * @internal\n */\nexport function mapGroupByPolyfill<K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Map<K, T[]> {\n const map = new Map<K, T[]>()\n let index = 0\n for (const item of items) {\n const key = keySelector(item, index)\n const group = map.get(key)\n if (group) {\n group.push(item)\n } else {\n map.set(key, [item])\n }\n index++\n }\n return map\n}\n\n/**\n * @internal\n */\nexport function mapGroupByNative<K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Map<K, T[]> {\n return Map.groupBy(items, keySelector)\n}\n\n/**\n * A polyfill for the `Map.groupBy` static method.\n *\n * @public\n */\nexport const mapGroupBy: <K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Map<K, T[]> = !!Map.groupBy ? mapGroupByNative : mapGroupByPolyfill\n","/**\n * Creates a new object with the same keys as the input object, but with values\n * transformed by the provided callback function. Similar to `Array.prototype.map()`\n * but for object values.\n\n * @param object - The object whose values will be transformed.\n * @param callback - A function that transforms each value. Receives the value and\n * its corresponding key as arguments.\n * @returns A new object with the same keys but transformed values.\n *\n * @example\n * ```typescript\n * const prices = { apple: 1, banana: 2, orange: 3 }\n * const doubled = mapValues(prices, (price) => price * 2)\n * // Result: { apple: 2, banana: 4, orange: 6 }\n * ```\n *\n * @example\n * ```typescript\n * const users = { john: 25, jane: 30, bob: 35 }\n * const greetings = mapValues(users, (age, name) => `${name} is ${age} years old`)\n * // Result: { john: 'john is 25 years old', jane: 'jane is 30 years old', bob: 'bob is 35 years old' }\n * ```\n *\n * @example\n * ```typescript\n * const data = { a: '1', b: '2', c: '3' }\n * const numbers = mapValues(data, (str) => parseInt(str, 10))\n * // Result: { a: 1, b: 2, c: 3 }\n * ```\n *\n * @public\n */\nexport function mapValues<ValueIn, ValueOut>(\n object: Record<string, ValueIn>,\n callback: (value: ValueIn, key: string) => ValueOut,\n): Record<string, ValueOut> {\n let result = {} as Record<string, ValueOut>\n for (const [key, value] of Object.entries(object)) {\n result[key] = callback(value, key)\n }\n return result\n}\n","/**\n * A TypeScript utility type that represents the entries of an object as a union of tuple types.\n * Each tuple contains a key-value pair where the key and value types are precisely typed\n * according to the input object type.\n *\n * @example\n * ```typescript\n * type MyObject = { a: 1; b: 'B' }\n * type MyEntries = ObjectEntries<MyObject>\n * // ^ [\"a\", 1] | [\"b\", \"B\"]\n * ```\n *\n * @public\n */\nexport type ObjectEntries<T extends Record<string, any>> = {\n [K in keyof T]: [K, T[K]]\n}[keyof T]\n\n/**\n * A type-safe wrapper around `Object.entries()` that preserves the exact types of object keys\n * and values. Unlike the standard `Object.entries()` which returns `[string, any][]`, this\n * function returns an array of tuples where each tuple is precisely typed according to the\n * input object's structure.\n *\n * This is particularly useful when working with objects that have known, fixed property types\n * and you want to maintain type safety when iterating over entries.\n *\n * @example\n * ```typescript\n * const myObject = { a: 1, b: 'hello', c: true } as const\n * const entries = objectEntries(myObject)\n * // Type: ([\"a\", 1] | [\"b\", \"hello\"] | [\"c\", true])[]\n *\n * for (const [key, value] of entries) {\n * // key is typed as \"a\" | \"b\" | \"c\"\n * // value is typed as 1 | \"hello\" | true\n * console.log(`${key}: ${value}`)\n * }\n * ```\n *\n * @example\n * ```typescript\n * interface User {\n * name: string\n * age: number\n * active: boolean\n * }\n *\n * const user: User = { name: 'Alice', age: 30, active: true }\n * const entries = objectEntries(user)\n * // Type: ([\"name\", string] | [\"age\", number] | [\"active\", boolean])[]\n * ```\n *\n * @public\n */\nexport const objectEntries: <T extends Record<string, any>>(\n obj: T,\n) => ObjectEntries<T>[] = Object.entries\n","/**\n * @internal\n */\nexport function objectGroupByPolyfill<K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Partial<Record<K, T[]>> {\n const result: Partial<Record<K, T[]>> = {}\n let index = 0\n for (const item of items) {\n const key = keySelector(item, index)\n const group = result[key]\n if (group) {\n group.push(item)\n } else {\n result[key] = [item]\n }\n index++\n }\n return result\n}\n\n/**\n * @internal\n */\nexport function objectGroupByNative<K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Partial<Record<K, T[]>> {\n return Object.groupBy(items, keySelector)\n}\n\n/**\n * A polyfill for the `Object.groupBy` static method.\n *\n * @public\n */\nexport const objectGroupBy: <K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Partial<Record<K, T[]>> = !!Object.groupBy\n ? objectGroupByNative\n : objectGroupByPolyfill\n","/**\n * Creates a function that will only execute the provided function once.\n * Subsequent calls will return the cached result from the first execution.\n *\n * @param fn The function to execute once\n * @returns A function that will only execute the provided function once\n * @example\n * ```ts\n * const getValue = once(() => expensiveOperation())\n * getValue() // executes expensiveOperation\n * getValue() // returns cached result\n * ```\n */\nexport function once<T>(fn: () => T): () => T {\n let called = false\n let result: T\n return () => {\n if (!called) {\n result = fn()\n called = true\n // @ts-expect-error - micro-optimization for memory management\n fn = undefined\n }\n return result\n }\n}\n","/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ocavue/utils",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.2.0",
5
5
  "description": "A collection of utility functions for the browser and other environments",
6
6
  "author": "ocavue <ocavue@gmail.com>",
7
7
  "license": "MIT",
@@ -55,16 +55,16 @@
55
55
  "publishConfig": {
56
56
  "access": "public"
57
57
  },
58
- "size-limit": [
59
- {
60
- "path": "dist/index.js"
61
- }
62
- ],
63
58
  "renovate": {
64
59
  "extends": [
65
60
  "github>ocavue/config-renovate"
66
61
  ]
67
62
  },
63
+ "size-limit": [
64
+ {
65
+ "path": "dist/index.js"
66
+ }
67
+ ],
68
68
  "scripts": {
69
69
  "build": "tsdown",
70
70
  "dev": "tsdown --watch",
@@ -144,10 +144,13 @@ describe('DefaultMap', () => {
144
144
  })
145
145
 
146
146
  it('works with iteration methods', () => {
147
- const map = new DefaultMap<string, number>(() => 0, [
148
- ['a', 1],
149
- ['b', 2],
150
- ])
147
+ const map = new DefaultMap<string, number>(
148
+ () => 0,
149
+ [
150
+ ['a', 1],
151
+ ['b', 2],
152
+ ],
153
+ )
151
154
 
152
155
  expect([...map.keys()]).toEqual(['a', 'b'])
153
156
  expect([...map.values()]).toEqual([1, 2])
package/src/index.ts CHANGED
@@ -3,6 +3,10 @@ export { DefaultMap } from './default-map'
3
3
  export * from './dom'
4
4
  export { formatBytes } from './format-bytes'
5
5
  export { getId } from './get-id'
6
+ export { isDeepEqual } from './is-deep-equal'
7
+ export { mapGroupBy } from './map-group-by'
8
+ export { mapValues } from './map-values'
9
+ export { objectEntries, type ObjectEntries } from './object-entries'
10
+ export { objectGroupBy } from './object-group-by'
6
11
  export { once } from './once'
7
12
  export { sleep } from './sleep'
8
- export { isDeepEqual } from './is-deep-equal'
@@ -59,8 +59,30 @@ describe('isDeepEqual', () => {
59
59
  })
60
60
 
61
61
  it('handles nested arrays', () => {
62
- expect(isDeepEqual([[1, 2], [3, 4]], [[1, 2], [3, 4]])).toBe(true)
63
- expect(isDeepEqual([[1, 2], [3, 4]], [[1, 2], [3, 5]])).toBe(false)
62
+ expect(
63
+ isDeepEqual(
64
+ [
65
+ [1, 2],
66
+ [3, 4],
67
+ ],
68
+ [
69
+ [1, 2],
70
+ [3, 4],
71
+ ],
72
+ ),
73
+ ).toBe(true)
74
+ expect(
75
+ isDeepEqual(
76
+ [
77
+ [1, 2],
78
+ [3, 4],
79
+ ],
80
+ [
81
+ [1, 2],
82
+ [3, 5],
83
+ ],
84
+ ),
85
+ ).toBe(false)
64
86
  })
65
87
  })
66
88
 
@@ -105,9 +127,7 @@ describe('isDeepEqual', () => {
105
127
 
106
128
  it('returns false for sets with different values', () => {
107
129
  expect(isDeepEqual(new Set([1, 2, 3]), new Set([1, 2, 4]))).toBe(false)
108
- expect(isDeepEqual(new Set(['a', 'b']), new Set(['a', 'c']))).toBe(
109
- false,
110
- )
130
+ expect(isDeepEqual(new Set(['a', 'b']), new Set(['a', 'c']))).toBe(false)
111
131
  })
112
132
 
113
133
  it('handles set order independence', () => {
@@ -145,44 +165,28 @@ describe('isDeepEqual', () => {
145
165
  })
146
166
 
147
167
  it('returns false for maps with different keys', () => {
148
- expect(
149
- isDeepEqual(new Map([['a', 1]]), new Map([['b', 1]])),
150
- ).toBe(false)
168
+ expect(isDeepEqual(new Map([['a', 1]]), new Map([['b', 1]]))).toBe(false)
151
169
  })
152
170
 
153
171
  it('returns false for maps with different values', () => {
154
- expect(
155
- isDeepEqual(new Map([['a', 1]]), new Map([['a', 2]])),
156
- ).toBe(false)
172
+ expect(isDeepEqual(new Map([['a', 1]]), new Map([['a', 2]]))).toBe(false)
157
173
  })
158
174
 
159
175
  it('handles nested values in maps', () => {
160
176
  expect(
161
- isDeepEqual(
162
- new Map([['a', { b: 1 }]]),
163
- new Map([['a', { b: 1 }]]),
164
- ),
177
+ isDeepEqual(new Map([['a', { b: 1 }]]), new Map([['a', { b: 1 }]])),
165
178
  ).toBe(true)
166
179
  expect(
167
- isDeepEqual(
168
- new Map([['a', { b: 1 }]]),
169
- new Map([['a', { b: 2 }]]),
170
- ),
180
+ isDeepEqual(new Map([['a', { b: 1 }]]), new Map([['a', { b: 2 }]])),
171
181
  ).toBe(false)
172
182
  })
173
183
 
174
184
  it('handles arrays as map values', () => {
175
185
  expect(
176
- isDeepEqual(
177
- new Map([['a', [1, 2, 3]]]),
178
- new Map([['a', [1, 2, 3]]]),
179
- ),
186
+ isDeepEqual(new Map([['a', [1, 2, 3]]]), new Map([['a', [1, 2, 3]]])),
180
187
  ).toBe(true)
181
188
  expect(
182
- isDeepEqual(
183
- new Map([['a', [1, 2, 3]]]),
184
- new Map([['a', [1, 2, 4]]]),
185
- ),
189
+ isDeepEqual(new Map([['a', [1, 2, 3]]]), new Map([['a', [1, 2, 4]]])),
186
190
  ).toBe(false)
187
191
  })
188
192
  })
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import {
4
+ mapGroupBy,
5
+ mapGroupByPolyfill,
6
+ mapGroupByNative,
7
+ } from './map-group-by'
8
+
9
+ const testCases = [
10
+ { name: 'mapGroupBy', fn: mapGroupBy },
11
+ { name: 'mapGroupByPolyfill', fn: mapGroupByPolyfill },
12
+ ]
13
+
14
+ if (!!Map.groupBy) {
15
+ testCases.push({ name: 'mapGroupByNative', fn: mapGroupByNative })
16
+ }
17
+
18
+ describe.each(testCases)('$name', ({ fn }) => {
19
+ it('groups items by key', () => {
20
+ const items = [1, 2, 3, 4, 5, 6]
21
+ const result = fn(items, (item) => item % 2)
22
+
23
+ expect(result.get(0)).toEqual([2, 4, 6])
24
+ expect(result.get(1)).toEqual([1, 3, 5])
25
+ })
26
+
27
+ it('works with string keys', () => {
28
+ const items = ['apple', 'banana', 'apricot', 'blueberry']
29
+ const result = fn(items, (item) => item[0])
30
+
31
+ expect(result.get('a')).toEqual(['apple', 'apricot'])
32
+ expect(result.get('b')).toEqual(['banana', 'blueberry'])
33
+ })
34
+
35
+ it('works with object keys', () => {
36
+ const keyA = { type: 'A' }
37
+ const keyB = { type: 'B' }
38
+ const items = [1, 2, 3, 4]
39
+ const result = fn(items, (item) => (item % 2 === 0 ? keyA : keyB))
40
+
41
+ expect(result.get(keyA)).toEqual([2, 4])
42
+ expect(result.get(keyB)).toEqual([1, 3])
43
+ })
44
+
45
+ it('passes index to key selector', () => {
46
+ const items = ['a', 'b', 'c', 'd']
47
+ const result = fn(items, (_, index) => Math.floor(index / 2))
48
+
49
+ expect(result.get(0)).toEqual(['a', 'b'])
50
+ expect(result.get(1)).toEqual(['c', 'd'])
51
+ })
52
+
53
+ it('handles empty array', () => {
54
+ const result = fn([], (item) => item)
55
+ expect(result.size).toBe(0)
56
+ })
57
+
58
+ it('handles single item', () => {
59
+ const result = fn([42], (item) => item)
60
+ expect(result.get(42)).toEqual([42])
61
+ })
62
+
63
+ it('handles all items mapping to same key', () => {
64
+ const items = [1, 2, 3, 4]
65
+ const result = fn(items, () => 'same')
66
+
67
+ expect(result.size).toBe(1)
68
+ expect(result.get('same')).toEqual([1, 2, 3, 4])
69
+ })
70
+
71
+ it('works with iterables other than arrays', () => {
72
+ const set = new Set([1, 2, 3, 4, 5])
73
+ const result = fn(set, (item) => item % 2)
74
+
75
+ expect(result.get(0)).toEqual([2, 4])
76
+ expect(result.get(1)).toEqual([1, 3, 5])
77
+ })
78
+ })
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @internal
3
+ */
4
+ export function mapGroupByPolyfill<K, T>(
5
+ items: Iterable<T>,
6
+ keySelector: (item: T, index: number) => K,
7
+ ): Map<K, T[]> {
8
+ const map = new Map<K, T[]>()
9
+ let index = 0
10
+ for (const item of items) {
11
+ const key = keySelector(item, index)
12
+ const group = map.get(key)
13
+ if (group) {
14
+ group.push(item)
15
+ } else {
16
+ map.set(key, [item])
17
+ }
18
+ index++
19
+ }
20
+ return map
21
+ }
22
+
23
+ /**
24
+ * @internal
25
+ */
26
+ export function mapGroupByNative<K, T>(
27
+ items: Iterable<T>,
28
+ keySelector: (item: T, index: number) => K,
29
+ ): Map<K, T[]> {
30
+ return Map.groupBy(items, keySelector)
31
+ }
32
+
33
+ /**
34
+ * A polyfill for the `Map.groupBy` static method.
35
+ *
36
+ * @public
37
+ */
38
+ export const mapGroupBy: <K, T>(
39
+ items: Iterable<T>,
40
+ keySelector: (item: T, index: number) => K,
41
+ ) => Map<K, T[]> = !!Map.groupBy ? mapGroupByNative : mapGroupByPolyfill
@@ -0,0 +1,41 @@
1
+ // @vitest-environment node
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import { mapValues } from './map-values'
6
+
7
+ describe('mapValues', () => {
8
+ it('transforms values with callback function', () => {
9
+ const prices = { apple: 1, banana: 2, orange: 3 }
10
+ const doubled = mapValues(prices, (price) => price * 2)
11
+
12
+ expect(doubled).toEqual({ apple: 2, banana: 4, orange: 6 })
13
+ })
14
+
15
+ it('provides key to callback function', () => {
16
+ const users = { john: 25, jane: 30 }
17
+ const greetings = mapValues(
18
+ users,
19
+ (age, name) => `${name} is ${age} years old`,
20
+ )
21
+
22
+ expect(greetings).toEqual({
23
+ john: 'john is 25 years old',
24
+ jane: 'jane is 30 years old',
25
+ })
26
+ })
27
+
28
+ it('handles empty objects', () => {
29
+ const empty = {}
30
+ const result = mapValues(empty, (value) => value)
31
+
32
+ expect(result).toEqual({})
33
+ })
34
+
35
+ it('transforms value types', () => {
36
+ const data = { a: '1', b: '2', c: '3' }
37
+ const numbers = mapValues(data, (str) => Number.parseInt(str, 10))
38
+
39
+ expect(numbers).toEqual({ a: 1, b: 2, c: 3 })
40
+ })
41
+ })
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Creates a new object with the same keys as the input object, but with values
3
+ * transformed by the provided callback function. Similar to `Array.prototype.map()`
4
+ * but for object values.
5
+
6
+ * @param object - The object whose values will be transformed.
7
+ * @param callback - A function that transforms each value. Receives the value and
8
+ * its corresponding key as arguments.
9
+ * @returns A new object with the same keys but transformed values.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const prices = { apple: 1, banana: 2, orange: 3 }
14
+ * const doubled = mapValues(prices, (price) => price * 2)
15
+ * // Result: { apple: 2, banana: 4, orange: 6 }
16
+ * ```
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const users = { john: 25, jane: 30, bob: 35 }
21
+ * const greetings = mapValues(users, (age, name) => `${name} is ${age} years old`)
22
+ * // Result: { john: 'john is 25 years old', jane: 'jane is 30 years old', bob: 'bob is 35 years old' }
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const data = { a: '1', b: '2', c: '3' }
28
+ * const numbers = mapValues(data, (str) => parseInt(str, 10))
29
+ * // Result: { a: 1, b: 2, c: 3 }
30
+ * ```
31
+ *
32
+ * @public
33
+ */
34
+ export function mapValues<ValueIn, ValueOut>(
35
+ object: Record<string, ValueIn>,
36
+ callback: (value: ValueIn, key: string) => ValueOut,
37
+ ): Record<string, ValueOut> {
38
+ let result = {} as Record<string, ValueOut>
39
+ for (const [key, value] of Object.entries(object)) {
40
+ result[key] = callback(value, key)
41
+ }
42
+ return result
43
+ }
@@ -0,0 +1,33 @@
1
+ // @vitest-environment node
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import { objectEntries } from './object-entries'
6
+
7
+ describe('objectEntries', () => {
8
+ it('returns entries for an empty object', () => {
9
+ const obj = {}
10
+ const entries = objectEntries(obj)
11
+ expect(entries).toEqual([])
12
+ })
13
+
14
+ it('returns entries for objects with mixed types', () => {
15
+ const obj = { name: 'Alice', age: 30, active: true }
16
+ const entries = objectEntries(obj)
17
+ expect(entries).toEqual([
18
+ ['name', 'Alice'],
19
+ ['age', 30],
20
+ ['active', true],
21
+ ])
22
+ })
23
+
24
+ it('preserves type safety with const objects', () => {
25
+ const obj = { a: 1, b: 'hello', c: true } as const
26
+ const entries = objectEntries(obj)
27
+
28
+ expect(entries).toHaveLength(3)
29
+ expect(entries).toContainEqual(['a', 1])
30
+ expect(entries).toContainEqual(['b', 'hello'])
31
+ expect(entries).toContainEqual(['c', true])
32
+ })
33
+ })
@@ -0,0 +1,58 @@
1
+ /**
2
+ * A TypeScript utility type that represents the entries of an object as a union of tuple types.
3
+ * Each tuple contains a key-value pair where the key and value types are precisely typed
4
+ * according to the input object type.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * type MyObject = { a: 1; b: 'B' }
9
+ * type MyEntries = ObjectEntries<MyObject>
10
+ * // ^ ["a", 1] | ["b", "B"]
11
+ * ```
12
+ *
13
+ * @public
14
+ */
15
+ export type ObjectEntries<T extends Record<string, any>> = {
16
+ [K in keyof T]: [K, T[K]]
17
+ }[keyof T]
18
+
19
+ /**
20
+ * A type-safe wrapper around `Object.entries()` that preserves the exact types of object keys
21
+ * and values. Unlike the standard `Object.entries()` which returns `[string, any][]`, this
22
+ * function returns an array of tuples where each tuple is precisely typed according to the
23
+ * input object's structure.
24
+ *
25
+ * This is particularly useful when working with objects that have known, fixed property types
26
+ * and you want to maintain type safety when iterating over entries.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const myObject = { a: 1, b: 'hello', c: true } as const
31
+ * const entries = objectEntries(myObject)
32
+ * // Type: (["a", 1] | ["b", "hello"] | ["c", true])[]
33
+ *
34
+ * for (const [key, value] of entries) {
35
+ * // key is typed as "a" | "b" | "c"
36
+ * // value is typed as 1 | "hello" | true
37
+ * console.log(`${key}: ${value}`)
38
+ * }
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * interface User {
44
+ * name: string
45
+ * age: number
46
+ * active: boolean
47
+ * }
48
+ *
49
+ * const user: User = { name: 'Alice', age: 30, active: true }
50
+ * const entries = objectEntries(user)
51
+ * // Type: (["name", string] | ["age", number] | ["active", boolean])[]
52
+ * ```
53
+ *
54
+ * @public
55
+ */
56
+ export const objectEntries: <T extends Record<string, any>>(
57
+ obj: T,
58
+ ) => ObjectEntries<T>[] = Object.entries
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import {
4
+ objectGroupBy,
5
+ objectGroupByPolyfill,
6
+ objectGroupByNative,
7
+ } from './object-group-by'
8
+
9
+ const testCases = [
10
+ { name: 'objectGroupBy', fn: objectGroupBy },
11
+ { name: 'objectGroupByPolyfill', fn: objectGroupByPolyfill },
12
+ ]
13
+
14
+ if (!!Object.groupBy) {
15
+ testCases.push({ name: 'objectGroupByNative', fn: objectGroupByNative })
16
+ }
17
+
18
+ describe.each(testCases)('$name', ({ fn }) => {
19
+ it('groups items by key', () => {
20
+ const items = [1, 2, 3, 4, 5, 6]
21
+ const result = fn(items, (item) => (item % 2 === 0 ? 'even' : 'odd'))
22
+
23
+ expect(result.even).toEqual([2, 4, 6])
24
+ expect(result.odd).toEqual([1, 3, 5])
25
+ })
26
+
27
+ it('works with string keys', () => {
28
+ const items = ['apple', 'banana', 'apricot', 'blueberry']
29
+ const result = fn(items, (item) => item[0])
30
+
31
+ expect(result.a).toEqual(['apple', 'apricot'])
32
+ expect(result.b).toEqual(['banana', 'blueberry'])
33
+ })
34
+
35
+ it('works with number keys', () => {
36
+ const items = ['a', 'b', 'c', 'd']
37
+ const result = fn(items, (_, index) => Math.floor(index / 2))
38
+
39
+ expect(result[0]).toEqual(['a', 'b'])
40
+ expect(result[1]).toEqual(['c', 'd'])
41
+ })
42
+
43
+ it('works with symbol keys', () => {
44
+ const symA = Symbol('A')
45
+ const symB = Symbol('B')
46
+ const items = [1, 2, 3, 4]
47
+ const result = fn(items, (item) => (item % 2 === 0 ? symA : symB))
48
+
49
+ expect(result[symA]).toEqual([2, 4])
50
+ expect(result[symB]).toEqual([1, 3])
51
+ })
52
+
53
+ it('passes index to key selector', () => {
54
+ const items = ['a', 'b', 'c', 'd']
55
+ const result = fn(items, (_, index) => (index < 2 ? 'first' : 'second'))
56
+
57
+ expect(result.first).toEqual(['a', 'b'])
58
+ expect(result.second).toEqual(['c', 'd'])
59
+ })
60
+
61
+ it('handles empty array', () => {
62
+ const result = fn([], (item) => String(item))
63
+ expect(Object.keys(result)).toHaveLength(0)
64
+ })
65
+
66
+ it('handles single item', () => {
67
+ const result = fn([42], (item) => String(item))
68
+ expect(result['42']).toEqual([42])
69
+ })
70
+
71
+ it('handles all items mapping to same key', () => {
72
+ const items = [1, 2, 3, 4]
73
+ const result = fn(items, () => 'same')
74
+
75
+ expect(Object.keys(result)).toHaveLength(1)
76
+ expect(result.same).toEqual([1, 2, 3, 4])
77
+ })
78
+
79
+ it('works with iterables other than arrays', () => {
80
+ const set = new Set([1, 2, 3, 4, 5])
81
+ const result = fn(set, (item) => (item % 2 === 0 ? 'even' : 'odd'))
82
+
83
+ expect(result.even).toEqual([2, 4])
84
+ expect(result.odd).toEqual([1, 3, 5])
85
+ })
86
+ })
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @internal
3
+ */
4
+ export function objectGroupByPolyfill<K extends PropertyKey, T>(
5
+ items: Iterable<T>,
6
+ keySelector: (item: T, index: number) => K,
7
+ ): Partial<Record<K, T[]>> {
8
+ const result: Partial<Record<K, T[]>> = {}
9
+ let index = 0
10
+ for (const item of items) {
11
+ const key = keySelector(item, index)
12
+ const group = result[key]
13
+ if (group) {
14
+ group.push(item)
15
+ } else {
16
+ result[key] = [item]
17
+ }
18
+ index++
19
+ }
20
+ return result
21
+ }
22
+
23
+ /**
24
+ * @internal
25
+ */
26
+ export function objectGroupByNative<K extends PropertyKey, T>(
27
+ items: Iterable<T>,
28
+ keySelector: (item: T, index: number) => K,
29
+ ): Partial<Record<K, T[]>> {
30
+ return Object.groupBy(items, keySelector)
31
+ }
32
+
33
+ /**
34
+ * A polyfill for the `Object.groupBy` static method.
35
+ *
36
+ * @public
37
+ */
38
+ export const objectGroupBy: <K extends PropertyKey, T>(
39
+ items: Iterable<T>,
40
+ keySelector: (item: T, index: number) => K,
41
+ ) => Partial<Record<K, T[]>> = !!Object.groupBy
42
+ ? objectGroupByNative
43
+ : objectGroupByPolyfill