@adviser/cement 0.4.63 → 0.4.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/cjs/index.cjs +1 -0
  2. package/cjs/index.cjs.map +1 -1
  3. package/cjs/index.d.ts +1 -0
  4. package/cjs/index.d.ts.map +1 -1
  5. package/cjs/keyed-ng.cjs +93 -0
  6. package/cjs/keyed-ng.cjs.map +1 -0
  7. package/cjs/keyed-ng.d.ts +51 -0
  8. package/cjs/keyed-ng.d.ts.map +1 -0
  9. package/cjs/keyed-ng.test.cjs +151 -0
  10. package/cjs/keyed-ng.test.cjs.map +1 -0
  11. package/cjs/keyed-ng.test.d.ts +2 -0
  12. package/cjs/keyed-ng.test.d.ts.map +1 -0
  13. package/cjs/lru-map-set.cjs +2 -1
  14. package/cjs/lru-map-set.cjs.map +1 -1
  15. package/cjs/lru-map-set.d.ts +4 -1
  16. package/cjs/lru-map-set.d.ts.map +1 -1
  17. package/cjs/resolve-once.cjs +67 -104
  18. package/cjs/resolve-once.cjs.map +1 -1
  19. package/cjs/resolve-once.d.ts +26 -62
  20. package/cjs/resolve-once.d.ts.map +1 -1
  21. package/cjs/resolve-once.test.cjs +119 -31
  22. package/cjs/resolve-once.test.cjs.map +1 -1
  23. package/cjs/version.cjs +1 -1
  24. package/deno.json +1 -1
  25. package/esm/index.d.ts +1 -0
  26. package/esm/index.d.ts.map +1 -1
  27. package/esm/index.js +1 -0
  28. package/esm/index.js.map +1 -1
  29. package/esm/keyed-ng.d.ts +51 -0
  30. package/esm/keyed-ng.d.ts.map +1 -0
  31. package/esm/keyed-ng.js +89 -0
  32. package/esm/keyed-ng.js.map +1 -0
  33. package/esm/keyed-ng.test.d.ts +2 -0
  34. package/esm/keyed-ng.test.d.ts.map +1 -0
  35. package/esm/keyed-ng.test.js +149 -0
  36. package/esm/keyed-ng.test.js.map +1 -0
  37. package/esm/lru-map-set.d.ts +4 -1
  38. package/esm/lru-map-set.d.ts.map +1 -1
  39. package/esm/lru-map-set.js +2 -1
  40. package/esm/lru-map-set.js.map +1 -1
  41. package/esm/resolve-once.d.ts +26 -62
  42. package/esm/resolve-once.d.ts.map +1 -1
  43. package/esm/resolve-once.js +66 -102
  44. package/esm/resolve-once.js.map +1 -1
  45. package/esm/resolve-once.test.js +119 -31
  46. package/esm/resolve-once.test.js.map +1 -1
  47. package/esm/version.js +1 -1
  48. package/package.json +3 -3
  49. package/src/index.ts +1 -0
  50. package/src/keyed-ng.ts +628 -0
  51. package/src/lru-map-set.ts +6 -2
  52. package/src/resolve-once.ts +281 -324
  53. package/ts/cjs/index.d.ts +1 -0
  54. package/ts/cjs/index.d.ts.map +1 -1
  55. package/ts/cjs/index.js +1 -0
  56. package/ts/cjs/index.js.map +1 -1
  57. package/ts/cjs/keyed-ng.d.ts +51 -0
  58. package/ts/cjs/keyed-ng.d.ts.map +1 -0
  59. package/ts/cjs/keyed-ng.js +93 -0
  60. package/ts/cjs/keyed-ng.js.map +1 -0
  61. package/ts/cjs/keyed-ng.test.d.ts +2 -0
  62. package/ts/cjs/keyed-ng.test.d.ts.map +1 -0
  63. package/ts/cjs/keyed-ng.test.js +151 -0
  64. package/ts/cjs/keyed-ng.test.js.map +1 -0
  65. package/ts/cjs/lru-map-set.d.ts +4 -1
  66. package/ts/cjs/lru-map-set.d.ts.map +1 -1
  67. package/ts/cjs/lru-map-set.js +2 -1
  68. package/ts/cjs/lru-map-set.js.map +1 -1
  69. package/ts/cjs/resolve-once.d.ts +26 -62
  70. package/ts/cjs/resolve-once.d.ts.map +1 -1
  71. package/ts/cjs/resolve-once.js +67 -104
  72. package/ts/cjs/resolve-once.js.map +1 -1
  73. package/ts/cjs/resolve-once.test.js +119 -31
  74. package/ts/cjs/resolve-once.test.js.map +1 -1
  75. package/ts/cjs/version.js +1 -1
  76. package/ts/esm/index.d.ts +1 -0
  77. package/ts/esm/index.d.ts.map +1 -1
  78. package/ts/esm/index.js +1 -0
  79. package/ts/esm/index.js.map +1 -1
  80. package/ts/esm/keyed-ng.d.ts +51 -0
  81. package/ts/esm/keyed-ng.d.ts.map +1 -0
  82. package/ts/esm/keyed-ng.js +89 -0
  83. package/ts/esm/keyed-ng.js.map +1 -0
  84. package/ts/esm/keyed-ng.test.d.ts +2 -0
  85. package/ts/esm/keyed-ng.test.d.ts.map +1 -0
  86. package/ts/esm/keyed-ng.test.js +149 -0
  87. package/ts/esm/keyed-ng.test.js.map +1 -0
  88. package/ts/esm/lru-map-set.d.ts +4 -1
  89. package/ts/esm/lru-map-set.d.ts.map +1 -1
  90. package/ts/esm/lru-map-set.js +2 -1
  91. package/ts/esm/lru-map-set.js.map +1 -1
  92. package/ts/esm/resolve-once.d.ts +26 -62
  93. package/ts/esm/resolve-once.d.ts.map +1 -1
  94. package/ts/esm/resolve-once.js +66 -102
  95. package/ts/esm/resolve-once.js.map +1 -1
  96. package/ts/esm/resolve-once.test.js +119 -31
  97. package/ts/esm/resolve-once.test.js.map +1 -1
  98. package/ts/esm/version.js +1 -1
@@ -17,9 +17,10 @@
17
17
  import { Future } from "./future.js";
18
18
  import { UnPromisify } from "./is-promise.js";
19
19
  import { isPromise } from "./is-promise.js";
20
- import { LRUMap, LRUParam, UnregFn } from "./lru-map-set.js";
20
+ import { UnregFn } from "./lru-map-set.js";
21
21
  import { Result } from "./result.js";
22
22
  import { Option } from "./option.js";
23
+ import { KeyedIf, KeyedNg, KeyedNgItem, KeyedNgItemWithoutValue, KeyedNgOptions } from "./keyed-ng.js";
23
24
 
24
25
  /**
25
26
  * Internal item representing a queued function in a ResolveSeq sequence.
@@ -562,6 +563,11 @@ export class ResolveOnce<T, CTX = void> implements ResolveOnceIf<T, CTX> {
562
563
 
563
564
  reset<R>(fn?: (c: CTX) => R): ResultOnce<R> {
564
565
  if (this.#state === "initial") {
566
+ if (!fn) {
567
+ // eslint-disable-next-line no-console
568
+ console.warn("ResolveOnce.reset called but not yet resolved and no fn given");
569
+ return undefined as ResultOnce<R>;
570
+ }
565
571
  return this.once(fn as (c: CTX) => R);
566
572
  }
567
573
  if (this.#state === "processing") {
@@ -573,407 +579,364 @@ export class ResolveOnce<T, CTX = void> implements ResolveOnceIf<T, CTX> {
573
579
  }
574
580
  }
575
581
 
582
+ // /**
583
+ // * Configuration parameters for Keyed instances.
584
+ // * @template K - The key type
585
+ // * @template V - The value type
586
+ // */
587
+ // export interface KeyedParam<K, V> {
588
+ // readonly lru: Partial<LRUParam<V, K>>;
589
+ // }
590
+
576
591
  /**
577
- * Configuration parameters for Keyed instances.
592
+ * Represents a key-value pair where the value is wrapped in a Result.
578
593
  * @template K - The key type
579
594
  * @template V - The value type
580
595
  */
581
- export interface KeyedParam<K, V> {
582
- readonly lru: Partial<LRUParam<V, K>>;
596
+ export interface KeyItem<K, V> {
597
+ readonly key: K;
598
+ readonly value: Result<V>;
583
599
  }
584
600
 
585
601
  /**
586
- * Extended configuration that includes context.
602
+ * Configuration parameters for KeyedResolvOnce, excluding the createValue factory.
587
603
  * @template K - The key type
588
604
  * @template V - The value type
589
605
  * @template CTX - The context type
590
606
  */
591
- export type AddKeyedParam<K, V, CTX extends NonNullable<object>> = KeyedParam<K, V> & { readonly ctx: CTX };
607
+ export type AddKeyedParam<K, V, CTX extends NonNullable<object>> = Omit<KeyedNgOptions<K, V, CTX>, "createValue">;
592
608
 
593
- export interface KeyedIf<T extends { reset: () => void }, K = string> {
594
- /**
595
- * Registers a callback that fires when a new entry is added to the map.
596
- *
597
- * @param fn - Callback function receiving key and value
598
- * @returns Unregister function to remove the callback
599
- */
600
- onSet(fn: (key: K, value: T) => void): UnregFn;
601
-
602
- /**
603
- * Registers a callback that fires when an entry is deleted from the map.
604
- *
605
- * @param fn - Callback function receiving key and value
606
- * @returns Unregister function to remove the callback
607
- */
608
- onDelete(fn: (key: K, value: T) => void): UnregFn;
609
-
610
- /**
611
- * Updates the LRU parameters of the underlying map.
612
- *
613
- * @param params - New parameters to apply
614
- */
615
- setParam(params: KeyedParam<K, T>): void;
616
-
617
- /**
618
- * Async variant of get() that accepts a function returning a promise for the key.
619
- *
620
- * @param key - Function that returns a promise resolving to the key
621
- * @returns Promise resolving to the value
622
- */
623
- asyncGet(key: () => Promise<K>): Promise<T>;
609
+ /**
610
+ * Type helper that adds a key property to a context object.
611
+ * @template X - The context type
612
+ * @template K - The key type
613
+ */
614
+ export type WithKey<X extends NonNullable<object>, K> = X & { readonly key: K };
624
615
 
625
- /**
626
- * Gets or creates a value for the given key.
627
- *
628
- * If the key doesn't exist, creates a new instance using the factory function.
629
- *
630
- * @param key - The key or function returning the key
631
- * @returns The value associated with the key
632
- */
633
- get(key: K | (() => K)): T;
616
+ /**
617
+ * Type helper that adds an optional reset method to a value type.
618
+ * @template V - The value type
619
+ */
620
+ export type WithOptionalReset<V> = V & { readonly reset?: () => void };
634
621
 
635
- /**
636
- * Checks if a key exists in the map.
637
- *
638
- * @param key - The key or function returning the key
639
- * @returns True if the key exists
640
- */
641
- has(key: K | (() => K)): boolean;
622
+ /**
623
+ * Represents an item in a KeyedResolvOnce collection with its resolved result.
624
+ * @template K - The key type
625
+ * @template T - The value type
626
+ * @template CTX - The context type
627
+ */
628
+ export interface KeyedResolveOnceItem<K, T, CTX extends NonNullable<object>> {
629
+ /** The key associated with this item */
630
+ readonly key: K;
631
+ /** The resolved value wrapped in a Result (Ok or Err) */
632
+ readonly value: Result<T>;
633
+ /** The complete KeyedNgItem containing metadata */
634
+ readonly item: KeyedNgItem<K, ResolveOnce<WithOptionalReset<T>, KeyedNgItemWithoutValue<K, CTX>>, CTX>;
635
+ }
642
636
 
643
- /**
644
- * Deletes an entry from the map.
645
- *
646
- * @param key - The key to delete
647
- */
648
- delete(key: K): void;
637
+ /**
638
+ * Keyed collection of ResolveOnce instances.
639
+ *
640
+ * Manages a map of ResolveOnce instances indexed by keys, with optional LRU caching.
641
+ * Each key gets its own ResolveOnce instance that can be accessed and manipulated independently.
642
+ * Values can optionally have a reset() method for cleanup on deletion.
643
+ *
644
+ * @template T - The return type of the ResolveOnce instances (must include optional reset)
645
+ * @template K - The key type (defaults to string)
646
+ * @template CTX - Optional context type (defaults to empty object)
647
+ * @template PT - Plain type of T without reset (for internal use)
648
+ *
649
+ * @example
650
+ * ```typescript
651
+ * const cache = new KeyedResolvOnce<number, string>();
652
+ *
653
+ * // Each key gets its own ResolveOnce
654
+ * const result1 = cache.get('key1').once(() => expensiveCalc1());
655
+ * const result2 = cache.get('key2').once(() => expensiveCalc2());
656
+ *
657
+ * // Delete specific key
658
+ * cache.delete('key1');
659
+ *
660
+ * // Iterate over all resolved entries
661
+ * cache.forEach((item) => {
662
+ * console.log(item.key, item.value.Ok);
663
+ * });
664
+ * ```
665
+ *
666
+ * @example
667
+ * ```typescript
668
+ * // With custom key type and context
669
+ * interface UserKey { org: string; id: string; }
670
+ * interface UserContext { apiKey: string; }
671
+ *
672
+ * const users = new KeyedResolvOnce<User, UserKey, UserContext>({
673
+ * key2string: (key) => `${key.org}:${key.id}`,
674
+ * ctx: { apiKey: 'default' },
675
+ * lru: { max: 100 }
676
+ * });
677
+ *
678
+ * const user = users.get({ org: 'acme', id: '123' })
679
+ * .once(({ givenKey, ctx }) => fetchUser(givenKey, ctx));
680
+ * ```
681
+ */
682
+ export class KeyedResolvOnce<T extends WithOptionalReset<PT>, K = string, CTX extends NonNullable<object> = object, PT = T>
683
+ implements
684
+ Omit<
685
+ // KeyedIf<ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>, WithOptionalReset<T>, K>
686
+ KeyedIf<
687
+ KeyedNgItem<K, ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>, CTX>,
688
+ ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>,
689
+ K,
690
+ CTX
691
+ >,
692
+ "entries" | "forEach" | "onSet" | "onDelete" | "values" | "setParam"
693
+ >
694
+ {
695
+ /** @internal */
696
+ readonly _keyed: KeyedNg<K, ResolveOnce<WithOptionalReset<T>, KeyedNgItemWithoutValue<K, CTX>>, CTX>;
649
697
 
650
698
  /**
651
- * Resets and deletes an entry from the map.
652
- *
653
- * Calls the value's reset() method before removing it.
699
+ * Creates a new KeyedResolvOnce instance.
654
700
  *
655
- * @param key - The key to reset and delete
701
+ * @param kp - Configuration options (key2string, ctx, lru)
656
702
  */
657
- unget(key: K): void;
703
+ constructor(kp: Partial<AddKeyedParam<K, T, CTX>> = {}) {
704
+ this._keyed = new KeyedNg({
705
+ createValue: (
706
+ item: KeyedNgItem<K, ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>, CTX>,
707
+ ): ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>> => {
708
+ return new ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>({
709
+ ...item,
710
+ ctx: kp.ctx ?? item.ctx,
711
+ });
712
+ },
713
+ key2string: kp.key2string,
714
+ ctx: kp.ctx as CTX,
715
+ lru: kp.lru,
716
+ });
717
+ }
658
718
 
659
719
  /**
660
- * Resets all entries and clears the map.
720
+ * Returns all keys currently in the collection.
661
721
  *
662
- * Calls reset() on all values before clearing.
722
+ * @returns Array of all keys
663
723
  */
664
- reset(): void;
724
+ keys(): K[] {
725
+ return this._keyed.keys();
726
+ }
665
727
 
666
728
  /**
667
- * Returns all values in the map.
729
+ * Returns all resolved items with their values wrapped in Result.
668
730
  *
669
- * @returns Array of all values
670
- */
671
- values(): T[];
672
-
673
- /**
674
- * Returns all keys in the map.
731
+ * Only includes items that have been resolved (ready state).
732
+ * Each item contains the key, Result-wrapped value, and full item metadata.
675
733
  *
676
- * @returns Array of all keys
677
- */
678
- keys(): K[];
679
-
680
- /**
681
- * Iterates over all entries in the map.
734
+ * @returns Array of all resolved items
682
735
  *
683
- * @yields Key-value pairs
736
+ * @example
737
+ * ```typescript
738
+ * const items = cache.values();
739
+ * items.forEach(({ key, value }) => {
740
+ * if (value.Ok) {
741
+ * console.log(key, value.unwrap());
742
+ * } else {
743
+ * console.error(key, value.unwrapErr());
744
+ * }
745
+ * });
746
+ * ```
684
747
  */
685
- forEach(fn: (k: K, v: T, idx: number) => void): void;
686
-
687
- entries(): Iterable<[K, T]>;
688
- }
689
-
690
- /**
691
- * Base class for managing keyed instances with LRU caching.
692
- *
693
- * Keyed provides a map-like interface where values are lazily created via a factory function
694
- * and cached with optional LRU eviction. Values must have a `reset()` method for cleanup.
695
- *
696
- * @template T - The value type (must have a reset method)
697
- * @template K - The key type
698
- * @template CTX - Optional context type passed to the factory
699
- *
700
- * @example
701
- * ```typescript
702
- * const keyed = new Keyed(
703
- * (ctx) => new ResolveOnce(ctx),
704
- * { lru: { maxEntries: 100 } }
705
- * );
706
- *
707
- * const instance = keyed.get('myKey');
708
- * ```
709
- */
710
- export class Keyed<T extends { reset: () => void }, K = string, CTX extends NonNullable<object> = object> implements KeyedIf<T, K> {
711
- protected readonly _map: LRUMap<K, T>;
712
- readonly #ctx: CTX;
713
-
714
- readonly factory: (ctx: AddKey<CTX, K>) => T;
715
-
716
- constructor(factory: (ctx: AddKey<CTX, K>) => T, ctx: Partial<AddKeyedParam<K, T, CTX>>) {
717
- this.#ctx = ctx.ctx || ({} as CTX);
718
- this.factory = factory;
719
- this._map = new LRUMap<K, T>(ctx?.lru ?? ({ maxEntries: -1 } as LRUParam<T, K>));
748
+ values(): KeyedResolveOnceItem<K, T, CTX>[] {
749
+ return this._keyed
750
+ .values()
751
+ .filter((i) => i.value.ready)
752
+ .map((item) => ({
753
+ key: item.givenKey,
754
+ value: item.value.error ? Result.Err<T>(item.value.error) : Result.Ok<T>(item.value.value as T),
755
+ item,
756
+ }));
720
757
  }
721
758
 
722
759
  /**
723
- * Registers a callback that fires when a new entry is added to the map.
760
+ * Registers a callback that fires when a new ResolveOnce instance is created.
724
761
  *
725
- * @param fn - Callback function receiving key and value
726
- * @returns Unregister function to remove the callback
762
+ * @param fn - Callback receiving the key and ResolveOnce instance
763
+ * @returns Unregister function
727
764
  */
728
- onSet(fn: (key: K, value: T) => void): UnregFn {
729
- return this._map.onSet(fn);
765
+ onSet(fn: (key: K, value: ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>) => void): UnregFn {
766
+ return this._keyed.onSet((item) => {
767
+ fn(item.givenKey, item.value);
768
+ });
730
769
  }
731
770
 
732
771
  /**
733
- * Registers a callback that fires when an entry is deleted from the map.
772
+ * Registers a callback that fires when a ResolveOnce instance is deleted.
734
773
  *
735
- * @param fn - Callback function receiving key and value
736
- * @returns Unregister function to remove the callback
774
+ * @param fn - Callback receiving the key and ResolveOnce instance
775
+ * @returns Unregister function
737
776
  */
738
- onDelete(fn: (key: K, value: T) => void): UnregFn {
739
- return this._map.onDelete(fn);
777
+ onDelete(fn: (key: K, value: ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>) => void): UnregFn {
778
+ return this._keyed.onDelete((item) => {
779
+ fn(item.givenKey, item.value);
780
+ });
740
781
  }
741
782
 
742
783
  /**
743
- * Updates the LRU parameters of the underlying map.
784
+ * Updates the LRU parameters dynamically.
744
785
  *
745
- * @param params - New parameters to apply
786
+ * @param params - New LRU parameters
746
787
  */
747
- setParam(params: KeyedParam<K, T>): void {
748
- this._map.setParam(params.lru);
788
+ setParam(params: Partial<AddKeyedParam<K, ResolveOnce<T, CTX>, CTX>>): void {
789
+ this._keyed.setParam({ lru: params.lru });
749
790
  }
750
791
 
751
792
  /**
752
- * Async variant of get() that accepts a function returning a promise for the key.
793
+ * Asynchronously gets or creates a ResolveOnce for a key resolved from a promise.
753
794
  *
754
- * @param key - Function that returns a promise resolving to the key
755
- * @returns Promise resolving to the value
795
+ * @param key - Function returning a promise that resolves to the key
796
+ * @returns Promise resolving to the ResolveOnce instance
756
797
  */
757
- async asyncGet(key: () => Promise<K>): Promise<T> {
758
- return this.get(await key());
798
+ asyncGet(key: () => Promise<K>): Promise<ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>> {
799
+ return this._keyed.asyncGet(key);
759
800
  }
760
801
 
761
802
  /**
762
- * Gets or creates a value for the given key.
803
+ * Gets or creates a ResolveOnce instance for the given key.
763
804
  *
764
- * If the key doesn't exist, creates a new instance using the factory function.
805
+ * This is the primary method for accessing ResolveOnce instances. Each unique
806
+ * key gets its own instance that persists across calls.
765
807
  *
766
808
  * @param key - The key or function returning the key
767
- * @returns The value associated with the key
809
+ * @param ctx - Optional context override for this operation
810
+ * @returns The ResolveOnce instance for this key
811
+ *
812
+ * @example
813
+ * ```typescript
814
+ * const result = cache.get('myKey').once(({ refKey, givenKey, ctx }) => {
815
+ * return computeValue(givenKey, ctx);
816
+ * });
817
+ * ```
768
818
  */
769
- get(key: K | (() => K)): T {
819
+ get(key: K | (() => K), ctx?: CTX): ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>> {
770
820
  if (typeof key === "function") {
771
821
  key = (key as () => K)();
772
822
  }
773
- let keyed = this._map.get(key);
774
- if (!keyed) {
775
- keyed = this.factory({ ...this.#ctx, key: key });
776
- this._map.set(key, keyed);
777
- }
778
- return keyed;
823
+ return this._keyed.getItem(key, ctx).value;
824
+ }
825
+
826
+ /**
827
+ * Gets or creates the complete KeyedNgItem for a key.
828
+ *
829
+ * Useful when you need access to the full item structure including metadata.
830
+ *
831
+ * @param key - The key to get
832
+ * @param ctx - Optional context override
833
+ * @returns The complete KeyedNgItem
834
+ */
835
+ getItem(key: K, ctx?: CTX): KeyedNgItem<K, ResolveOnce<T, KeyedNgItemWithoutValue<K, CTX>>, CTX> {
836
+ return this._keyed.getItem(key, ctx);
779
837
  }
780
838
 
781
839
  /**
782
- * Checks if a key exists in the map.
840
+ * Checks if a key exists in the collection.
783
841
  *
784
842
  * @param key - The key or function returning the key
785
843
  * @returns True if the key exists
786
844
  */
787
845
  has(key: K | (() => K)): boolean {
788
- if (typeof key === "function") {
789
- key = (key as () => K)();
790
- }
791
- return this._map.has(key);
846
+ return this._keyed.has(key);
792
847
  }
793
848
 
794
849
  /**
795
- * Deletes an entry from the map.
850
+ * Deletes an entry from the collection.
851
+ *
852
+ * Triggers onDelete callbacks before removal.
796
853
  *
797
854
  * @param key - The key to delete
798
855
  */
799
856
  delete(key: K): void {
800
- this._map.delete(key);
857
+ this._keyed.delete(key);
801
858
  }
802
859
 
803
860
  /**
804
- * Resets and deletes an entry from the map.
861
+ * Resets and removes an entry from the collection.
805
862
  *
806
- * Calls the value's reset() method before removing it.
863
+ * Calls the optional reset() method on the value before deletion,
864
+ * allowing for cleanup operations.
807
865
  *
808
866
  * @param key - The key to reset and delete
809
867
  */
810
868
  unget(key: K): void {
811
- const keyed = this._map.get(key);
812
- keyed?.reset();
813
- this._map.delete(key);
869
+ const item = this._keyed.getItem(key);
870
+ item.value.reset?.();
871
+ return this._keyed.delete(item.givenKey);
814
872
  }
815
873
 
816
874
  /**
817
- * Resets all entries and clears the map.
875
+ * Resets all entries by calling their optional reset() methods.
818
876
  *
819
- * Calls reset() on all values before clearing.
877
+ * Does not remove entries from the collection, only resets their state.
878
+ * Useful for cleanup without losing the collection structure.
820
879
  */
821
880
  reset(): void {
822
- this._map.forEach((keyed) => keyed.reset());
823
- this._map.clear();
824
- }
825
-
826
- /**
827
- * Returns all values in the map.
828
- *
829
- * @returns Array of all values
830
- */
831
- values(): T[] {
832
- const results: T[] = [];
833
- this.forEach((_, v) => {
834
- results.push(v);
835
- });
836
- return results;
837
- }
838
-
839
- /**
840
- * Returns all keys in the map.
841
- *
842
- * @returns Array of all keys
843
- */
844
- keys(): K[] {
845
- const results: K[] = [];
846
- this.forEach((k) => {
847
- results.push(k);
848
- });
849
- return results;
850
- }
851
-
852
- /**
853
- * Iterates over all entries in the map.
854
- *
855
- * @yields Key-value pairs
856
- */
857
- forEach(fn: (k: K, v: T, idx: number) => void): void {
858
- let idx = 0;
859
- for (const [k, v] of this._map.entries()) {
860
- fn(k, v, idx++);
881
+ for (const v of this._keyed.values()) {
882
+ v.value.reset?.();
861
883
  }
862
884
  }
863
885
 
864
- *entries(): Iterable<[K, T]> {
865
- for (const [k, v] of this._map.entries()) {
866
- yield [k, v];
867
- }
868
- }
869
- }
870
-
871
- /**
872
- * Represents a key-value pair where the value is wrapped in a Result.
873
- * @template K - The key type
874
- * @template V - The value type
875
- */
876
- export interface KeyItem<K, V> {
877
- readonly key: K;
878
- readonly value: Result<V>;
879
- }
880
-
881
- /**
882
- * Keyed collection of ResolveOnce instances.
883
- *
884
- * Manages a map of ResolveOnce instances indexed by keys, with optional LRU caching.
885
- * Each key gets its own ResolveOnce instance that can be accessed and manipulated independently.
886
- *
887
- * @template T - The return type of the ResolveOnce instances
888
- * @template K - The key type
889
- * @template CTX - Optional context type
890
- *
891
- * @example
892
- * ```typescript
893
- * const cache = new KeyedResolvOnce<number, string>();
894
- *
895
- * // Each key gets its own ResolveOnce
896
- * const result1 = cache.get('key1').once(() => expensiveCalc1());
897
- * const result2 = cache.get('key2').once(() => expensiveCalc2());
898
- * ```
899
- */
900
- export class KeyedResolvOnce<T, K = string, CTX extends NonNullable<object> = object>
901
- implements Omit<KeyedIf<ResolveOnce<T, AddKey<CTX, K>>, K>, "forEach" | "keys" | "values" | "entries">
902
- {
903
- readonly _keyed: KeyedIf<ResolveOnce<T, AddKey<CTX, K>>, K>;
904
- constructor(kp: Partial<AddKeyedParam<K, ResolveOnce<T, CTX>, CTX>> = {}) {
905
- this._keyed = new Keyed(
906
- (ctx) => new ResolveOnce<T, AddKey<CTX, K>>(ctx),
907
- kp as AddKeyedParam<K, ResolveOnce<T, AddKey<CTX, K>>, CTX>,
908
- );
909
- }
910
- keys(): K[] {
911
- const results: K[] = [];
912
- this.forEach((k) => {
913
- results.push(k.key);
914
- });
915
- return results;
916
- }
917
- values(): KeyItem<K, T>[] {
918
- const results: KeyItem<K, T>[] = [];
919
- this.forEach((v) => {
920
- results.push(v);
921
- });
922
- return results;
923
- }
924
- onSet(fn: (key: K, value: ResolveOnce<T, AddKey<CTX, K>>) => void): UnregFn {
925
- return this._keyed.onSet(fn);
926
- }
927
- onDelete(fn: (key: K, value: ResolveOnce<T, AddKey<CTX, K>>) => void): UnregFn {
928
- return this._keyed.onDelete(fn);
929
- }
930
- setParam(params: KeyedParam<K, ResolveOnce<T, AddKey<CTX, K>>>): void {
931
- this._keyed.setParam(params);
932
- }
933
- asyncGet(key: () => Promise<K>): Promise<ResolveOnce<T, AddKey<CTX, K>>> {
934
- return this._keyed.asyncGet(key);
935
- }
936
- get(key: K | (() => K)): ResolveOnce<T, AddKey<CTX, K>> {
937
- return this._keyed.get(key);
938
- }
939
- has(key: K | (() => K)): boolean {
940
- return this._keyed.has(key);
941
- }
942
- delete(key: K): void {
943
- this._keyed.delete(key);
944
- }
945
- unget(key: K): void {
946
- this._keyed.unget(key);
947
- }
948
- reset(): void {
949
- this._keyed.reset();
950
- }
951
-
952
886
  /**
953
887
  * Iterates over all completed entries, yielding key-result pairs.
954
888
  *
955
889
  * Only yields entries that have been resolved (ready state).
956
890
  * Values are wrapped in Result to distinguish success from error.
957
891
  *
958
- * @yields Key-result pairs for completed entries
892
+ * @param fn - Callback receiving KeyItem and index
893
+ *
894
+ * @example
895
+ * ```typescript
896
+ * cache.forEach((item, idx) => {
897
+ * console.log(idx, item.key);
898
+ * if (item.value.Ok) {
899
+ * console.log('Success:', item.value.unwrap());
900
+ * } else {
901
+ * console.error('Error:', item.value.unwrapErr());
902
+ * }
903
+ * });
904
+ * ```
959
905
  */
960
906
  forEach(fn: (ki: KeyItem<K, T>, idx: number) => void): void {
961
- let idx = 0;
962
- for (const [k, v] of this._keyed.entries()) {
907
+ for (const [item, idx] of this._keyed.entries()) {
908
+ const v = item.value;
909
+ const k = item.givenKey;
963
910
  if (!v.ready) {
964
911
  continue;
965
912
  }
966
913
  if (v.error) {
967
- fn({ key: k, value: Result.Err<T>(v.error) }, idx++);
914
+ fn({ key: k, value: Result.Err<T>(v.error) }, idx);
968
915
  } else {
969
- fn({ key: k, value: Result.Ok<T>(v.value as T) }, idx++);
916
+ fn({ key: k, value: Result.Ok<T>(v.value as T) }, idx);
970
917
  }
971
918
  }
972
919
  }
973
920
 
921
+ /**
922
+ * Returns an iterable of all completed entries.
923
+ *
924
+ * Only yields entries that have been resolved. Values are wrapped in Result.
925
+ *
926
+ * @returns Iterable of KeyItem entries
927
+ *
928
+ * @example
929
+ * ```typescript
930
+ * for (const item of cache.entries()) {
931
+ * console.log(item.key, item.value.Ok);
932
+ * }
933
+ * ```
934
+ */
974
935
  *entries(): Iterable<KeyItem<K, T>> {
975
936
  /* this is not optimal, but sufficient for now */
976
- for (const [k, v] of this._keyed.entries()) {
937
+ for (const [item] of this._keyed.entries()) {
938
+ const v = item.value;
939
+ const k = item.givenKey;
977
940
  if (!v.ready) {
978
941
  continue;
979
942
  }
@@ -986,37 +949,14 @@ export class KeyedResolvOnce<T, K = string, CTX extends NonNullable<object> = ob
986
949
  }
987
950
  }
988
951
 
989
- /**
990
- * Type helper that adds a key property to a context object.
991
- *
992
- * Used by keyed collections to provide the current key to factory functions and callbacks.
993
- *
994
- * @template X - The context type
995
- * @template K - The key type
996
- *
997
- * @example
998
- * ```typescript
999
- * type MyContext = { userId: string };
1000
- * type WithKey = AddKey<MyContext, number>;
1001
- * // Result: { userId: string, key: number }
1002
- * ```
1003
- */
1004
- export type AddKey<X extends NonNullable<object>, K> = X & { key: K };
1005
-
1006
- /**
1007
- * Configuration type for KeyedResolvSeq.
1008
- * @internal
1009
- */
1010
- type WithCTX<K, T, CTX extends NonNullable<object>> = KeyedParam<K, ResolveSeq<T, AddKey<CTX, K>>> & { readonly ctx: CTX };
1011
-
1012
952
  /**
1013
953
  * Keyed collection of ResolveSeq instances.
1014
954
  *
1015
955
  * Manages a map of ResolveSeq instances indexed by keys, with optional LRU caching.
1016
956
  * Each key gets its own ResolveSeq instance for sequential execution of operations.
1017
957
  *
1018
- * @template T - The return type of the ResolveSeq instances
1019
- * @template K - The key type
958
+ * @template VALUEType - The return type of the ResolveSeq instances
959
+ * @template KEYType - The key type
1020
960
  * @template CTX - Optional context type
1021
961
  *
1022
962
  * @example
@@ -1028,13 +968,30 @@ type WithCTX<K, T, CTX extends NonNullable<object>> = KeyedParam<K, ResolveSeq<T
1028
968
  * sequences.get('user2').add(() => updateUser2());
1029
969
  * ```
1030
970
  */
1031
- export class KeyedResolvSeq<T extends NonNullable<unknown>, K = string, CTX extends NonNullable<object> = object> extends Keyed<
1032
- ResolveSeq<T, AddKey<CTX, K>>,
1033
- K,
1034
- CTX
1035
- > {
1036
- constructor(kp: Partial<WithCTX<K, T, CTX>> = {}) {
1037
- super((ctx) => new ResolveSeq<T, AddKey<CTX, K>>(ctx), kp);
971
+ export class KeyedResolvSeq<
972
+ VALUEType extends NonNullable<unknown>,
973
+ KEYType = string,
974
+ CTX extends NonNullable<object> = object,
975
+ > extends KeyedNg<KEYType, ResolveSeq<VALUEType, KeyedNgItemWithoutValue<KEYType, CTX>>, CTX> {
976
+ /**
977
+ * Creates a new KeyedResolvSeq instance.
978
+ *
979
+ * @param kp - Configuration options (key2string, ctx, lru)
980
+ */
981
+ constructor(kp: Partial<Omit<KeyedNgOptions<KEYType, VALUEType, CTX>, "createValue">> = {}) {
982
+ super({
983
+ createValue: (
984
+ item: KeyedNgItem<KEYType, ResolveSeq<VALUEType, KeyedNgItemWithoutValue<KEYType, CTX>>, CTX>,
985
+ ): ResolveSeq<VALUEType, KeyedNgItemWithoutValue<KEYType, CTX>> => {
986
+ return new ResolveSeq<VALUEType, KeyedNgItemWithoutValue<KEYType, CTX>>({
987
+ ...item,
988
+ ctx: kp.ctx ?? item.ctx,
989
+ });
990
+ },
991
+ key2string: kp.key2string,
992
+ ctx: kp.ctx as CTX,
993
+ lru: kp.lru,
994
+ });
1038
995
  }
1039
996
  }
1040
997