1- import { getRef , Reference } from './get-ref ' ;
1+ import { RefInfo } from './types ' ;
22
33const objectMap = Symbol ( 'objectMap' ) ;
44const value = Symbol ( 'value' ) ;
@@ -16,6 +16,52 @@ function createCacheNode(): CacheNode {
1616
1717const EMPTY_TUPLE : unknown = Object . freeze ( [ ] ) ;
1818const tupleCache : CacheNode = createCacheNode ( ) ;
19+ const primitives = new Map < unknown , RefInfo > ( ) ;
20+ export type Reference = object & { [ value ] : Reference } ;
21+
22+ /**
23+ * Gets an object reference for any value, enabling it to be used as a WeakMap key.
24+ *
25+ * For objects (including arrays, functions, etc.), returns the object itself.
26+ * For primitives (string, number, boolean, null, undefined, symbol, bigint),
27+ * returns a cached wrapper object that represents that primitive value.
28+ *
29+ * This function ensures that the same primitive value always gets the same wrapper object,
30+ * enabling primitives to be used consistently in WeakMap-based caching scenarios.
31+ *
32+ * @param v - Any value to get a reference for
33+ * @returns An object that can be used as a WeakMap key
34+ *
35+ * @example
36+ * ```typescript
37+ * const ref1 = getRef(42);
38+ * const ref2 = getRef(42);
39+ * console.log(ref1 === ref2); // true - same wrapper for same primitive
40+ *
41+ * const obj = {};
42+ * const ref3 = getRef(obj);
43+ * console.log(ref3 === obj); // true - objects are returned as-is
44+ * ```
45+ */
46+ export function getRef ( v : unknown , basics ?: RefInfo [ ] ) : Reference {
47+ if ( typeof v === 'object' && v !== null ) return v as Reference ;
48+ let el = primitives . get ( v ) ;
49+ if ( ! el ) primitives . set ( v , ( el = { ref : { [ value ] : v } , count : 0 } ) ) ;
50+ basics ?. push ( el ) ;
51+ return el . ref as Reference ;
52+ }
53+
54+ const primitiveRegistry = new FinalizationRegistry ( ( heldValue ) => {
55+ const current = primitives . get ( heldValue ) ;
56+ if ( ! current || current . count <= 1 ) primitives . delete ( heldValue ) ;
57+ else current . count -- ;
58+ } ) ;
59+ function registerUsage ( basics : RefInfo [ ] , result : object ) {
60+ basics . forEach ( ( x ) => {
61+ x . count ++ ;
62+ primitiveRegistry . register ( result , x . ref ) ;
63+ } ) ;
64+ }
1965
2066/**
2167 * Creates a cached, immutable tuple from an array of elements.
@@ -66,9 +112,10 @@ export function tuple<T extends Readonly<Array<unknown>>>(
66112 if ( ! length ) return EMPTY_TUPLE as Readonly < T > ;
67113
68114 let current : CacheNode = tupleCache ;
115+ const basics : RefInfo [ ] = [ ] ;
69116
70117 for ( let i = 0 ; i < length ; ++ i ) {
71- const el = getRef ( elements [ i ] ) ;
118+ const el = getRef ( elements [ i ] , basics ) ;
72119
73120 const map = current [ objectMap ] ;
74121 let node = map . get ( el ) ;
@@ -81,11 +128,12 @@ export function tuple<T extends Readonly<Array<unknown>>>(
81128
82129 let ref = current [ value ] ;
83130 let result : Readonly < T > | undefined ;
84- if ( ref ) result = ref . deref ( ) as Readonly < T > ;
85- else {
131+ if ( ref ) result = ref . deref ( ) as Readonly < T > | undefined ;
132+ if ( ! result ) {
86133 current [ value ] = ref = new WeakRef (
87134 ( result = Object . freeze ( elements . slice ( ) ) as Readonly < T > ) ,
88135 ) ;
136+ registerUsage ( basics , result ) ;
89137 }
90138
91139 return result ;
0 commit comments