@inox-tools/utils 0.6.0 → 0.8.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/lazy.d.ts +182 -4
- package/dist/lazy.js +1 -1
- package/dist/lazy.js.map +1 -1
- package/package.json +5 -5
- package/src/lazy.ts +361 -8
package/dist/lazy.d.ts
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback type for attaching side effects to lazy values with a name identifier.
|
|
3
|
+
*
|
|
4
|
+
* @template T - The type of the lazy value
|
|
5
|
+
* @template O - The return type of the attachment callback (defaults to void)
|
|
6
|
+
*/
|
|
7
|
+
type LazyMapping<T, O = void> = (name: string, value: T) => O;
|
|
8
|
+
/**
|
|
9
|
+
* Utility type that extracts the inner types from an array of Lazy instances.
|
|
10
|
+
*
|
|
11
|
+
* @template T - A tuple of Lazy instances
|
|
12
|
+
*/
|
|
13
|
+
type UnwrapLazies<T extends Lazy<any>[]> = T extends [
|
|
14
|
+
Lazy<infer First>,
|
|
15
|
+
...infer Rest extends Lazy<any>[]
|
|
16
|
+
] ? [First, ...UnwrapLazies<Rest>] : [];
|
|
1
17
|
/**
|
|
2
18
|
* A lazily computed memoized value.
|
|
3
19
|
*
|
|
4
20
|
* The given factory is only constructed on first use of the value.
|
|
5
21
|
* Any subsequent use retrieves the same instance of the value.
|
|
22
|
+
*
|
|
23
|
+
* If the value is accessed while it is being created, an error is thrown to prevent circular dependencies.
|
|
24
|
+
* When that happens, the Lazy instance becomes unusable.
|
|
25
|
+
*
|
|
26
|
+
* Lazy implements the Promise interface, allowing it to be awaited directly.
|
|
27
|
+
*
|
|
28
|
+
* @template T - The type of the lazily computed value
|
|
6
29
|
*/
|
|
7
|
-
declare class Lazy<T> {
|
|
30
|
+
declare class Lazy<T> implements Promise<T> {
|
|
8
31
|
private factory;
|
|
9
|
-
private
|
|
10
|
-
private
|
|
32
|
+
private value;
|
|
33
|
+
private attachments?;
|
|
11
34
|
private constructor();
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new Lazy instance from a factory function.
|
|
37
|
+
*
|
|
38
|
+
* @param factory - A function that produces the value when first accessed
|
|
39
|
+
* @returns A new Lazy instance wrapping the factory
|
|
40
|
+
*/
|
|
12
41
|
static of<T>(factory: () => T): Lazy<T>;
|
|
13
42
|
/**
|
|
14
43
|
* Wrap the given factory into a lazily computed memoized value.
|
|
@@ -16,7 +45,156 @@ declare class Lazy<T> {
|
|
|
16
45
|
* The function will at most be called once, on the first use of the value.
|
|
17
46
|
*/
|
|
18
47
|
static wrap<T>(factory: () => T): () => T;
|
|
48
|
+
/**
|
|
49
|
+
* Gets the lazily computed value, creating it if necessary.
|
|
50
|
+
*
|
|
51
|
+
* @returns The computed value
|
|
52
|
+
* @throws Error if a circular dependency is detected during value creation
|
|
53
|
+
*/
|
|
19
54
|
get(): T;
|
|
55
|
+
ensureInitialized(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Implements Promise.then() for Promise compatibility.
|
|
58
|
+
* Allows the Lazy instance to be awaited.
|
|
59
|
+
*/
|
|
60
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
|
|
61
|
+
/**
|
|
62
|
+
* Implements Promise.catch() for Promise compatibility.
|
|
63
|
+
*/
|
|
64
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Implements Promise.finally() for Promise compatibility.
|
|
67
|
+
*/
|
|
68
|
+
finally(onfinally?: (() => void) | null | undefined): Promise<T>;
|
|
69
|
+
/** String tag for Object.prototype.toString */
|
|
70
|
+
[Symbol.toStringTag]: string;
|
|
71
|
+
/**
|
|
72
|
+
* Registers a callback to be executed when, and if, the lazy value is computed.
|
|
73
|
+
* If the value is already computed, the callback is invoked immediately.
|
|
74
|
+
*
|
|
75
|
+
* @param attach - Callback to execute with the computed value
|
|
76
|
+
* @returns This instance for chaining
|
|
77
|
+
*/
|
|
78
|
+
attach(attach: (value: T) => void): this;
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new Lazy that transforms this value using the provided function.
|
|
81
|
+
* The new Lazy is automatically evaluated when this one is evaluated.
|
|
82
|
+
* This way, if either Lazy is evaluated, both values are computed.
|
|
83
|
+
*
|
|
84
|
+
* @template O - The type of the transformed value
|
|
85
|
+
* @param mapper - Transformation function to apply to the value
|
|
86
|
+
* @returns A new Lazy instance containing the transformed value
|
|
87
|
+
*/
|
|
88
|
+
chain<O>(mapper: (value: T) => O): Lazy<O>;
|
|
89
|
+
/**
|
|
90
|
+
* Attaches a callback to multiple named lazy values.
|
|
91
|
+
* The callback receives both the name and value for each lazy when evaluated.
|
|
92
|
+
*
|
|
93
|
+
* @template T - The type of the lazy values
|
|
94
|
+
* @param lazies - Record of named Lazy instances
|
|
95
|
+
* @param attach - Callback to execute for each lazy value
|
|
96
|
+
*/
|
|
97
|
+
static attachMulti<T>(lazies: Record<string, Lazy<T>>, attach: LazyMapping<T>): void;
|
|
98
|
+
/**
|
|
99
|
+
* Chains a transformation across multiple named lazy values.
|
|
100
|
+
* Creates a new record of Lazy instances with transformed values.
|
|
101
|
+
*
|
|
102
|
+
* @template T - The type of the input lazy values
|
|
103
|
+
* @template R - The record type containing the lazy instances
|
|
104
|
+
* @template O - The type of the transformed values
|
|
105
|
+
* @param lazies - Record of named Lazy instances
|
|
106
|
+
* @param mapper - Transformation function receiving name and value
|
|
107
|
+
* @returns A new record of Lazy instances with the same keys
|
|
108
|
+
*/
|
|
109
|
+
static chainMulti<T, const R extends Record<string, Lazy<T>>, O>(lazies: R, mapper: LazyMapping<T, O>): Record<keyof R, Lazy<O>>;
|
|
110
|
+
/**
|
|
111
|
+
* Attaches a callback that is invoked when all lazy values in the array are evaluated.
|
|
112
|
+
* The callback receives all values as arguments in order.
|
|
113
|
+
*
|
|
114
|
+
* @template Ts - Tuple type of Lazy instances
|
|
115
|
+
* @param lazies - Array of Lazy instances to wait for
|
|
116
|
+
* @param attach - Callback receiving all unwrapped values
|
|
117
|
+
*/
|
|
118
|
+
static attachAll<const Ts extends Lazy<any>[]>(lazies: Ts, attach: (...args: UnwrapLazies<Ts>) => void): void;
|
|
119
|
+
/**
|
|
120
|
+
* Creates a new Lazy that combines all values from the given lazy instances.
|
|
121
|
+
* The transformation is invoked when all input lazies are evaluated.
|
|
122
|
+
*
|
|
123
|
+
* @template Ts - Tuple type of Lazy instances
|
|
124
|
+
* @template O - The type of the combined result
|
|
125
|
+
* @param lazies - Array of Lazy instances to combine
|
|
126
|
+
* @param mapper - Transformation function receiving all unwrapped values
|
|
127
|
+
* @returns A new Lazy instance containing the combined result
|
|
128
|
+
*/
|
|
129
|
+
static chainAll<const Ts extends Lazy<any>[], O>(lazies: Ts, mapper: (...args: UnwrapLazies<Ts>) => O): Lazy<O>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A keyed lazy value store that memoizes values by string key.
|
|
133
|
+
*
|
|
134
|
+
* Each key maps to a lazily computed value that is only created on first access.
|
|
135
|
+
* Supports attachments that are notified when new values are created.
|
|
136
|
+
* @template T - The type of values stored
|
|
137
|
+
*/
|
|
138
|
+
declare class LazyKeyed<T> {
|
|
139
|
+
private factory;
|
|
140
|
+
/** Map of key to either a tuple containing the value or CREATING symbol */
|
|
141
|
+
private readonly instances;
|
|
142
|
+
/** List of callbacks to invoke when any value is created */
|
|
143
|
+
private readonly attachments;
|
|
144
|
+
private constructor();
|
|
145
|
+
/**
|
|
146
|
+
* Creates a new LazyKeyed instance from a factory function.
|
|
147
|
+
* @param factory - A function that produces a value for a given key
|
|
148
|
+
* @returns A new LazyKeyed instance
|
|
149
|
+
*/
|
|
150
|
+
static of<T>(factory: (key: string) => T): LazyKeyed<T>;
|
|
151
|
+
/**
|
|
152
|
+
* Gets the value for the given key, creating it if necessary.
|
|
153
|
+
* @param key - The key to look up or create
|
|
154
|
+
* @returns The value associated with the key
|
|
155
|
+
* @throws Error if a circular dependency is detected during value creation
|
|
156
|
+
*/
|
|
157
|
+
get(key: string): T;
|
|
158
|
+
/**
|
|
159
|
+
* Pre-initializes values for the given keys.
|
|
160
|
+
* Useful for eagerly creating values that will be needed later.
|
|
161
|
+
* @param key - The first key to reserve
|
|
162
|
+
* @param keys - Additional keys to reserve
|
|
163
|
+
* @returns This instance for chaining
|
|
164
|
+
*/
|
|
165
|
+
reserve(key: string, ...keys: string[]): this;
|
|
166
|
+
/**
|
|
167
|
+
* Registers a callback to be executed for each value when created.
|
|
168
|
+
* Immediately invokes the callback for any already-created values.
|
|
169
|
+
* @param attach - Callback receiving the key and value
|
|
170
|
+
* @returns This instance for chaining
|
|
171
|
+
*/
|
|
172
|
+
attach(attach: LazyMapping<T>): this;
|
|
173
|
+
/**
|
|
174
|
+
* Registers a callback for a specific key only.
|
|
175
|
+
* The callback is invoked when the value for that key is created.
|
|
176
|
+
* @param key - The key to watch
|
|
177
|
+
* @param attach - Callback receiving the value
|
|
178
|
+
* @returns This instance for chaining
|
|
179
|
+
*/
|
|
180
|
+
attachOne(key: string, attach: (value: T) => void): this;
|
|
181
|
+
/**
|
|
182
|
+
* Creates a new LazyKeyed that transforms values using the provided function.
|
|
183
|
+
* Values in the new LazyKeyed are automatically created when source values are created.
|
|
184
|
+
* @template O - The type of the transformed values
|
|
185
|
+
* @param mapper - Transformation function receiving key and value
|
|
186
|
+
* @returns A new LazyKeyed instance with transformed values
|
|
187
|
+
*/
|
|
188
|
+
chain<O>(mapper: LazyMapping<T, O>): LazyKeyed<O>;
|
|
189
|
+
/**
|
|
190
|
+
* Creates a Lazy that transforms the value for a specific key.
|
|
191
|
+
* The Lazy is automatically evaluated when the source value is created.
|
|
192
|
+
* @template O - The type of the transformed value
|
|
193
|
+
* @param key - The key to transform
|
|
194
|
+
* @param mapper - Transformation function receiving the value
|
|
195
|
+
* @returns A Lazy instance containing the transformed value
|
|
196
|
+
*/
|
|
197
|
+
chainOne<O>(key: string, mapper: (value: T) => O): Lazy<O>;
|
|
20
198
|
}
|
|
21
199
|
|
|
22
|
-
export { Lazy };
|
|
200
|
+
export { Lazy, LazyKeyed };
|
package/dist/lazy.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
class a{constructor(
|
|
1
|
+
const s=Symbol("creating"),c=Symbol("missing"),l=()=>{throw new Error("This Lazy value has already been consumed.")};class a{constructor(t){this.factory=t;}value=c;attachments=[];static of(t){return new a(t)}static wrap(t){const e=a.of(t);return e.get.bind(e)}get(){if(this.value===c){this.value=s;const t=this.factory();this.factory=l,this.value=t;for(const e of this.attachments)e(t);delete this.attachments;}if(this.value===s)throw new Error("Circular dependency detected during Lazy value creation.");return this.value}ensureInitialized(){this.value!==s&&this.get();}then(t,e){return Promise.resolve().then(()=>this.get()).then(t,e)}catch(t){return this.then(void 0,t)}finally(t){return this.then().finally(t)}[Symbol.toStringTag]="Lazy";attach(t){return this.value!==c&&this.value!==s?t(this.value):this.attachments.push(t),this}chain(t){const e=a.of(()=>t(this.get()));return this.attach(()=>e.ensureInitialized()),e}static attachMulti(t,e){for(const[i,n]of Object.entries(t))n.attach(r=>e(i,r));}static chainMulti(t,e){const i={};for(const[n,r]of Object.entries(t))i[n]=r.chain(o=>e(n,o));return i}static attachAll(t,e){function i(n=0){if(n>=t.length){e(...t.map(r=>r.get()));return}t[n].attach(()=>{i(n+1);});}i();}static chainAll(t,e){const i=a.of(()=>e(...t.map(n=>n.get())));return this.attachAll(t,()=>i.ensureInitialized()),i}}class u{constructor(t){this.factory=t;}instances=new Map;attachments=[];static of(t){return new this(t)}get(t){const e=this.instances.get(t);if(e===s)throw new Error("Circular dependency detected during LazyKeyed value creation with value: "+t);if(e!==void 0)return e[0];this.instances.set(t,s);const i=this.factory(t);this.instances.set(t,[i]);for(const n of this.attachments)n(t,i);return i}reserve(t,...e){e.unshift(t);for(const i of e)this.instances.get(i)===void 0&&this.get(i);return this}attach(t){for(const[e,i]of this.instances.entries())i!==s&&t(e,i[0]);return this.attachments.push(t),this}attachOne(t,e){return this.attach((i,n)=>{i===t&&e(n);})}chain(t){const e=u.of(i=>t(i,this.get(i)));return this.attach(i=>{e.reserve(i);}),e}chainOne(t,e){const i=a.of(()=>e(this.get(t)));return this.attachOne(t,()=>i.ensureInitialized()),i}}export{a as Lazy,u as LazyKeyed};//# sourceMappingURL=lazy.js.map
|
|
2
2
|
//# sourceMappingURL=lazy.js.map
|
package/dist/lazy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lazy.ts"],"names":["Lazy","factory","lazy"],"mappings":"AAMO,MAAMA,CAAQ,CAIZ,WAAA,CAAoBC,CAAAA,CAAkB,CAAlB,aAAAA,EAAmB,CAHvC,WAAA,CAAc,KAAA,CACd,KAAA,CAIR,OAAc,GAAMA,CAAAA,CAA2B,CAC9C,OAAO,IAAI,IAAA,CAAKA,CAAO,CACxB,CAOA,OAAc,IAAA,CAAQA,CAAAA,CAA2B,CAChD,MAAMC,EAAOF,CAAAA,CAAK,EAAA,CAAGC,CAAO,CAAA,CAC5B,OAAOC,CAAAA,CAAK,IAAI,IAAA,CAAKA,CAAI,CAC1B,CAEO,GAAA,EAAS,CACf,OAAK,IAAA,CAAK,WAAA,GACT,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,OAAA,EAAQ,CAC1B,IAAA,CAAK,WAAA,CAAc,IAAA,CAAA,CAGb,IAAA,CAAK,KACb,CACD","file":"lazy.js","sourcesContent":["/**\n * A lazily computed memoized value.\n *\n * The given factory is only constructed on first use of the value.\n * Any subsequent use retrieves the same instance of the value.\n */\nexport class Lazy<T> {\n\tprivate initialized = false;\n\tprivate value?: T;\n\n\tprivate constructor(private factory: () => T) {}\n\n\tpublic static of<T>(factory: () => T): Lazy<T> {\n\t\treturn new this(factory);\n\t}\n\n\t/**\n\t * Wrap the given factory into a lazily computed memoized value.\n\t *\n\t * The function will at most be called once, on the first use of the value.\n\t */\n\tpublic static wrap<T>(factory: () => T): () => T {\n\t\tconst lazy = Lazy.of(factory);\n\t\treturn lazy.get.bind(lazy);\n\t}\n\n\tpublic get(): T {\n\t\tif (!this.initialized) {\n\t\t\tthis.value = this.factory();\n\t\t\tthis.initialized = true;\n\t\t}\n\n\t\treturn this.value!;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lazy.ts"],"names":["CREATING","MISSING","SPENT_FACTORY","Lazy","factory","lazy","value","attach","onfulfilled","onrejected","onfinally","mapper","other","lazies","name","result","attachNext","index","l","LazyKeyed","key","stored","newInstance","keys","k","instance","newKeyed"],"mappings":"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","file":"lazy.js","sourcesContent":["/**\n * Callback type for attaching side effects to lazy values with a name identifier.\n *\n * @template T - The type of the lazy value\n * @template O - The return type of the attachment callback (defaults to void)\n */\ntype LazyMapping<T, O = void> = (name: string, value: T) => O;\n\n/**\n * Utility type that extracts the inner types from an array of Lazy instances.\n *\n * @template T - A tuple of Lazy instances\n */\ntype UnwrapLazies<T extends Lazy<any>[]> = T extends [\n\tLazy<infer First>,\n\t...infer Rest extends Lazy<any>[],\n]\n\t? [First, ...UnwrapLazies<Rest>]\n\t: [];\n\n/** Symbol indicating a lazy value is currently being created (for circular dependency detection) */\nconst CREATING = Symbol('creating');\n/** Symbol indicating a lazy value has not yet been initialized */\nconst MISSING = Symbol('missing');\n\nconst SPENT_FACTORY = (): never => {\n\tthrow new Error('This Lazy value has already been consumed.');\n};\n\n/**\n * A lazily computed memoized value.\n *\n * The given factory is only constructed on first use of the value.\n * Any subsequent use retrieves the same instance of the value.\n *\n * If the value is accessed while it is being created, an error is thrown to prevent circular dependencies.\n * When that happens, the Lazy instance becomes unusable.\n *\n * Lazy implements the Promise interface, allowing it to be awaited directly.\n *\n * @template T - The type of the lazily computed value\n */\nexport class Lazy<T> implements Promise<T> {\n\tprivate value: T | typeof CREATING | typeof MISSING = MISSING;\n\n\tprivate attachments?: ((value: T) => void)[] = [];\n\n\tprivate constructor(private factory: () => T) {}\n\n\t/**\n\t * Creates a new Lazy instance from a factory function.\n\t *\n\t * @param factory - A function that produces the value when first accessed\n\t * @returns A new Lazy instance wrapping the factory\n\t */\n\tpublic static of<T>(factory: () => T): Lazy<T> {\n\t\treturn new Lazy(factory);\n\t}\n\n\t/**\n\t * Wrap the given factory into a lazily computed memoized value.\n\t *\n\t * The function will at most be called once, on the first use of the value.\n\t */\n\tpublic static wrap<T>(factory: () => T): () => T {\n\t\tconst lazy = Lazy.of(factory);\n\t\treturn lazy.get.bind(lazy);\n\t}\n\n\t/**\n\t * Gets the lazily computed value, creating it if necessary.\n\t *\n\t * @returns The computed value\n\t * @throws Error if a circular dependency is detected during value creation\n\t */\n\tpublic get(): T {\n\t\tif (this.value === MISSING) {\n\t\t\tthis.value = CREATING;\n\t\t\tconst value = this.factory();\n\t\t\t// Mark factory as spent to prevent reuse and release references to closure.\n\t\t\tthis.factory = SPENT_FACTORY;\n\t\t\tthis.value = value;\n\t\t\tfor (const attach of this.attachments!) {\n\t\t\t\tattach(value);\n\t\t\t}\n\t\t\tdelete this.attachments;\n\t\t}\n\t\tif (this.value === CREATING) {\n\t\t\tthrow new Error('Circular dependency detected during Lazy value creation.');\n\t\t}\n\n\t\treturn this.value;\n\t}\n\n\tpublic ensureInitialized(): void {\n\t\tif (this.value === CREATING) {\n\t\t\t// this Lazy is already being created; do nothing to avoid circular dependency\n\t\t\treturn;\n\t\t}\n\t\tthis.get();\n\t}\n\n\t/**\n\t * Implements Promise.then() for Promise compatibility.\n\t * Allows the Lazy instance to be awaited.\n\t */\n\tpublic then<TResult1 = T, TResult2 = never>(\n\t\tonfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n\t\tonrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined\n\t): Promise<TResult1 | TResult2> {\n\t\treturn Promise.resolve()\n\t\t\t.then(() => this.get())\n\t\t\t.then(onfulfilled, onrejected);\n\t}\n\n\t/**\n\t * Implements Promise.catch() for Promise compatibility.\n\t */\n\tpublic catch<TResult = never>(\n\t\tonrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined\n\t): Promise<T | TResult> {\n\t\treturn this.then(undefined, onrejected);\n\t}\n\n\t/**\n\t * Implements Promise.finally() for Promise compatibility.\n\t */\n\tpublic finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n\t\treturn this.then().finally(onfinally);\n\t}\n\n\t/** String tag for Object.prototype.toString */\n\t[Symbol.toStringTag]: string = 'Lazy';\n\n\t/**\n\t * Registers a callback to be executed when, and if, the lazy value is computed.\n\t * If the value is already computed, the callback is invoked immediately.\n\t *\n\t * @param attach - Callback to execute with the computed value\n\t * @returns This instance for chaining\n\t */\n\tpublic attach(attach: (value: T) => void): this {\n\t\tif (this.value !== MISSING && this.value !== CREATING) {\n\t\t\tattach(this.value as T); // Force evaluation if already present\n\t\t} else {\n\t\t\tthis.attachments!.push(attach);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Creates a new Lazy that transforms this value using the provided function.\n\t * The new Lazy is automatically evaluated when this one is evaluated.\n\t * This way, if either Lazy is evaluated, both values are computed.\n\t *\n\t * @template O - The type of the transformed value\n\t * @param mapper - Transformation function to apply to the value\n\t * @returns A new Lazy instance containing the transformed value\n\t */\n\tpublic chain<O>(mapper: (value: T) => O): Lazy<O> {\n\t\tconst other = Lazy.of(() => mapper(this.get()));\n\n\t\tthis.attach(() => other.ensureInitialized());\n\n\t\treturn other;\n\t}\n\n\t/**\n\t * Attaches a callback to multiple named lazy values.\n\t * The callback receives both the name and value for each lazy when evaluated.\n\t *\n\t * @template T - The type of the lazy values\n\t * @param lazies - Record of named Lazy instances\n\t * @param attach - Callback to execute for each lazy value\n\t */\n\tpublic static attachMulti<T>(lazies: Record<string, Lazy<T>>, attach: LazyMapping<T>): void {\n\t\tfor (const [name, lazy] of Object.entries(lazies)) {\n\t\t\tlazy.attach((value) => attach(name, value));\n\t\t}\n\t}\n\n\t/**\n\t * Chains a transformation across multiple named lazy values.\n\t * Creates a new record of Lazy instances with transformed values.\n\t *\n\t * @template T - The type of the input lazy values\n\t * @template R - The record type containing the lazy instances\n\t * @template O - The type of the transformed values\n\t * @param lazies - Record of named Lazy instances\n\t * @param mapper - Transformation function receiving name and value\n\t * @returns A new record of Lazy instances with the same keys\n\t */\n\tpublic static chainMulti<T, const R extends Record<string, Lazy<T>>, O>(\n\t\tlazies: R,\n\t\tmapper: LazyMapping<T, O>\n\t): Record<keyof R, Lazy<O>> {\n\t\tconst result: Record<string, Lazy<O>> = {};\n\t\tfor (const [name, lazy] of Object.entries(lazies)) {\n\t\t\tresult[name] = lazy.chain((value) => mapper(name, value));\n\t\t}\n\n\t\treturn result as Record<keyof R, Lazy<O>>;\n\t}\n\n\t/**\n\t * Attaches a callback that is invoked when all lazy values in the array are evaluated.\n\t * The callback receives all values as arguments in order.\n\t *\n\t * @template Ts - Tuple type of Lazy instances\n\t * @param lazies - Array of Lazy instances to wait for\n\t * @param attach - Callback receiving all unwrapped values\n\t */\n\tpublic static attachAll<const Ts extends Lazy<any>[]>(\n\t\tlazies: Ts,\n\t\tattach: (...args: UnwrapLazies<Ts>) => void\n\t): void {\n\t\tfunction attachNext(index = 0) {\n\t\t\tif (index >= lazies.length) {\n\t\t\t\tattach(...(lazies.map((l) => l.get()) as UnwrapLazies<Ts>));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlazies[index].attach(() => {\n\t\t\t\tattachNext(index + 1);\n\t\t\t});\n\t\t}\n\n\t\tattachNext();\n\t}\n\n\t/**\n\t * Creates a new Lazy that combines all values from the given lazy instances.\n\t * The transformation is invoked when all input lazies are evaluated.\n\t *\n\t * @template Ts - Tuple type of Lazy instances\n\t * @template O - The type of the combined result\n\t * @param lazies - Array of Lazy instances to combine\n\t * @param mapper - Transformation function receiving all unwrapped values\n\t * @returns A new Lazy instance containing the combined result\n\t */\n\tpublic static chainAll<const Ts extends Lazy<any>[], O>(\n\t\tlazies: Ts,\n\t\tmapper: (...args: UnwrapLazies<Ts>) => O\n\t): Lazy<O> {\n\t\tconst lazy = Lazy.of(() => mapper(...(lazies.map((l) => l.get()) as UnwrapLazies<Ts>)));\n\n\t\tthis.attachAll(lazies, () => lazy.ensureInitialized());\n\n\t\treturn lazy;\n\t}\n}\n\n/**\n * A keyed lazy value store that memoizes values by string key.\n *\n * Each key maps to a lazily computed value that is only created on first access.\n * Supports attachments that are notified when new values are created.\n * @template T - The type of values stored\n */\nexport class LazyKeyed<T> {\n\t/** Map of key to either a tuple containing the value or CREATING symbol */\n\tprivate readonly instances = new Map<string, [T] | typeof CREATING>();\n\n\t/** List of callbacks to invoke when any value is created */\n\tprivate readonly attachments: LazyMapping<T>[] = [];\n\n\tprivate constructor(private factory: (key: string) => T) {}\n\n\t/**\n\t * Creates a new LazyKeyed instance from a factory function.\n\t * @param factory - A function that produces a value for a given key\n\t * @returns A new LazyKeyed instance\n\t */\n\tpublic static of<T>(factory: (key: string) => T): LazyKeyed<T> {\n\t\treturn new this(factory);\n\t}\n\n\t/**\n\t * Gets the value for the given key, creating it if necessary.\n\t * @param key - The key to look up or create\n\t * @returns The value associated with the key\n\t * @throws Error if a circular dependency is detected during value creation\n\t */\n\tpublic get(key: string): T {\n\t\tconst stored = this.instances.get(key);\n\n\t\tif (stored === CREATING) {\n\t\t\tthrow new Error(\n\t\t\t\t'Circular dependency detected during LazyKeyed value creation with value: ' + key\n\t\t\t);\n\t\t}\n\n\t\tif (stored !== undefined) {\n\t\t\treturn stored[0];\n\t\t}\n\n\t\tthis.instances.set(key, CREATING);\n\n\t\tconst newInstance = this.factory(key);\n\n\t\tthis.instances.set(key, [newInstance]);\n\n\t\tfor (const attach of this.attachments) {\n\t\t\tattach(key, newInstance);\n\t\t}\n\n\t\treturn newInstance;\n\t}\n\n\t/**\n\t * Pre-initializes values for the given keys.\n\t * Useful for eagerly creating values that will be needed later.\n\t * @param key - The first key to reserve\n\t * @param keys - Additional keys to reserve\n\t * @returns This instance for chaining\n\t */\n\tpublic reserve(key: string, ...keys: string[]): this {\n\t\tkeys.unshift(key);\n\t\tfor (const k of keys) {\n\t\t\tif (this.instances.get(k) === undefined) {\n\t\t\t\tthis.get(k);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Registers a callback to be executed for each value when created.\n\t * Immediately invokes the callback for any already-created values.\n\t * @param attach - Callback receiving the key and value\n\t * @returns This instance for chaining\n\t */\n\tpublic attach(attach: LazyMapping<T>): this {\n\t\tfor (const [name, instance] of this.instances.entries()) {\n\t\t\tif (instance !== CREATING) {\n\t\t\t\tattach(name, instance[0]);\n\t\t\t}\n\t\t}\n\t\tthis.attachments.push(attach);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Registers a callback for a specific key only.\n\t * The callback is invoked when the value for that key is created.\n\t * @param key - The key to watch\n\t * @param attach - Callback receiving the value\n\t * @returns This instance for chaining\n\t */\n\tpublic attachOne(key: string, attach: (value: T) => void): this {\n\t\treturn this.attach((name, value) => {\n\t\t\tif (name === key) {\n\t\t\t\tattach(value);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Creates a new LazyKeyed that transforms values using the provided function.\n\t * Values in the new LazyKeyed are automatically created when source values are created.\n\t * @template O - The type of the transformed values\n\t * @param mapper - Transformation function receiving key and value\n\t * @returns A new LazyKeyed instance with transformed values\n\t */\n\tpublic chain<O>(mapper: LazyMapping<T, O>): LazyKeyed<O> {\n\t\tconst newKeyed = LazyKeyed.of((key) => mapper(key, this.get(key)));\n\n\t\tthis.attach((key) => {\n\t\t\tnewKeyed.reserve(key);\n\t\t});\n\n\t\treturn newKeyed;\n\t}\n\n\t/**\n\t * Creates a Lazy that transforms the value for a specific key.\n\t * The Lazy is automatically evaluated when the source value is created.\n\t * @template O - The type of the transformed value\n\t * @param key - The key to transform\n\t * @param mapper - Transformation function receiving the value\n\t * @returns A Lazy instance containing the transformed value\n\t */\n\tpublic chainOne<O>(key: string, mapper: (value: T) => O): Lazy<O> {\n\t\tconst lazy = Lazy.of(() => mapper(this.get(key)));\n\t\tthis.attachOne(key, () => lazy.ensureInitialized());\n\t\treturn lazy;\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inox-tools/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A collection of utilities used throughout Inox Tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"utilities"
|
|
@@ -22,18 +22,18 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/hast": "^3.0.4",
|
|
24
24
|
"@types/mdast": "^4.0.4",
|
|
25
|
-
"@types/node": "^24.0
|
|
25
|
+
"@types/node": "^24.3.0",
|
|
26
26
|
"@types/unist": "^3.0.3",
|
|
27
27
|
"@types/xast": "^2.0.4",
|
|
28
28
|
"@vitest/coverage-v8": "^3.2.4",
|
|
29
29
|
"@vitest/ui": "^3.2.4",
|
|
30
|
-
"astro": "^5.
|
|
30
|
+
"astro": "^5.13.3",
|
|
31
31
|
"jest-extended": "^6.0.0",
|
|
32
32
|
"mdast-util-from-markdown": "^2.0.2",
|
|
33
33
|
"tsup": "8.5.0",
|
|
34
|
-
"typescript": "^5.
|
|
34
|
+
"typescript": "^5.9.2",
|
|
35
35
|
"unist-util-is": "^6.0.0",
|
|
36
|
-
"vite": "^7.
|
|
36
|
+
"vite": "^7.1.3",
|
|
37
37
|
"vitest": "^3.2.4"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
package/src/lazy.ts
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback type for attaching side effects to lazy values with a name identifier.
|
|
3
|
+
*
|
|
4
|
+
* @template T - The type of the lazy value
|
|
5
|
+
* @template O - The return type of the attachment callback (defaults to void)
|
|
6
|
+
*/
|
|
7
|
+
type LazyMapping<T, O = void> = (name: string, value: T) => O;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Utility type that extracts the inner types from an array of Lazy instances.
|
|
11
|
+
*
|
|
12
|
+
* @template T - A tuple of Lazy instances
|
|
13
|
+
*/
|
|
14
|
+
type UnwrapLazies<T extends Lazy<any>[]> = T extends [
|
|
15
|
+
Lazy<infer First>,
|
|
16
|
+
...infer Rest extends Lazy<any>[],
|
|
17
|
+
]
|
|
18
|
+
? [First, ...UnwrapLazies<Rest>]
|
|
19
|
+
: [];
|
|
20
|
+
|
|
21
|
+
/** Symbol indicating a lazy value is currently being created (for circular dependency detection) */
|
|
22
|
+
const CREATING = Symbol('creating');
|
|
23
|
+
/** Symbol indicating a lazy value has not yet been initialized */
|
|
24
|
+
const MISSING = Symbol('missing');
|
|
25
|
+
|
|
26
|
+
const SPENT_FACTORY = (): never => {
|
|
27
|
+
throw new Error('This Lazy value has already been consumed.');
|
|
28
|
+
};
|
|
29
|
+
|
|
1
30
|
/**
|
|
2
31
|
* A lazily computed memoized value.
|
|
3
32
|
*
|
|
4
33
|
* The given factory is only constructed on first use of the value.
|
|
5
34
|
* Any subsequent use retrieves the same instance of the value.
|
|
35
|
+
*
|
|
36
|
+
* If the value is accessed while it is being created, an error is thrown to prevent circular dependencies.
|
|
37
|
+
* When that happens, the Lazy instance becomes unusable.
|
|
38
|
+
*
|
|
39
|
+
* Lazy implements the Promise interface, allowing it to be awaited directly.
|
|
40
|
+
*
|
|
41
|
+
* @template T - The type of the lazily computed value
|
|
6
42
|
*/
|
|
7
|
-
export class Lazy<T> {
|
|
8
|
-
private
|
|
9
|
-
|
|
43
|
+
export class Lazy<T> implements Promise<T> {
|
|
44
|
+
private value: T | typeof CREATING | typeof MISSING = MISSING;
|
|
45
|
+
|
|
46
|
+
private attachments?: ((value: T) => void)[] = [];
|
|
10
47
|
|
|
11
48
|
private constructor(private factory: () => T) {}
|
|
12
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new Lazy instance from a factory function.
|
|
52
|
+
*
|
|
53
|
+
* @param factory - A function that produces the value when first accessed
|
|
54
|
+
* @returns A new Lazy instance wrapping the factory
|
|
55
|
+
*/
|
|
13
56
|
public static of<T>(factory: () => T): Lazy<T> {
|
|
14
|
-
return new
|
|
57
|
+
return new Lazy(factory);
|
|
15
58
|
}
|
|
16
59
|
|
|
17
60
|
/**
|
|
@@ -24,12 +67,322 @@ export class Lazy<T> {
|
|
|
24
67
|
return lazy.get.bind(lazy);
|
|
25
68
|
}
|
|
26
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Gets the lazily computed value, creating it if necessary.
|
|
72
|
+
*
|
|
73
|
+
* @returns The computed value
|
|
74
|
+
* @throws Error if a circular dependency is detected during value creation
|
|
75
|
+
*/
|
|
27
76
|
public get(): T {
|
|
28
|
-
if (
|
|
29
|
-
this.value =
|
|
30
|
-
|
|
77
|
+
if (this.value === MISSING) {
|
|
78
|
+
this.value = CREATING;
|
|
79
|
+
const value = this.factory();
|
|
80
|
+
// Mark factory as spent to prevent reuse and release references to closure.
|
|
81
|
+
this.factory = SPENT_FACTORY;
|
|
82
|
+
this.value = value;
|
|
83
|
+
for (const attach of this.attachments!) {
|
|
84
|
+
attach(value);
|
|
85
|
+
}
|
|
86
|
+
delete this.attachments;
|
|
87
|
+
}
|
|
88
|
+
if (this.value === CREATING) {
|
|
89
|
+
throw new Error('Circular dependency detected during Lazy value creation.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this.value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public ensureInitialized(): void {
|
|
96
|
+
if (this.value === CREATING) {
|
|
97
|
+
// this Lazy is already being created; do nothing to avoid circular dependency
|
|
98
|
+
return;
|
|
31
99
|
}
|
|
100
|
+
this.get();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Implements Promise.then() for Promise compatibility.
|
|
105
|
+
* Allows the Lazy instance to be awaited.
|
|
106
|
+
*/
|
|
107
|
+
public then<TResult1 = T, TResult2 = never>(
|
|
108
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
|
|
109
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
|
|
110
|
+
): Promise<TResult1 | TResult2> {
|
|
111
|
+
return Promise.resolve()
|
|
112
|
+
.then(() => this.get())
|
|
113
|
+
.then(onfulfilled, onrejected);
|
|
114
|
+
}
|
|
32
115
|
|
|
33
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Implements Promise.catch() for Promise compatibility.
|
|
118
|
+
*/
|
|
119
|
+
public catch<TResult = never>(
|
|
120
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined
|
|
121
|
+
): Promise<T | TResult> {
|
|
122
|
+
return this.then(undefined, onrejected);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Implements Promise.finally() for Promise compatibility.
|
|
127
|
+
*/
|
|
128
|
+
public finally(onfinally?: (() => void) | null | undefined): Promise<T> {
|
|
129
|
+
return this.then().finally(onfinally);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** String tag for Object.prototype.toString */
|
|
133
|
+
[Symbol.toStringTag]: string = 'Lazy';
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Registers a callback to be executed when, and if, the lazy value is computed.
|
|
137
|
+
* If the value is already computed, the callback is invoked immediately.
|
|
138
|
+
*
|
|
139
|
+
* @param attach - Callback to execute with the computed value
|
|
140
|
+
* @returns This instance for chaining
|
|
141
|
+
*/
|
|
142
|
+
public attach(attach: (value: T) => void): this {
|
|
143
|
+
if (this.value !== MISSING && this.value !== CREATING) {
|
|
144
|
+
attach(this.value as T); // Force evaluation if already present
|
|
145
|
+
} else {
|
|
146
|
+
this.attachments!.push(attach);
|
|
147
|
+
}
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates a new Lazy that transforms this value using the provided function.
|
|
153
|
+
* The new Lazy is automatically evaluated when this one is evaluated.
|
|
154
|
+
* This way, if either Lazy is evaluated, both values are computed.
|
|
155
|
+
*
|
|
156
|
+
* @template O - The type of the transformed value
|
|
157
|
+
* @param mapper - Transformation function to apply to the value
|
|
158
|
+
* @returns A new Lazy instance containing the transformed value
|
|
159
|
+
*/
|
|
160
|
+
public chain<O>(mapper: (value: T) => O): Lazy<O> {
|
|
161
|
+
const other = Lazy.of(() => mapper(this.get()));
|
|
162
|
+
|
|
163
|
+
this.attach(() => other.ensureInitialized());
|
|
164
|
+
|
|
165
|
+
return other;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Attaches a callback to multiple named lazy values.
|
|
170
|
+
* The callback receives both the name and value for each lazy when evaluated.
|
|
171
|
+
*
|
|
172
|
+
* @template T - The type of the lazy values
|
|
173
|
+
* @param lazies - Record of named Lazy instances
|
|
174
|
+
* @param attach - Callback to execute for each lazy value
|
|
175
|
+
*/
|
|
176
|
+
public static attachMulti<T>(lazies: Record<string, Lazy<T>>, attach: LazyMapping<T>): void {
|
|
177
|
+
for (const [name, lazy] of Object.entries(lazies)) {
|
|
178
|
+
lazy.attach((value) => attach(name, value));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Chains a transformation across multiple named lazy values.
|
|
184
|
+
* Creates a new record of Lazy instances with transformed values.
|
|
185
|
+
*
|
|
186
|
+
* @template T - The type of the input lazy values
|
|
187
|
+
* @template R - The record type containing the lazy instances
|
|
188
|
+
* @template O - The type of the transformed values
|
|
189
|
+
* @param lazies - Record of named Lazy instances
|
|
190
|
+
* @param mapper - Transformation function receiving name and value
|
|
191
|
+
* @returns A new record of Lazy instances with the same keys
|
|
192
|
+
*/
|
|
193
|
+
public static chainMulti<T, const R extends Record<string, Lazy<T>>, O>(
|
|
194
|
+
lazies: R,
|
|
195
|
+
mapper: LazyMapping<T, O>
|
|
196
|
+
): Record<keyof R, Lazy<O>> {
|
|
197
|
+
const result: Record<string, Lazy<O>> = {};
|
|
198
|
+
for (const [name, lazy] of Object.entries(lazies)) {
|
|
199
|
+
result[name] = lazy.chain((value) => mapper(name, value));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result as Record<keyof R, Lazy<O>>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Attaches a callback that is invoked when all lazy values in the array are evaluated.
|
|
207
|
+
* The callback receives all values as arguments in order.
|
|
208
|
+
*
|
|
209
|
+
* @template Ts - Tuple type of Lazy instances
|
|
210
|
+
* @param lazies - Array of Lazy instances to wait for
|
|
211
|
+
* @param attach - Callback receiving all unwrapped values
|
|
212
|
+
*/
|
|
213
|
+
public static attachAll<const Ts extends Lazy<any>[]>(
|
|
214
|
+
lazies: Ts,
|
|
215
|
+
attach: (...args: UnwrapLazies<Ts>) => void
|
|
216
|
+
): void {
|
|
217
|
+
function attachNext(index = 0) {
|
|
218
|
+
if (index >= lazies.length) {
|
|
219
|
+
attach(...(lazies.map((l) => l.get()) as UnwrapLazies<Ts>));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
lazies[index].attach(() => {
|
|
224
|
+
attachNext(index + 1);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
attachNext();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Creates a new Lazy that combines all values from the given lazy instances.
|
|
233
|
+
* The transformation is invoked when all input lazies are evaluated.
|
|
234
|
+
*
|
|
235
|
+
* @template Ts - Tuple type of Lazy instances
|
|
236
|
+
* @template O - The type of the combined result
|
|
237
|
+
* @param lazies - Array of Lazy instances to combine
|
|
238
|
+
* @param mapper - Transformation function receiving all unwrapped values
|
|
239
|
+
* @returns A new Lazy instance containing the combined result
|
|
240
|
+
*/
|
|
241
|
+
public static chainAll<const Ts extends Lazy<any>[], O>(
|
|
242
|
+
lazies: Ts,
|
|
243
|
+
mapper: (...args: UnwrapLazies<Ts>) => O
|
|
244
|
+
): Lazy<O> {
|
|
245
|
+
const lazy = Lazy.of(() => mapper(...(lazies.map((l) => l.get()) as UnwrapLazies<Ts>)));
|
|
246
|
+
|
|
247
|
+
this.attachAll(lazies, () => lazy.ensureInitialized());
|
|
248
|
+
|
|
249
|
+
return lazy;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* A keyed lazy value store that memoizes values by string key.
|
|
255
|
+
*
|
|
256
|
+
* Each key maps to a lazily computed value that is only created on first access.
|
|
257
|
+
* Supports attachments that are notified when new values are created.
|
|
258
|
+
* @template T - The type of values stored
|
|
259
|
+
*/
|
|
260
|
+
export class LazyKeyed<T> {
|
|
261
|
+
/** Map of key to either a tuple containing the value or CREATING symbol */
|
|
262
|
+
private readonly instances = new Map<string, [T] | typeof CREATING>();
|
|
263
|
+
|
|
264
|
+
/** List of callbacks to invoke when any value is created */
|
|
265
|
+
private readonly attachments: LazyMapping<T>[] = [];
|
|
266
|
+
|
|
267
|
+
private constructor(private factory: (key: string) => T) {}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Creates a new LazyKeyed instance from a factory function.
|
|
271
|
+
* @param factory - A function that produces a value for a given key
|
|
272
|
+
* @returns A new LazyKeyed instance
|
|
273
|
+
*/
|
|
274
|
+
public static of<T>(factory: (key: string) => T): LazyKeyed<T> {
|
|
275
|
+
return new this(factory);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Gets the value for the given key, creating it if necessary.
|
|
280
|
+
* @param key - The key to look up or create
|
|
281
|
+
* @returns The value associated with the key
|
|
282
|
+
* @throws Error if a circular dependency is detected during value creation
|
|
283
|
+
*/
|
|
284
|
+
public get(key: string): T {
|
|
285
|
+
const stored = this.instances.get(key);
|
|
286
|
+
|
|
287
|
+
if (stored === CREATING) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
'Circular dependency detected during LazyKeyed value creation with value: ' + key
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (stored !== undefined) {
|
|
294
|
+
return stored[0];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.instances.set(key, CREATING);
|
|
298
|
+
|
|
299
|
+
const newInstance = this.factory(key);
|
|
300
|
+
|
|
301
|
+
this.instances.set(key, [newInstance]);
|
|
302
|
+
|
|
303
|
+
for (const attach of this.attachments) {
|
|
304
|
+
attach(key, newInstance);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return newInstance;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Pre-initializes values for the given keys.
|
|
312
|
+
* Useful for eagerly creating values that will be needed later.
|
|
313
|
+
* @param key - The first key to reserve
|
|
314
|
+
* @param keys - Additional keys to reserve
|
|
315
|
+
* @returns This instance for chaining
|
|
316
|
+
*/
|
|
317
|
+
public reserve(key: string, ...keys: string[]): this {
|
|
318
|
+
keys.unshift(key);
|
|
319
|
+
for (const k of keys) {
|
|
320
|
+
if (this.instances.get(k) === undefined) {
|
|
321
|
+
this.get(k);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Registers a callback to be executed for each value when created.
|
|
329
|
+
* Immediately invokes the callback for any already-created values.
|
|
330
|
+
* @param attach - Callback receiving the key and value
|
|
331
|
+
* @returns This instance for chaining
|
|
332
|
+
*/
|
|
333
|
+
public attach(attach: LazyMapping<T>): this {
|
|
334
|
+
for (const [name, instance] of this.instances.entries()) {
|
|
335
|
+
if (instance !== CREATING) {
|
|
336
|
+
attach(name, instance[0]);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
this.attachments.push(attach);
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Registers a callback for a specific key only.
|
|
345
|
+
* The callback is invoked when the value for that key is created.
|
|
346
|
+
* @param key - The key to watch
|
|
347
|
+
* @param attach - Callback receiving the value
|
|
348
|
+
* @returns This instance for chaining
|
|
349
|
+
*/
|
|
350
|
+
public attachOne(key: string, attach: (value: T) => void): this {
|
|
351
|
+
return this.attach((name, value) => {
|
|
352
|
+
if (name === key) {
|
|
353
|
+
attach(value);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Creates a new LazyKeyed that transforms values using the provided function.
|
|
360
|
+
* Values in the new LazyKeyed are automatically created when source values are created.
|
|
361
|
+
* @template O - The type of the transformed values
|
|
362
|
+
* @param mapper - Transformation function receiving key and value
|
|
363
|
+
* @returns A new LazyKeyed instance with transformed values
|
|
364
|
+
*/
|
|
365
|
+
public chain<O>(mapper: LazyMapping<T, O>): LazyKeyed<O> {
|
|
366
|
+
const newKeyed = LazyKeyed.of((key) => mapper(key, this.get(key)));
|
|
367
|
+
|
|
368
|
+
this.attach((key) => {
|
|
369
|
+
newKeyed.reserve(key);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
return newKeyed;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Creates a Lazy that transforms the value for a specific key.
|
|
377
|
+
* The Lazy is automatically evaluated when the source value is created.
|
|
378
|
+
* @template O - The type of the transformed value
|
|
379
|
+
* @param key - The key to transform
|
|
380
|
+
* @param mapper - Transformation function receiving the value
|
|
381
|
+
* @returns A Lazy instance containing the transformed value
|
|
382
|
+
*/
|
|
383
|
+
public chainOne<O>(key: string, mapper: (value: T) => O): Lazy<O> {
|
|
384
|
+
const lazy = Lazy.of(() => mapper(this.get(key)));
|
|
385
|
+
this.attachOne(key, () => lazy.ensureInitialized());
|
|
386
|
+
return lazy;
|
|
34
387
|
}
|
|
35
388
|
}
|