-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathlazy.ts
403 lines (363 loc) · 12.6 KB
/
lazy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
import { CDK_DEBUG, debugModeEnabled } from './debug';
import { IResolvable, IResolveContext } from './resolvable';
import { captureStackTrace } from './stack-trace';
import { Token } from './token';
/**
* Interface for lazy string producers
*/
export interface IStringProducer {
/**
* Produce the string value
*/
produce(context: IResolveContext): string | undefined;
}
/**
* Interface for (stable) lazy string producers
*/
export interface IStableStringProducer {
/**
* Produce the string value
*/
produce(): string | undefined;
}
/**
* Interface for lazy list producers
*/
export interface IListProducer {
/**
* Produce the list value
*/
produce(context: IResolveContext): string[] | undefined;
}
/**
* Interface for (stable) lazy list producers
*/
export interface IStableListProducer {
/**
* Produce the list value
*/
produce(): string[] | undefined;
}
/**
* Interface for lazy number producers
*/
export interface INumberProducer {
/**
* Produce the number value
*/
produce(context: IResolveContext): number | undefined;
}
/**
* Interface for (stable) lazy number producers
*/
export interface IStableNumberProducer {
/**
* Produce the number value
*/
produce(): number | undefined;
}
/**
* Interface for lazy untyped value producers
*/
export interface IAnyProducer {
/**
* Produce the value
*/
produce(context: IResolveContext): any;
}
/**
* Interface for (stable) lazy untyped value producers
*/
export interface IStableAnyProducer {
/**
* Produce the value
*/
produce(): any;
}
/**
* Options for creating a lazy string token
*/
export interface LazyStringValueOptions {
/**
* Use the given name as a display hint
*
* @default - No hint
*/
readonly displayHint?: string;
}
/**
* Options for creating a lazy list token
*/
export interface LazyListValueOptions {
/**
* Use the given name as a display hint
*
* @default - No hint
*/
readonly displayHint?: string;
/**
* If the produced list is empty, return 'undefined' instead
*
* @default false
*/
readonly omitEmpty?: boolean;
}
/**
* Options for creating lazy untyped tokens
*/
export interface LazyAnyValueOptions {
/**
* Use the given name as a display hint
*
* @default - No hint
*/
readonly displayHint?: string;
/**
* If the produced value is an array and it is empty, return 'undefined' instead
*
* @default false
*/
readonly omitEmptyArray?: boolean;
}
/**
* Lazily produce a value
*
* Can be used to return a string, list or numeric value whose actual value
* will only be calculated later, during synthesis.
*/
export class Lazy {
/**
* Defer the calculation of a string value to synthesis time
*
* Use this if you want to render a string to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* If you are simply looking to force a value to a `string` type and don't need
* the calculation to be deferred, use `Token.asString()` instead.
*
* @deprecated Use `Lazy.string()` or `Lazy.uncachedString()` instead.
*/
public static stringValue(producer: IStringProducer, options: LazyStringValueOptions = {}) {
return Token.asString(new LazyString(producer, false), options);
}
/**
* Defer the one-time calculation of a string value to synthesis time
*
* Use this if you want to render a string to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* If you are simply looking to force a value to a `string` type and don't need
* the calculation to be deferred, use `Token.asString()` instead.
*
* The inner function will only be invoked once, and the resolved value
* cannot depend on the Stack the Token is used in.
*/
public static string(producer: IStableStringProducer, options: LazyStringValueOptions = {}) {
return Token.asString(new LazyString(producer, true), options);
}
/**
* Defer the calculation of a string value to synthesis time
*
* Use of this function is not recommended; unless you know you need it for sure, you
* probably don't. Use `Lazy.string()` instead.
*
* The inner function may be invoked multiple times during synthesis. You
* should only use this method if the returned value depends on variables
* that may change during the Aspect application phase of synthesis, or if
* the value depends on the Stack the value is being used in. Both of these
* cases are rare, and only ever occur for AWS Construct Library authors.
*/
public static uncachedString(producer: IStringProducer, options: LazyStringValueOptions = {}) {
return Token.asString(new LazyString(producer, false), options);
}
/**
* Defer the one-time calculation of a number value to synthesis time
*
* Use this if you want to render a number to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* If you are simply looking to force a value to a `number` type and don't need
* the calculation to be deferred, use `Token.asNumber()` instead.
*
* @deprecated Use `Lazy.number()` or `Lazy.uncachedNumber()` instead.
*/
public static numberValue(producer: INumberProducer) {
return Token.asNumber(new LazyNumber(producer, false));
}
/**
* Defer the one-time calculation of a number value to synthesis time
*
* Use this if you want to render a number to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* If you are simply looking to force a value to a `number` type and don't need
* the calculation to be deferred, use `Token.asNumber()` instead.
*
* The inner function will only be invoked once, and the resolved value
* cannot depend on the Stack the Token is used in.
*/
public static number(producer: IStableNumberProducer) {
return Token.asNumber(new LazyNumber(producer, true));
}
/**
* Defer the calculation of a number value to synthesis time
*
* Use of this function is not recommended; unless you know you need it for sure, you
* probably don't. Use `Lazy.number()` instead.
*
* The inner function may be invoked multiple times during synthesis. You
* should only use this method if the returned value depends on variables
* that may change during the Aspect application phase of synthesis, or if
* the value depends on the Stack the value is being used in. Both of these
* cases are rare, and only ever occur for AWS Construct Library authors.
*/
public static uncachedNumber(producer: INumberProducer) {
return Token.asNumber(new LazyNumber(producer, false));
}
/**
* Defer the one-time calculation of a list value to synthesis time
*
* Use this if you want to render a list to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* If you are simply looking to force a value to a `string[]` type and don't need
* the calculation to be deferred, use `Token.asList()` instead.
*
* @deprecated Use `Lazy.list()` or `Lazy.uncachedList()` instead.
*/
public static listValue(producer: IListProducer, options: LazyListValueOptions = {}) {
return Token.asList(new LazyList(producer, false, options), options);
}
/**
* Defer the calculation of a list value to synthesis time
*
* Use of this function is not recommended; unless you know you need it for sure, you
* probably don't. Use `Lazy.list()` instead.
*
* The inner function may be invoked multiple times during synthesis. You
* should only use this method if the returned value depends on variables
* that may change during the Aspect application phase of synthesis, or if
* the value depends on the Stack the value is being used in. Both of these
* cases are rare, and only ever occur for AWS Construct Library authors.
*/
public static uncachedList(producer: IListProducer, options: LazyListValueOptions = {}) {
return Token.asList(new LazyList(producer, false, options), options);
}
/**
* Defer the one-time calculation of a list value to synthesis time
*
* Use this if you want to render a list to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* If you are simply looking to force a value to a `string[]` type and don't need
* the calculation to be deferred, use `Token.asList()` instead.
*
* The inner function will only be invoked once, and the resolved value
* cannot depend on the Stack the Token is used in.
*/
public static list(producer: IStableListProducer, options: LazyListValueOptions = {}) {
return Token.asList(new LazyList(producer, true, options), options);
}
/**
* Defer the one-time calculation of an arbitrarily typed value to synthesis time
*
* Use this if you want to render an object to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* @deprecated Use `Lazy.any()` or `Lazy.uncachedAny()` instead.
*/
public static anyValue(producer: IAnyProducer, options: LazyAnyValueOptions = {}): IResolvable {
return new LazyAny(producer, false, options);
}
/**
* Defer the one-time calculation of an arbitrarily typed value to synthesis time
*
* Use this if you want to render an object to a template whose actual value depends on
* some state mutation that may happen after the construct has been created.
*
* The inner function will only be invoked one time and cannot depend on
* resolution context.
*/
public static any(producer: IStableAnyProducer, options: LazyAnyValueOptions = {}): IResolvable {
return new LazyAny(producer, true, options);
}
/**
* Defer the calculation of an untyped value to synthesis time
*
* Use of this function is not recommended; unless you know you need it for sure, you
* probably don't. Use `Lazy.any()` instead.
*
* The inner function may be invoked multiple times during synthesis. You
* should only use this method if the returned value depends on variables
* that may change during the Aspect application phase of synthesis, or if
* the value depends on the Stack the value is being used in. Both of these
* cases are rare, and only ever occur for AWS Construct Library authors.
*/
public static uncachedAny(producer: IAnyProducer, options: LazyAnyValueOptions = {}): IResolvable {
return new LazyAny(producer, false, options);
}
private constructor() {
}
}
interface ILazyProducer<A> {
produce(context: IResolveContext): A | undefined;
}
abstract class LazyBase<A> implements IResolvable {
public readonly creationStack: string[];
private _cached?: A;
constructor(private readonly producer: ILazyProducer<A>, private readonly cache: boolean) {
// Stack trace capture is conditionned to `debugModeEnabled()`, because
// lazies can be created in a fairly thrashy way, and the stack traces are
// large and slow to obtain; but are mostly useful only when debugging a
// resolution issue.
this.creationStack = debugModeEnabled()
? captureStackTrace(this.constructor)
: [`Execute again with ${CDK_DEBUG}=true to capture stack traces`];
}
public resolve(context: IResolveContext) {
if (this.cache) {
return this._cached ?? (this._cached = this.producer.produce(context));
} else {
return this.producer.produce(context);
}
}
public toString() {
return Token.asString(this);
}
/**
* Turn this Token into JSON
*
* Called automatically when JSON.stringify() is called on a Token.
*/
public toJSON(): any {
return '<unresolved-lazy>';
}
}
class LazyString extends LazyBase<string> {
}
class LazyNumber extends LazyBase<number> {
}
class LazyList extends LazyBase<Array<string>> {
constructor(producer: IListProducer, cache: boolean, private readonly options: LazyListValueOptions = {}) {
super(producer, cache);
}
public resolve(context: IResolveContext) {
const resolved = super.resolve(context);
if (resolved?.length === 0 && this.options.omitEmpty) {
return undefined;
}
return resolved;
}
}
class LazyAny extends LazyBase<any> {
constructor(producer: IAnyProducer, cache: boolean, private readonly options: LazyAnyValueOptions = {}) {
super(producer, cache);
}
public resolve(context: IResolveContext) {
const resolved = super.resolve(context);
if (Array.isArray(resolved) && resolved.length === 0 && this.options.omitEmptyArray) {
return undefined;
}
return resolved;
}
}