nhb-toolbox 4.28.0 → 4.28.4

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/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ All notable changes to the package will be documented here.
6
6
 
7
7
  ---
8
8
 
9
+ ## [4.28.4] - 2025-12-02
10
+
11
+ - **Updated** *implementation* and *tsdoc* for:
12
+ - `cloneObject`: used `structuredClone` and `stableStringify` (optionally force to use *deterministic serialization*) internally and falls back to *shallow cloning* if serialization fails.
13
+ - `stableStringify`: stringified value of *Date-like objects* (`Date`, `Chronos`, `Moment.js`, `Day.js`, `Luxon`, `JS-Joda`, `Temporal`) is converted to string representation (in the same way that `JSON.stringify` would serialize them).
14
+ - **Updated** `convertObjectValues` behaviour: *fields* configured for *number conversion* now return `NaN` when *parsing fails*.
15
+ - **Updated** *reference links* in *tsdoc* of some *hash* utilities.
16
+
17
+ ## [4.28.1] - 2025-12-02
18
+
19
+ - **Updated** *type* names `TokenHeader` to `SignetHeader` and `TokenPayload` to `SignetPayload` and both are available to import.
20
+
9
21
  ## [4.28.0] - 2025-12-01
10
22
 
11
23
  - **Added** *new* class `Cipher` to *encrypt/decrypt* string with *secret key*.
@@ -34,7 +34,7 @@ exports.isInvalidOrEmptyArray = isInvalidOrEmptyArray;
34
34
  const shuffleArray = (array) => {
35
35
  if ((0, exports.isInvalidOrEmptyArray)(array))
36
36
  return array;
37
- const shuffled = structuredClone(array);
37
+ const shuffled = [...array];
38
38
  for (let i = shuffled?.length - 1; i > 0; i--) {
39
39
  const j = Math.floor(Math.random() * (i + 1));
40
40
  [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
@@ -15,9 +15,9 @@ function md5(str) {
15
15
  const $str = str.substring(i - 64);
16
16
  const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
17
17
  for (i = 0; i < $str.length; i++) {
18
- tail[i >> 2] |= $str.charCodeAt(i) << (i % 4 << 3);
18
+ tail[i >> 2] |= $str.charCodeAt(i) << ((i % 4) << 3);
19
19
  }
20
- tail[i >> 2] |= 0x80 << (i % 4 << 3);
20
+ tail[i >> 2] |= 0x80 << ((i % 4) << 3);
21
21
  if (i > 55) {
22
22
  (0, helpers_1._md5cycle)(state, tail);
23
23
  for (let j = 0; j < 16; j++) {
@@ -1,19 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.countObjectFields = exports.cloneObject = void 0;
3
+ exports.cloneObject = cloneObject;
4
+ exports.countObjectFields = countObjectFields;
4
5
  exports.extractObjectKeys = extractObjectKeys;
5
6
  exports.extractObjectKeysDeep = extractObjectKeysDeep;
6
7
  const non_primitives_1 = require("../guards/non-primitives");
7
- const cloneObject = (obj) => {
8
- return JSON.parse(JSON.stringify(obj));
9
- };
10
- exports.cloneObject = cloneObject;
11
- const countObjectFields = (obj) => {
8
+ const index_1 = require("../utils/index");
9
+ function cloneObject(obj, serialize = false) {
10
+ try {
11
+ if (!serialize && typeof structuredClone === 'function') {
12
+ return structuredClone(obj);
13
+ }
14
+ return JSON.parse((0, index_1.stableStringify)(obj));
15
+ }
16
+ catch {
17
+ return { ...obj };
18
+ }
19
+ }
20
+ function countObjectFields(obj) {
12
21
  if (obj != null)
13
22
  return Object.keys(obj)?.length;
14
23
  return 0;
15
- };
16
- exports.countObjectFields = countObjectFields;
24
+ }
17
25
  function extractObjectKeys(obj, tuple) {
18
26
  const keys = (0, non_primitives_1.isNotEmptyObject)(obj) ? Object.keys(obj) : [];
19
27
  return tuple ? keys : keys;
@@ -5,29 +5,25 @@ exports.pickFields = pickFields;
5
5
  exports.deleteFields = deleteFields;
6
6
  exports.pickObjectFieldsByCondition = pickObjectFieldsByCondition;
7
7
  exports.remapFields = remapFields;
8
+ const non_primitives_1 = require("../guards/non-primitives");
9
+ const primitives_1 = require("../guards/primitives");
8
10
  function convertObjectValues(data, options) {
9
- const { keys, convertTo } = options;
10
- const _shouldPreserveValue = (value) => convertTo === 'number' && (typeof value !== 'string' || isNaN(Number(value)));
11
+ const { keys, convertTo } = options || {};
11
12
  const _setValueAtPath = (obj, path, convertTo) => {
12
13
  const segments = path.split('.');
13
14
  let current = obj;
14
15
  segments?.forEach((key, index) => {
15
16
  if (index === segments?.length - 1) {
16
17
  const value = current?.[key];
17
- if (_shouldPreserveValue(value)) {
18
- return;
19
- }
20
- if (convertTo === 'string' && typeof value !== 'string') {
18
+ if (convertTo === 'string' && !(0, primitives_1.isString)(value)) {
21
19
  current[key] = String(value);
22
20
  }
23
- else if (convertTo === 'number' &&
24
- typeof value !== 'number' &&
25
- !isNaN(Number(value))) {
21
+ else if (convertTo === 'number' && !(0, primitives_1.isNumber)(value)) {
26
22
  current[key] = Number(value);
27
23
  }
28
24
  }
29
25
  else {
30
- if (typeof current?.[key] === 'object' && current?.[key] !== null) {
26
+ if ((0, non_primitives_1.isObject)(current?.[key])) {
31
27
  current = current?.[key];
32
28
  }
33
29
  else {
@@ -39,7 +35,7 @@ function convertObjectValues(data, options) {
39
35
  return obj;
40
36
  };
41
37
  const _convertValue = (obj) => {
42
- let newObj = structuredClone(obj);
38
+ let newObj = { ...obj };
43
39
  keys?.forEach((key) => {
44
40
  newObj = _setValueAtPath(newObj, key, convertTo);
45
41
  });
@@ -17,6 +17,7 @@ exports.deepParsePrimitives = deepParsePrimitives;
17
17
  exports.definePrototypeMethod = definePrototypeMethod;
18
18
  const helpers_1 = require("../array/helpers");
19
19
  const sort_1 = require("../array/sort");
20
+ const guards_1 = require("../date/guards");
20
21
  const non_primitives_1 = require("../guards/non-primitives");
21
22
  const primitives_1 = require("../guards/primitives");
22
23
  const specials_1 = require("../guards/specials");
@@ -137,7 +138,9 @@ function stableStringify(obj) {
137
138
  const keys = Object.keys(obj).sort();
138
139
  return ('{' +
139
140
  keys
140
- .map((k) => JSON.stringify(k, _replacer) + ':' + stableStringify(obj[k]))
141
+ .map((k) => JSON.stringify(k, _replacer) +
142
+ ':' +
143
+ ((0, guards_1.isDateLike)(obj[k]) ? JSON.stringify(obj[k]) : stableStringify(obj[k])))
141
144
  .join(',') +
142
145
  '}');
143
146
  }
@@ -1,5 +1,5 @@
1
1
  import type { GenericObject } from '../object/types';
2
- import type { DecodedToken, SignOptions, TokenPayload, TokenString, VerifiedToken, VerifyOptions } from './types';
2
+ import type { DecodedToken, SignetPayload, SignOptions, TokenString, VerifiedToken, VerifyOptions } from './types';
3
3
  /**
4
4
  * * A lightweight, secure implementation of JWT-like tokens using `HMAC-SHA256` signatures.
5
5
  * - This class provides methods to create, verify, and decode tokens with a simple API similar to JSON Web Tokens (`JWT`) but with a smaller footprint and zero dependencies.
@@ -166,7 +166,7 @@ export declare class Signet {
166
166
  *
167
167
  * @remarks
168
168
  * - Tokens without `exp` claim are considered non-expiring (returns `false`)
169
- * - Uses current system time for comparison
169
+ * - Uses current system time for comparison ({@link Date.now()})
170
170
  * - Does not verify the signature (use only with trusted tokens or after verification)
171
171
  *
172
172
  * @example
@@ -192,7 +192,10 @@ export declare class Signet {
192
192
  *
193
193
  * @throws If the token is malformed or cannot be decoded.
194
194
  *
195
- * @remarks Useful for implementing time-based access control, like activation links that shouldn't be used until a certain time.
195
+ * @remarks
196
+ * - Useful for implementing time-based access control, like activation links that shouldn't be used until a certain time.
197
+ * - Uses current system time for comparison ({@link Date.now()})
198
+ * - Does not verify the signature (use only with trusted tokens or after verification)
196
199
  *
197
200
  * @example
198
201
  * ```typescript
@@ -310,7 +313,7 @@ export declare class Signet {
310
313
  * @param options - Optional validation criteria for token claims.
311
314
  *
312
315
  * @returns A {@link VerifiedToken} object indicating success or failure.
313
- * - If valid: `{ isValid: true, payload: TokenPayload<T> }`
316
+ * - If valid: `{ isValid: true, payload: SignetPayload<T> }`
314
317
  * - If invalid: `{ isValid: false, error: string }`
315
318
  *
316
319
  * @remarks
@@ -440,5 +443,5 @@ export declare class Signet {
440
443
  * const canDelete = payload.permissions.includes('delete');
441
444
  * ```
442
445
  */
443
- decodePayload<T extends GenericObject = GenericObject>(token: string): TokenPayload<T>;
446
+ decodePayload<T extends GenericObject = GenericObject>(token: string): SignetPayload<T>;
444
447
  }
@@ -59,8 +59,8 @@ export declare function sha1(msg: string): string;
59
59
  * // Returns: '7037e204b825b83553ba336a6ec35b796d505599286ae864729ed6cb33ae9fe1'
60
60
  * ```
61
61
  *
62
- * @see {@link sha256Bytes} for hashing raw bytes
63
- * @see {@link utf8ToBytes} for converting string to `UTF-8` bytes
64
- * @see {@link bytesToHex} for converting bytes to a hexadecimal string
62
+ * @see {@link https://toolbox.nazmul-nhb.dev/docs/utilities/hash/sha256Bytes sha256Bytes} for hashing raw bytes
63
+ * @see {@link https://toolbox.nazmul-nhb.dev/docs/utilities/hash/encoding#utf8tobytes utf8ToBytes} for converting string to bytes
64
+ * @see {@link https://toolbox.nazmul-nhb.dev/docs/utilities/hash/encoding#bytestotex bytesToHex} for converting bytes to a hexadecimal string
65
65
  */
66
66
  export declare function sha256(msg: string): string;
@@ -45,7 +45,7 @@ export interface DecodedUUID {
45
45
  node?: string;
46
46
  }
47
47
  /** Header for `Signet` */
48
- export type TokenHeader = {
48
+ export type SignetHeader = {
49
49
  /** Algorithm used. Currently supports `'HS256'` only */
50
50
  alg: 'HS256';
51
51
  /** Type of token. Fixed `'SIGNET+JWT'` */
@@ -79,14 +79,14 @@ export interface SignOptions extends VerifyOptions {
79
79
  */
80
80
  notBefore?: TimeWithUnit | Numeric;
81
81
  }
82
- /** Pattern of a valid 3-parts token */
82
+ /** 3-parts dot separated token string */
83
83
  export type TokenString = `${string}.${string}.${string}`;
84
84
  /** Interface of token verification result if token is valid */
85
85
  export type $ValidToken<T extends GenericObject = GenericObject> = {
86
86
  /** Whether the token is valid */
87
87
  isValid: true;
88
- /** Decoded payload after successful verification with common {@link TokenPayload} properties */
89
- payload: TokenPayload<T>;
88
+ /** Decoded payload after successful verification with common {@link SignetPayload} properties */
89
+ payload: SignetPayload<T>;
90
90
  };
91
91
  /** Interface of token verification result if token is invalid */
92
92
  export type $InvalidToken = {
@@ -97,7 +97,7 @@ export type $InvalidToken = {
97
97
  };
98
98
  /** Result of token verification */
99
99
  export type VerifiedToken<T extends GenericObject = GenericObject> = $ValidToken<T> | $InvalidToken;
100
- export type TokenPayload<T extends GenericObject = GenericObject> = {
100
+ export type SignetPayload<T extends GenericObject = GenericObject> = {
101
101
  /** When the token was created (unix timestamp in seconds) */
102
102
  iat: number;
103
103
  /** When the token was created (as JavaScript {@link Date}) */
@@ -120,9 +120,9 @@ export type TokenPayload<T extends GenericObject = GenericObject> = {
120
120
  /** Interface of a decoded token */
121
121
  export type DecodedToken<T extends GenericObject = GenericObject> = {
122
122
  /** Token header info, algorithm, type etc. */
123
- header: TokenHeader;
124
- /** Decoded payload after with common {@link TokenPayload} properties */
125
- payload: TokenPayload<T>;
123
+ header: SignetHeader;
124
+ /** Decoded payload after with common {@link SignetPayload} properties */
125
+ payload: SignetPayload<T>;
126
126
  /**
127
127
  * The `Base64`-encoded signature from the token.
128
128
  * This is the third part of the token string.
@@ -131,3 +131,4 @@ export type DecodedToken<T extends GenericObject = GenericObject> = {
131
131
  /** The header and payload in encrypted `Base64` format.*/
132
132
  signingInput: `${string}.${string}`;
133
133
  };
134
+ export type { SignetPayload as TokenPayload, SignetHeader as TokenHeader };
@@ -43,7 +43,7 @@ import type { DecodedUUID, SupportedVersion, UUID, UUIDOptions } from './types';
43
43
  * - `v7`: Millisecond precision; extremely high throughput may still cause rare collisions.
44
44
  * - `v8`: Uses a simple timestamp + randomness layout; custom layouts are not supported here.
45
45
  *
46
- * - Use {@link https://toolbox.nazmul-nhb.dev/docs/utilities/string/generateRandomID generateRandomID} for customized id generation or {@link randomHex} for hex-only string with custom length.
46
+ * - Use {@link https://toolbox.nazmul-nhb.dev/docs/utilities/string/generateRandomID generateRandomID} for customized id generation or {@link https://toolbox.nazmul-nhb.dev/docs/utilities/hash/randomHex randomHex} for hex-only random string with custom length.
47
47
  */
48
48
  export declare function uuid<V extends SupportedVersion = 'v4'>(options?: UUIDOptions<V>): UUID<V>;
49
49
  /**
@@ -1,19 +1,56 @@
1
1
  import type { Tuple } from '../utils/types';
2
2
  import type { DeepKeys, GenericObject } from './types';
3
3
  /**
4
- * * Deep clone an object.
4
+ * * Deep clone an object using `structuredClone` or deterministic *JSON serialization*.
5
5
  *
6
6
  * @param obj Object to clone.
7
+ * @param serialize Whether to force deterministic JSON serialization instead of using `structuredClone`. Defaults to `false`.
7
8
  * @returns Deep cloned object.
9
+ *
10
+ * @remarks
11
+ * **Primary behavior**
12
+ * - By default (`serialize = false`), the function uses {@link https://developer.mozilla.org/docs/Web/API/Window/structuredClone structuredClone} when available. This supports:
13
+ * - Circular references
14
+ * - `Date` objects
15
+ * - `Map` / `Set`
16
+ * - `RegExp`
17
+ * - Typed arrays
18
+ * - Most built-in JavaScript types
19
+ * - Preserves `undefined` values
20
+ *
21
+ * - **Note:** `structuredClone` **does not preserve class prototypes**, even though it preserves data types like `Date`, `Map`, and `Set`.
22
+ *
23
+ * **Deterministic serialization mode**
24
+ * - When `serialize = true`, or when `structuredClone` is unavailable, the function falls back to **stable JSON serialization** via `stableStringify`. This guarantees:
25
+ * - All object keys are sorted alphabetically.
26
+ * - Consistent output across environments (deterministic).
27
+ * - All `undefined` values are converted to `null`.
28
+ * - Converting date-like objects (`Date`, `Chronos`, `Moment.js`, `Day.js`, `Luxon`, `JS-Joda`, `Temporal`) **in the same way that {@link JSON.stringify} would serialize them**, ensuring predictable and JSON-compliant output.
29
+ *
30
+ * - This mode is ideal for:
31
+ * - Hashing
32
+ * - Signature generation
33
+ * - Deep equality checks
34
+ * - Anything requiring deterministic, environment-neutral output
35
+ *
36
+ * **Deterministic mode limitations**
37
+ * - JSON serialization will:
38
+ * - Drop functions and `Symbol` values.
39
+ * - Lose prototype and class instance information.
40
+ * - Convert all date-like objects into strings.
41
+ * - Fail on circular references.
42
+ *
43
+ * **Final safety fallback**
44
+ * - If JSON serialization fails (e.g., due to circular references), the function returns a **shallow clone** (`{ ...obj }`) to ensure the cloning never throws.
8
45
  */
9
- export declare const cloneObject: <T extends GenericObject>(obj: T) => T;
46
+ export declare function cloneObject<T extends GenericObject>(obj: T, serialize?: boolean): T;
10
47
  /**
11
48
  * * Count the number of fields in an object.
12
49
  *
13
50
  * @param obj Object to check.
14
51
  * @returns Number of fields in the object.
15
52
  */
16
- export declare const countObjectFields: <T extends GenericObject>(obj: T) => number;
53
+ export declare function countObjectFields<T extends GenericObject>(obj: T): number;
17
54
  /**
18
55
  * * Extracts all the top-level keys of an object as an array.
19
56
  *
@@ -129,14 +129,16 @@ export declare function getClassDetails(cls: Constructor): ClassDetails;
129
129
  * - This function guarantees **stable, repeatable output** by:
130
130
  * - Sorting all object keys alphabetically.
131
131
  * - Recursively stabilizing nested objects and arrays.
132
- * - Automatically converting all `undefined` values into `null` so the output remains valid JSON.
132
+ * - Converting all `undefined` values into `null` so the output remains valid JSON.
133
+ * - Converting date-like objects (`Date`, `Chronos`, `Moment.js`, `Day.js`, `Luxon`, `JS-Joda`, `Temporal`) **in the same way that {@link JSON.stringify} would serialize them**, ensuring predictable and JSON-compliant output.
133
134
  * - Falling back to native JSON serialization for primitives.
134
- * - Useful for:
135
+ *
136
+ * - **Useful for:**
135
137
  * - Hash generation (e.g., signatures, cache keys)
136
138
  * - Deep equality checks
137
139
  * - Producing predictable output across environments
138
140
  *
139
- * @param obj - The value to stringify into a deterministic JSON-like string.
141
+ * @param obj - The value to stringify into a deterministic JSON string.
140
142
  * @returns A stable, deterministic string representation of the input.
141
143
  */
142
144
  export declare function stableStringify(obj: unknown): string;
@@ -155,12 +157,12 @@ export declare function stripJsonEdgeGarbage(str: string): string;
155
157
  * @returns The parsed JSON value typed as `T`, or the original parsed value with optional primitive conversion.
156
158
  * - Returns `{}` if parsing fails, such as when the input is malformed or invalid JSON or passing single quoted string.
157
159
  *
158
- * - *Unlike `parseJsonToObject`, which ensures the root value is an object,
160
+ * - *Unlike {@link https://toolbox.nazmul-nhb.dev/docs/utilities/object/parseJsonToObject parseJsonToObject}, which ensures the root value is an object,
159
161
  * this function returns any valid JSON structure such as arrays, strings, numbers, or objects.*
160
162
  *
161
163
  * This is useful when you're not sure of the root structure of the JSON, or when you expect something other than an object.
162
164
  *
163
- * @see `parseJsonToObject` for strict object-only parsing.
165
+ * @see {@link https://toolbox.nazmul-nhb.dev/docs/utilities/object/parseJsonToObject parseJsonToObject} for strict object-only parsing.
164
166
  */
165
167
  export declare const parseJSON: <T = unknown>(value: string, parsePrimitives?: boolean) => T;
166
168
  /**
@@ -28,7 +28,7 @@ export const isInvalidOrEmptyArray = (value) => {
28
28
  export const shuffleArray = (array) => {
29
29
  if (isInvalidOrEmptyArray(array))
30
30
  return array;
31
- const shuffled = structuredClone(array);
31
+ const shuffled = [...array];
32
32
  for (let i = shuffled?.length - 1; i > 0; i--) {
33
33
  const j = Math.floor(Math.random() * (i + 1));
34
34
  [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
@@ -10,9 +10,9 @@ export function md5(str) {
10
10
  const $str = str.substring(i - 64);
11
11
  const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
12
12
  for (i = 0; i < $str.length; i++) {
13
- tail[i >> 2] |= $str.charCodeAt(i) << (i % 4 << 3);
13
+ tail[i >> 2] |= $str.charCodeAt(i) << ((i % 4) << 3);
14
14
  }
15
- tail[i >> 2] |= 0x80 << (i % 4 << 3);
15
+ tail[i >> 2] |= 0x80 << ((i % 4) << 3);
16
16
  if (i > 55) {
17
17
  _md5cycle(state, tail);
18
18
  for (let j = 0; j < 16; j++) {
@@ -1,12 +1,21 @@
1
1
  import { isNotEmptyObject } from '../guards/non-primitives.js';
2
- export const cloneObject = (obj) => {
3
- return JSON.parse(JSON.stringify(obj));
4
- };
5
- export const countObjectFields = (obj) => {
2
+ import { stableStringify } from '../utils/index.js';
3
+ export function cloneObject(obj, serialize = false) {
4
+ try {
5
+ if (!serialize && typeof structuredClone === 'function') {
6
+ return structuredClone(obj);
7
+ }
8
+ return JSON.parse(stableStringify(obj));
9
+ }
10
+ catch {
11
+ return { ...obj };
12
+ }
13
+ }
14
+ export function countObjectFields(obj) {
6
15
  if (obj != null)
7
16
  return Object.keys(obj)?.length;
8
17
  return 0;
9
- };
18
+ }
10
19
  export function extractObjectKeys(obj, tuple) {
11
20
  const keys = isNotEmptyObject(obj) ? Object.keys(obj) : [];
12
21
  return tuple ? keys : keys;
@@ -1,26 +1,22 @@
1
+ import { isObject } from '../guards/non-primitives.js';
2
+ import { isNumber, isString } from '../guards/primitives.js';
1
3
  export function convertObjectValues(data, options) {
2
- const { keys, convertTo } = options;
3
- const _shouldPreserveValue = (value) => convertTo === 'number' && (typeof value !== 'string' || isNaN(Number(value)));
4
+ const { keys, convertTo } = options || {};
4
5
  const _setValueAtPath = (obj, path, convertTo) => {
5
6
  const segments = path.split('.');
6
7
  let current = obj;
7
8
  segments?.forEach((key, index) => {
8
9
  if (index === segments?.length - 1) {
9
10
  const value = current?.[key];
10
- if (_shouldPreserveValue(value)) {
11
- return;
12
- }
13
- if (convertTo === 'string' && typeof value !== 'string') {
11
+ if (convertTo === 'string' && !isString(value)) {
14
12
  current[key] = String(value);
15
13
  }
16
- else if (convertTo === 'number' &&
17
- typeof value !== 'number' &&
18
- !isNaN(Number(value))) {
14
+ else if (convertTo === 'number' && !isNumber(value)) {
19
15
  current[key] = Number(value);
20
16
  }
21
17
  }
22
18
  else {
23
- if (typeof current?.[key] === 'object' && current?.[key] !== null) {
19
+ if (isObject(current?.[key])) {
24
20
  current = current?.[key];
25
21
  }
26
22
  else {
@@ -32,7 +28,7 @@ export function convertObjectValues(data, options) {
32
28
  return obj;
33
29
  };
34
30
  const _convertValue = (obj) => {
35
- let newObj = structuredClone(obj);
31
+ let newObj = { ...obj };
36
32
  keys?.forEach((key) => {
37
33
  newObj = _setValueAtPath(newObj, key, convertTo);
38
34
  });
@@ -1,5 +1,6 @@
1
1
  import { _resolveNestedKey } from '../array/helpers.js';
2
2
  import { sortAnArray } from '../array/sort.js';
3
+ import { isDateLike } from '../date/guards.js';
3
4
  import { isArray, isArrayOfType, isMethodDescriptor, isNotEmptyObject, isObject, isValidArray, } from '../guards/non-primitives.js';
4
5
  import { isNonEmptyString, isPrimitive, isString } from '../guards/primitives.js';
5
6
  import { isNumericString } from '../guards/specials.js';
@@ -119,7 +120,9 @@ export function stableStringify(obj) {
119
120
  const keys = Object.keys(obj).sort();
120
121
  return ('{' +
121
122
  keys
122
- .map((k) => JSON.stringify(k, _replacer) + ':' + stableStringify(obj[k]))
123
+ .map((k) => JSON.stringify(k, _replacer) +
124
+ ':' +
125
+ (isDateLike(obj[k]) ? JSON.stringify(obj[k]) : stableStringify(obj[k])))
123
126
  .join(',') +
124
127
  '}');
125
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nhb-toolbox",
3
- "version": "4.28.0",
3
+ "version": "4.28.4",
4
4
  "description": "A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -42,8 +42,8 @@
42
42
  "@eslint/js": "^9.39.1",
43
43
  "@types/jest": "^30.0.0",
44
44
  "@types/node": "^24.10.1",
45
- "@typescript-eslint/eslint-plugin": "^8.47.0",
46
- "@typescript-eslint/parser": "^8.47.0",
45
+ "@typescript-eslint/eslint-plugin": "^8.48.0",
46
+ "@typescript-eslint/parser": "^8.48.0",
47
47
  "eslint": "^9.39.1",
48
48
  "eslint-config-prettier": "^10.1.8",
49
49
  "eslint-plugin-prettier": "^5.5.4",
@@ -52,10 +52,10 @@
52
52
  "jest": "^30.2.0",
53
53
  "lint-staged": "^16.2.7",
54
54
  "nhb-scripts": "^1.8.88",
55
- "prettier": "^3.6.2",
56
- "ts-jest": "^29.4.5",
55
+ "prettier": "^3.7.3",
56
+ "ts-jest": "^29.4.6",
57
57
  "typescript": "^5.9.3",
58
- "typescript-eslint": "^8.47.0"
58
+ "typescript-eslint": "^8.48.0"
59
59
  },
60
60
  "keywords": [
61
61
  "toolbox",