@avstantso/core 1.2.2 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,6 +1,30 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## [1.3.0] - 2026-07-04
5
+
6
+ ### Added
7
+
8
+ - `DeepOps.freeze`
9
+ - `DeepOps.clone`
10
+ - `DeepOps.merge`
11
+
12
+ ## [1.2.4] - 2026-07-04
13
+
14
+ ### Added
15
+
16
+ - `TS.Func.Type`
17
+ - `Func.Types`
18
+ - `Func.determineType`
19
+
20
+
21
+ ## [1.2.3] - 2026-06-19
22
+
23
+ ### Changed
24
+
25
+ - Makes singleton idempotent for VM-reload. Like it need for Jest
26
+
27
+
4
28
  ## [1.2.2] - 2026-02-04
5
29
 
6
30
  ### Changed
package/README.md CHANGED
@@ -45,11 +45,12 @@ avstantso.Catch(error); // Handle errors using centralized error handling
45
45
  You can also import specific members directly without relying on the global singleton:
46
46
 
47
47
  ```typescript
48
- import { Func, Generic, X } from '@avstantso/core';
48
+ import { Func, Generic, X, DeepOps } from '@avstantso/core';
49
49
 
50
50
  // Use imported members directly
51
51
  const isExtended = Func.isExt(myFunction);
52
52
  const casted = Generic.Cast<MyType>(value);
53
+ const cloned = DeepOps.clone(someValue);
53
54
 
54
55
  // X is a no-op function useful for Promise chains
55
56
  promise.then(X).catch(X); // Ignore both success and error
@@ -59,6 +60,7 @@ promise.then(X).catch(X); // Ignore both success and error
59
60
  - `Func` - Function utilities (same as `avstantso.Func`)
60
61
  - `Generic` - Generic type utilities (same as `avstantso.Generic`)
61
62
  - `X` - No-op function for ignoring Promise results
63
+ - `DeepOps` - Deep freeze/clone/merge utilities (same as `avstantso.DeepOps`)
62
64
 
63
65
  ## API Reference
64
66
 
@@ -356,6 +358,82 @@ const cloned = deepClone(original);
356
358
  // Custom Date instance is properly cloned using registered clone method
357
359
  ```
358
360
 
361
+ ### AVStantso.DeepOps - Deep Freeze / Clone / Merge
362
+
363
+ Deep, recursive `freeze`/`clone`/`merge` operations over plain objects, `Array`/`Set`/`Map`, functions, atomic objects (`Date`/`Buffer`, see [`AtomicObjects`](#avstantsoatomicobjects---custom-object-registry)), and scalars. Each operation walks a value's node types (`scalar`, `atomic`, `plain`, `rich`, `func`) and can be tuned per-call via `walk{NodeType}Inc`/`walk{NodeType}Exc` options; sensible defaults are applied for each operation (see method docs below).
364
+
365
+ **Members:**
366
+
367
+ - `avstantso.DeepOps.NodeTypes` - `Key2Key` record of the node type names (`scalar`, `atomic`, `plain`, `rich`, `func`)
368
+ - `avstantso.DeepOps.is.options.*` - Typeguards for the various options shapes
369
+ - `avstantso.DeepOps.freeze(obj, options?)` - Recursively `Object.freeze` a value
370
+ - `avstantso.DeepOps.clone(obj, options?)` - Recursively clone a value
371
+ - `avstantso.DeepOps.merge(target, ...sources, options)` - Recursively merge one or more sources into a clone of `target`
372
+
373
+ ##### `avstantso.DeepOps.freeze(obj, options?)`
374
+
375
+ Recursively `Object.freeze`s `obj` in place and returns the same reference. By default, walks plain objects, `Array`/`Set`/`Map`/class instances, and functions (except `class` and `generator` functions, which are left unfrozen) — scalars and atomic objects (`Date`/`Buffer`) are **not** walked/frozen by default.
376
+
377
+ **Options:**
378
+ - `ownProps?: boolean` - If `true`, also walks own non-enumerable properties (not just enumerable ones)
379
+ - `walk{Scalar|Atomic|Plain|Rich|Func}{Inc|Exc}` - Fine-tune which node types are walked, optionally per sub-kind (JS type, class, or function kind) or via a custom walker function
380
+
381
+ **Custom per-object behavior:** an object with a method at `avstantso.DeepOps.freeze.symbol` (same symbol as `avstantso.symbolFreeze`) has that method called instead of the default freeze logic for that object.
382
+
383
+ **Example:**
384
+ ```typescript
385
+ const config = { db: { host: 'localhost', port: 5432 }, tags: ['a', 'b'] };
386
+ avstantso.DeepOps.freeze(config);
387
+
388
+ config.db.port = 5433; // throws TypeError: Cannot assign to read only property
389
+ ```
390
+
391
+ ##### `avstantso.DeepOps.clone(obj, options?)`
392
+
393
+ Recursively clones `obj` into a brand-new structure. By default, walks scalars, atomic objects (cloned via their registered `AtomicObjects` descriptor), plain objects, `Array`/`Set`/`Map` (**not** arbitrary class instances), and functions (except `class`/`generator`, which are omitted from the clone entirely).
394
+
395
+ **Options:**
396
+ - `copyFuncs?: boolean` - If `true`, copies function references as-is instead of wrapping them in a new delegating function
397
+ - `ownProps?: boolean` / `walk{NodeType}{Inc|Exc}` - Same as `freeze`
398
+
399
+ **Custom per-object behavior:** an object with a method at `avstantso.DeepOps.clone.symbol` has that method called, and its return value used as the clone, bypassing default clone logic for that object.
400
+
401
+ **Example:**
402
+ ```typescript
403
+ const original = { name: 'John', birthDate: new Date('1990-01-01'), tags: ['dev', 'ts'] };
404
+ const cloned = avstantso.DeepOps.clone(original);
405
+
406
+ cloned.tags.push('new'); // does not affect original.tags
407
+ cloned.birthDate.setFullYear(2000); // does not affect original.birthDate
408
+ ```
409
+
410
+ ##### `avstantso.DeepOps.merge(target, ...sources, options)`
411
+
412
+ Recursively merges one or more `sources` into a **clone** of `target` (returned as a new object — `target` itself is never mutated) and returns the result. Options are always the last argument. Uses the same default node walking as `clone`.
413
+
414
+ Merge behavior by node type:
415
+ - **Plain objects** - keys are merged recursively; keys only in a source are added (unless `onlyExists`)
416
+ - **Arrays** - merged **by index** by default (source overwrites overlapping indices, extra source items are appended); pass `concatArrays: true` to concatenate instead (`[...target, ...source]`)
417
+ - **Sets** - source items are added if not already present
418
+ - **Maps** - existing keys are merged recursively / overwritten, new keys added (unless `onlyExists`)
419
+ - **Scalars/atomics, or a type mismatch between target/source** - the source always wins (cloned)
420
+
421
+ **Options** (each may be a value or a `(node, key?) => value` callback, e.g. to target specific keys):
422
+ - `onlyExists?: Common.Option<boolean>` - If truthy, only merges into keys/indices that already exist in the target
423
+ - `isUniqueArrItems?: Common.Option<boolean>` - If truthy, dedupes the resulting array (`[...new Set(arr)]`)
424
+ - `nullAsDelete?: Common.Option<boolean>` - If truthy, a `null` source value deletes the corresponding target key instead of setting it to `null`
425
+ - `concatArrays?: Common.Option<boolean>` - If truthy, arrays are merged by concatenation instead of by index
426
+
427
+ **Example:**
428
+ ```typescript
429
+ const result = avstantso.DeepOps.merge(
430
+ { a: 1, tags: ['x'], history: [1, 2] },
431
+ { a: 2, tags: ['x', 'y'], history: [3] },
432
+ { isUniqueArrItems: (arr, key) => key === 'tags' }
433
+ );
434
+ // { a: 2, tags: ['x', 'y'], history: [1, 3] }
435
+ ```
436
+
359
437
  ### AVStantso.Generic - Generic Type Utilities
360
438
 
361
439
  Utilities for working with generic types.
@@ -380,12 +458,57 @@ Runtime utilities for working with functions.
380
458
 
381
459
  **Members:**
382
460
 
461
+ - `avstantso.Func.Types` - `Key2Key` record of runtime function type names
462
+ - `avstantso.Func.determineType(func)` - Determines the runtime type of a function
383
463
  - `avstantso.Func.OwnPropertyDescriptors` - Property descriptors of the base Function prototype
384
464
  - `avstantso.Func.isPropAllowed(key)` - Check if a property name can be used for function extension
385
465
  - `avstantso.Func.isPropAllowed.Not(key)` - Check if property name is reserved
386
466
  - `avstantso.Func.isExt(func)` - Check if function has extended properties
387
467
  - `avstantso.Func.Dynamic<N, F>(name, func)` - Creates a named function wrapper for better debugging in development environments
388
468
 
469
+ ##### `avstantso.Func.determineType(func)`
470
+
471
+ Determines the runtime type of a function: `'plain'`, `'async'`, `'generator'`, `'class'`, `'arrow'`, or `'bound'`.
472
+
473
+ **Parameters:**
474
+ - `func` - Function to inspect
475
+
476
+ **Returns:** `AVStantso.TS.Func.Type` - one of the runtime function type names
477
+
478
+ **Example:**
479
+ ```typescript
480
+ function plain() {}
481
+ avstantso.Func.determineType(plain); // 'plain'
482
+
483
+ const arrow = () => {};
484
+ avstantso.Func.determineType(arrow); // 'arrow'
485
+
486
+ const bound = plain.bind(null);
487
+ avstantso.Func.determineType(bound); // 'bound'
488
+
489
+ class MyClass {}
490
+ avstantso.Func.determineType(MyClass); // 'class'
491
+
492
+ async function asyncFn() {}
493
+ avstantso.Func.determineType(asyncFn); // 'async'
494
+
495
+ function* genFn() {}
496
+ avstantso.Func.determineType(genFn); // 'generator'
497
+
498
+ async function* asyncGenFn() {}
499
+ avstantso.Func.determineType(asyncGenFn); // 'generator'
500
+ ```
501
+
502
+ ##### `avstantso.Func.Types`
503
+
504
+ A `Key2Key` record (each key mapped to itself) listing all runtime function type names recognized by `determineType`: `plain`, `async`, `generator`, `class`, `arrow`, `bound`.
505
+
506
+ **Example:**
507
+ ```typescript
508
+ avstantso.Func.Types.plain; // 'plain'
509
+ avstantso.Func.Types.async; // 'async'
510
+ ```
511
+
389
512
  **Example - Type Guard Function Factory:**
390
513
  ```typescript
391
514
  const Types = ['insert', 'update', 'delete'] as const;
@@ -123,7 +123,8 @@ declare namespace AVStantso {
123
123
  */
124
124
  freeze?(): void;
125
125
  /**
126
- * @summary `symbol` for custom freeze
126
+ * @summary `symbol` for custom freeze.\
127
+ * Same symbol of {@link Code.DeepOps.Freeze.symbol}
127
128
  * @see freeze
128
129
  */
129
130
  symbolFreeze: symbol;