@cdktn/provider-generator 0.24.0-pre.71 → 0.24.0-pre.73
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/build/get/__tests__/generator/supported-stored-classes.test.d.ts +2 -0
- package/build/get/__tests__/generator/supported-stored-classes.test.js +137 -0
- package/build/get/__tests__/generator/types.test.js +44 -1
- package/build/get/generator/emitter/attributes-emitter.d.ts +1 -0
- package/build/get/generator/emitter/attributes-emitter.js +25 -1
- package/build/get/generator/models/attribute-model.d.ts +2 -0
- package/build/get/generator/models/attribute-model.js +49 -4
- package/build/get/generator/models/attribute-type-model.d.ts +24 -0
- package/build/get/generator/models/attribute-type-model.js +91 -5
- package/build/get/generator/models/supported-stored-classes.d.ts +5 -0
- package/build/get/generator/models/supported-stored-classes.js +86 -0
- package/package.json +5 -5
- package/src/get/__tests__/generator/__snapshots__/types.test.ts.snap +149 -0
- package/src/get/__tests__/generator/fixtures/deeply-nested-collections.test.fixture.json +129 -0
- package/src/get/__tests__/generator/supported-stored-classes.test.ts +147 -0
- package/src/get/__tests__/generator/types.test.ts +75 -0
- package/src/get/generator/emitter/attributes-emitter.ts +37 -1
- package/src/get/generator/models/attribute-model.ts +58 -4
- package/src/get/generator/models/attribute-type-model.ts +121 -9
- package/src/get/generator/models/supported-stored-classes.ts +88 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// Copyright (c) HashiCorp, Inc
|
|
2
2
|
// SPDX-License-Identifier: MPL-2.0
|
|
3
3
|
import { CodeMaker } from "codemaker";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AttributeModel,
|
|
6
|
+
CollectionAttributeTypeModel,
|
|
7
|
+
describeTerraformType,
|
|
8
|
+
} from "../models";
|
|
5
9
|
import { CUSTOM_DEFAULTS } from "../custom-defaults";
|
|
10
|
+
import { sanitizedComment } from "../sanitized-comments";
|
|
6
11
|
|
|
7
12
|
function titleCase(value: string) {
|
|
8
13
|
return value[0].toUpperCase() + value.slice(1);
|
|
@@ -34,6 +39,8 @@ export class AttributesEmitter {
|
|
|
34
39
|
);
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
this.emitStoredClassBoundaryDoc(att);
|
|
43
|
+
|
|
37
44
|
switch (getterType._type) {
|
|
38
45
|
case "plain":
|
|
39
46
|
this.code.openBlock(`public get ${att.name}()`);
|
|
@@ -121,6 +128,35 @@ export class AttributesEmitter {
|
|
|
121
128
|
return att.type.getStoredClassInitializer(att.terraformName);
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
// Emits a JSDoc block documenting the Terraform type immediately before getters whose
|
|
132
|
+
// stored class either collapsed a deeper, unsupported nesting into the nearest typed
|
|
133
|
+
// "Any"-wrapper, or fell all the way back to a plain, untyped getter because no stored
|
|
134
|
+
// class (not even a collapsed one) is available. Normal, fully-supported getters get no
|
|
135
|
+
// such doc, to limit snapshot churn.
|
|
136
|
+
private emitStoredClassBoundaryDoc(att: AttributeModel) {
|
|
137
|
+
const boundaryKind = att.storedClassBoundaryKind;
|
|
138
|
+
if (!boundaryKind) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const comment = sanitizedComment(this.code);
|
|
143
|
+
comment.line(`Terraform type: \`${describeTerraformType(att.type)}\`.`);
|
|
144
|
+
|
|
145
|
+
if (boundaryKind === "collapsed") {
|
|
146
|
+
const collapsedName = (att.type as CollectionAttributeTypeModel)
|
|
147
|
+
.resolvedStoredClass!.name;
|
|
148
|
+
comment.line(
|
|
149
|
+
`This nesting is deeper than the typed wrapper classes in the core cdktn package; it is exposed as \`${collapsedName}\` and values below that boundary are untyped tokens.`,
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
comment.line(
|
|
153
|
+
`No typed wrapper class for this shape exists in the core cdktn package; the value is returned as an untyped token (IResolvable).`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
comment.end();
|
|
158
|
+
}
|
|
159
|
+
|
|
124
160
|
public determineGetAttCall(att: AttributeModel): string {
|
|
125
161
|
if (att.isProvider) {
|
|
126
162
|
return `this.${att.storageName}`;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Copyright (c) HashiCorp, Inc
|
|
2
2
|
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
-
import {
|
|
3
|
+
import { logger } from "@cdktn/commons";
|
|
4
|
+
import {
|
|
5
|
+
AttributeTypeModel,
|
|
6
|
+
CollectionAttributeTypeModel,
|
|
7
|
+
} from "./attribute-type-model";
|
|
4
8
|
|
|
5
9
|
export type GetterType =
|
|
6
10
|
| { _type: "plain" }
|
|
@@ -69,6 +73,7 @@ export class AttributeModel {
|
|
|
69
73
|
public provider: boolean;
|
|
70
74
|
public required: boolean;
|
|
71
75
|
public forcePlainGetterType?: boolean;
|
|
76
|
+
private loggedStoredClassUnavailable = false;
|
|
72
77
|
|
|
73
78
|
constructor(options: AttributeModelOptions) {
|
|
74
79
|
this.storageName = options.storageName;
|
|
@@ -125,14 +130,63 @@ export class AttributeModel {
|
|
|
125
130
|
!this.isAssignable &&
|
|
126
131
|
(!this.type.isTokenizable || this.type.typeModelType === "map")
|
|
127
132
|
) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
if (this.type.isStoredClassUnavailable) {
|
|
134
|
+
// Defensive fallback: neither the composed stored class name (e.g.
|
|
135
|
+
// "StringListMapMap") nor a collapsed nearest-Any wrapper (e.g. "AnyMapMap")
|
|
136
|
+
// exists in the core cdktn package for this type. This should be rare - see
|
|
137
|
+
// resolveStoredClassName in supported-stored-classes.ts - so log it loudly to
|
|
138
|
+
// make regressions in the collapse logic visible.
|
|
139
|
+
if (!this.loggedStoredClassUnavailable) {
|
|
140
|
+
this.loggedStoredClassUnavailable = true;
|
|
141
|
+
logger.debug(
|
|
142
|
+
`No stored class (not even a collapsed nearest-Any wrapper) is available for computed attribute "${this.terraformFullName}" (composed storedClassType: "${this.type.storedClassType}"). Falling back to a plain, untyped getter.`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
getterType = {
|
|
147
|
+
_type: "stored_class",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
131
150
|
}
|
|
132
151
|
|
|
133
152
|
return getterType;
|
|
134
153
|
}
|
|
135
154
|
|
|
155
|
+
// Reports whether this attribute's getter sits at a "stored class boundary" that is
|
|
156
|
+
// worth documenting: "collapsed" when a deeper, unsupported nesting was collapsed into
|
|
157
|
+
// the nearest typed "Any"-wrapper (e.g. "AnyMapMap"), or "fallback" when no stored class
|
|
158
|
+
// at all is available and a plain, untyped getter is emitted instead. Mirrors the same
|
|
159
|
+
// condition tree as getterType above, so it only ever reports a boundary for attributes
|
|
160
|
+
// that actually went through that computed-collection branch - plain getters for
|
|
161
|
+
// ordinary tokenizable types (e.g. list(string)) are never flagged.
|
|
162
|
+
public get storedClassBoundaryKind(): "collapsed" | "fallback" | undefined {
|
|
163
|
+
if (this.forcePlainGetterType || this.isProvider) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.type.hasReferenceClass) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
this.computed &&
|
|
173
|
+
!this.isAssignable &&
|
|
174
|
+
(!this.type.isTokenizable || this.type.typeModelType === "map")
|
|
175
|
+
) {
|
|
176
|
+
if (this.type.isStoredClassUnavailable) {
|
|
177
|
+
return "fallback";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const resolvedStoredClass = (this.type as CollectionAttributeTypeModel)
|
|
181
|
+
.resolvedStoredClass;
|
|
182
|
+
if (resolvedStoredClass?.collapsed) {
|
|
183
|
+
return "collapsed";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
136
190
|
public get isStored(): boolean {
|
|
137
191
|
return this.isAssignable;
|
|
138
192
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MPL-2.0
|
|
3
3
|
import { Struct } from "./struct";
|
|
4
4
|
import { uppercaseFirst, downcaseFirst } from "../../../util";
|
|
5
|
+
import { resolveStoredClassName } from "./supported-stored-classes";
|
|
5
6
|
|
|
6
7
|
export interface AttributeTypeModel {
|
|
7
8
|
readonly struct?: Struct; // complex object type contained within the type
|
|
@@ -15,6 +16,37 @@ export interface AttributeTypeModel {
|
|
|
15
16
|
readonly typeModelType: string; // so we don't need to use instanceof
|
|
16
17
|
readonly hasReferenceClass: boolean; // used to determine if a stored_class getter should be used
|
|
17
18
|
readonly isTokenizable: boolean; // can the type be represented by a token type
|
|
19
|
+
readonly isStoredClassUnavailable: boolean; // true when no "cdktn.<Name>" stored class (not even a collapsed nearest-Any wrapper) is available for this type
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Describes the Terraform type of an attribute as its `list(...)`/`set(...)`/`map(...)`
|
|
23
|
+
// composed notation (matching how Terraform itself describes nested collection types),
|
|
24
|
+
// for use in generated JSDoc. Used only for collapsed/fallback getters (see
|
|
25
|
+
// AttributesEmitter) so it never needs to be exhaustive/perfectly stable for every shape.
|
|
26
|
+
export function describeTerraformType(model: AttributeTypeModel): string {
|
|
27
|
+
switch (model.typeModelType) {
|
|
28
|
+
case "list":
|
|
29
|
+
return `list(${describeTerraformType(
|
|
30
|
+
(model as CollectionAttributeTypeModel).elementType,
|
|
31
|
+
)})`;
|
|
32
|
+
case "set":
|
|
33
|
+
return `set(${describeTerraformType(
|
|
34
|
+
(model as CollectionAttributeTypeModel).elementType,
|
|
35
|
+
)})`;
|
|
36
|
+
case "map":
|
|
37
|
+
return `map(${describeTerraformType(
|
|
38
|
+
(model as CollectionAttributeTypeModel).elementType,
|
|
39
|
+
)})`;
|
|
40
|
+
case "struct":
|
|
41
|
+
return "object";
|
|
42
|
+
default:
|
|
43
|
+
// Covers both SimpleAttributeTypeModel (storedClassType is the Terraform primitive
|
|
44
|
+
// name, e.g. "string" | "number" | "boolean" | "any") and SkippedAttributeTypeModel
|
|
45
|
+
// (storedClassType is always "any").
|
|
46
|
+
return model.storedClassType === "boolean"
|
|
47
|
+
? "bool"
|
|
48
|
+
: model.storedClassType;
|
|
49
|
+
}
|
|
18
50
|
}
|
|
19
51
|
|
|
20
52
|
export class SkippedAttributeTypeModel implements AttributeTypeModel {
|
|
@@ -64,6 +96,10 @@ export class SkippedAttributeTypeModel implements AttributeTypeModel {
|
|
|
64
96
|
get isTokenizable() {
|
|
65
97
|
return false;
|
|
66
98
|
}
|
|
99
|
+
|
|
100
|
+
get isStoredClassUnavailable() {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
export class SimpleAttributeTypeModel implements AttributeTypeModel {
|
|
@@ -117,6 +153,10 @@ export class SimpleAttributeTypeModel implements AttributeTypeModel {
|
|
|
117
153
|
get isTokenizable() {
|
|
118
154
|
return true;
|
|
119
155
|
}
|
|
156
|
+
|
|
157
|
+
get isStoredClassUnavailable() {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
120
160
|
}
|
|
121
161
|
|
|
122
162
|
export class StructAttributeTypeModel implements AttributeTypeModel {
|
|
@@ -162,10 +202,21 @@ export class StructAttributeTypeModel implements AttributeTypeModel {
|
|
|
162
202
|
get isTokenizable() {
|
|
163
203
|
return false;
|
|
164
204
|
}
|
|
205
|
+
|
|
206
|
+
get isStoredClassUnavailable() {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
165
209
|
}
|
|
166
210
|
|
|
167
211
|
export interface CollectionAttributeTypeModel extends AttributeTypeModel {
|
|
168
212
|
elementType: AttributeTypeModel;
|
|
213
|
+
// Resolves the "cdktn.<name>" stored class to instantiate for this (non-complex)
|
|
214
|
+
// collection type, and whether that resolution collapsed a deeper, unsupported nesting
|
|
215
|
+
// into the nearest typed "Any"-wrapper. undefined only defensively - see
|
|
216
|
+
// resolveStoredClassName in supported-stored-classes.ts.
|
|
217
|
+
readonly resolvedStoredClass:
|
|
218
|
+
| { name: string; collapsed: boolean }
|
|
219
|
+
| undefined;
|
|
169
220
|
}
|
|
170
221
|
|
|
171
222
|
export class ListAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
@@ -198,9 +249,7 @@ export class ListAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
198
249
|
if (this.isComplex) {
|
|
199
250
|
return `new ${this.storedClassType}(this, "${name}", false)`;
|
|
200
251
|
} else {
|
|
201
|
-
return `new cdktn.${
|
|
202
|
-
this.storedClassType,
|
|
203
|
-
)}(this, "${name}", false)`;
|
|
252
|
+
return `new cdktn.${this.resolvedStoredClass!.name}(this, "${name}", false)`;
|
|
204
253
|
}
|
|
205
254
|
}
|
|
206
255
|
}
|
|
@@ -209,6 +258,13 @@ export class ListAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
209
258
|
return `${this.elementType.storedClassType}List`;
|
|
210
259
|
}
|
|
211
260
|
|
|
261
|
+
get resolvedStoredClass() {
|
|
262
|
+
return resolveStoredClassName(
|
|
263
|
+
this.storedClassType,
|
|
264
|
+
this.elementType.typeModelType,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
212
268
|
get inputTypeDefinition() {
|
|
213
269
|
if (this.isSingleItem) {
|
|
214
270
|
return this.elementType.inputTypeDefinition;
|
|
@@ -263,6 +319,34 @@ export class ListAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
263
319
|
return false;
|
|
264
320
|
}
|
|
265
321
|
}
|
|
322
|
+
|
|
323
|
+
// Complex (struct) elements generate their own class in the provider output, so they
|
|
324
|
+
// are always fine. Otherwise a stored class is only emitted if resolvedStoredClass
|
|
325
|
+
// finds one - see getStoredClassInitializer above. Note: hasReferenceClass (which
|
|
326
|
+
// triggers a stored_class getter) is only ever true here when isSingleItem or isComplex
|
|
327
|
+
// is true, and isSingleItem is only set for struct/block nesting (which is also
|
|
328
|
+
// isComplex), so this check only needs to guard the non-complex, non-single-item case.
|
|
329
|
+
//
|
|
330
|
+
// storedClassType is itself built up recursively (elementType.storedClassType is
|
|
331
|
+
// composed the same way for nested collections), so it already fully encodes the
|
|
332
|
+
// entire nested chain in one string (e.g. map(map(list(string))) composes all the way
|
|
333
|
+
// up to "stringListMapMap"). resolvedStoredClass first checks this type's own composed
|
|
334
|
+
// name (e.g. "StringListMapMap") against the fixed set of hand-written core classes; if
|
|
335
|
+
// that name doesn't exist, it falls back to the nearest typed "Any"-wrapper that still
|
|
336
|
+
// encodes this type's own layer and its element's layer (e.g. "AnyMapMap"), collapsing
|
|
337
|
+
// only the unsupported inner shape while preserving typed navigation for the outer
|
|
338
|
+
// layers; only if neither exists does it fall through to the plain, untyped getter (see
|
|
339
|
+
// AttributeModel.getterType). It must NOT also OR in
|
|
340
|
+
// elementType.isStoredClassUnavailable, since an inner element's un-composed name (e.g.
|
|
341
|
+
// "StringList" for a plain list(string) nested inside a map) is never separately
|
|
342
|
+
// instantiated and is irrelevant on its own; ORing it in would produce false positives
|
|
343
|
+
// that regress supported nestings such as map(list(string)) => cdktn.StringListMap.
|
|
344
|
+
get isStoredClassUnavailable() {
|
|
345
|
+
if (this.isComplex) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return this.resolvedStoredClass === undefined;
|
|
349
|
+
}
|
|
266
350
|
}
|
|
267
351
|
|
|
268
352
|
export class SetAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
@@ -295,9 +379,7 @@ export class SetAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
295
379
|
if (this.isComplex) {
|
|
296
380
|
return `new ${this.storedClassType}(this, "${name}", true)`;
|
|
297
381
|
} else {
|
|
298
|
-
return `new cdktn.${
|
|
299
|
-
this.storedClassType,
|
|
300
|
-
)}(this, "${name}", true)`;
|
|
382
|
+
return `new cdktn.${this.resolvedStoredClass!.name}(this, "${name}", true)`;
|
|
301
383
|
}
|
|
302
384
|
}
|
|
303
385
|
}
|
|
@@ -306,6 +388,13 @@ export class SetAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
306
388
|
return `${this.elementType.storedClassType}List`;
|
|
307
389
|
}
|
|
308
390
|
|
|
391
|
+
get resolvedStoredClass() {
|
|
392
|
+
return resolveStoredClassName(
|
|
393
|
+
this.storedClassType,
|
|
394
|
+
this.elementType.typeModelType,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
309
398
|
get inputTypeDefinition() {
|
|
310
399
|
if (this.isSingleItem) {
|
|
311
400
|
return this.elementType.inputTypeDefinition;
|
|
@@ -365,6 +454,15 @@ export class SetAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
365
454
|
return false;
|
|
366
455
|
}
|
|
367
456
|
}
|
|
457
|
+
|
|
458
|
+
// See comment on ListAttributeTypeModel.isStoredClassUnavailable above - same
|
|
459
|
+
// reasoning applies here.
|
|
460
|
+
get isStoredClassUnavailable() {
|
|
461
|
+
if (this.isComplex) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return this.resolvedStoredClass === undefined;
|
|
465
|
+
}
|
|
368
466
|
}
|
|
369
467
|
|
|
370
468
|
export class MapAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
@@ -386,9 +484,7 @@ export class MapAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
386
484
|
if (this.isComplex) {
|
|
387
485
|
return `new ${this.storedClassType}(this, "${name}")`;
|
|
388
486
|
} else {
|
|
389
|
-
return `new cdktn.${
|
|
390
|
-
this.storedClassType,
|
|
391
|
-
)}(this, "${name}")`;
|
|
487
|
+
return `new cdktn.${this.resolvedStoredClass!.name}(this, "${name}")`;
|
|
392
488
|
}
|
|
393
489
|
}
|
|
394
490
|
|
|
@@ -396,6 +492,13 @@ export class MapAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
396
492
|
return `${this.elementType.storedClassType}Map`;
|
|
397
493
|
}
|
|
398
494
|
|
|
495
|
+
get resolvedStoredClass() {
|
|
496
|
+
return resolveStoredClassName(
|
|
497
|
+
this.storedClassType,
|
|
498
|
+
this.elementType.typeModelType,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
399
502
|
get inputTypeDefinition() {
|
|
400
503
|
// map of booleans has token support, but booleans don't
|
|
401
504
|
if (this.elementType.storedClassType === "boolean") {
|
|
@@ -445,4 +548,13 @@ export class MapAttributeTypeModel implements CollectionAttributeTypeModel {
|
|
|
445
548
|
return false;
|
|
446
549
|
}
|
|
447
550
|
}
|
|
551
|
+
|
|
552
|
+
// See comment on ListAttributeTypeModel.isStoredClassUnavailable above - same
|
|
553
|
+
// reasoning applies here (MapAttributeTypeModel.hasReferenceClass is just isComplex).
|
|
554
|
+
get isStoredClassUnavailable() {
|
|
555
|
+
if (this.isComplex) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
return this.resolvedStoredClass === undefined;
|
|
559
|
+
}
|
|
448
560
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { uppercaseFirst } from "../../../util";
|
|
4
|
+
|
|
5
|
+
// The core cdktn package (packages/cdktn/src/complex-computed-list.ts) only hand-writes
|
|
6
|
+
// this fixed set of wrapper classes for computed, non-complex collection attributes.
|
|
7
|
+
// Arbitrarily deep nestings of list/set/map (e.g. map(map(list(string)))) compose a
|
|
8
|
+
// storedClassType (e.g. "stringListMapMap") that has no corresponding class. Rather than
|
|
9
|
+
// falling back straight to a plain, untyped getter, resolveStoredClassName below first
|
|
10
|
+
// looks for the nearest typed "Any"-wrapper that still exists in this set, collapsing the
|
|
11
|
+
// unsupported inner shape into an untyped boundary while preserving typed navigation for
|
|
12
|
+
// the outer layers (see https://github.com/open-constructs/cdk-terrain/issues/11).
|
|
13
|
+
const SUPPORTED_STORED_CLASSES = new Set([
|
|
14
|
+
"StringMap",
|
|
15
|
+
"NumberMap",
|
|
16
|
+
"BooleanMap",
|
|
17
|
+
"AnyMap",
|
|
18
|
+
"StringMapMap",
|
|
19
|
+
"NumberMapMap",
|
|
20
|
+
"BooleanMapMap",
|
|
21
|
+
"AnyMapMap",
|
|
22
|
+
"BooleanList",
|
|
23
|
+
"StringListList",
|
|
24
|
+
"NumberListList",
|
|
25
|
+
"BooleanListList",
|
|
26
|
+
"AnyListList",
|
|
27
|
+
"StringMapList",
|
|
28
|
+
"NumberMapList",
|
|
29
|
+
"BooleanMapList",
|
|
30
|
+
"AnyMapList",
|
|
31
|
+
"StringListMap",
|
|
32
|
+
"NumberListMap",
|
|
33
|
+
"BooleanListMap",
|
|
34
|
+
"AnyListMap",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// Resolves the composed stored class name to use for a collection attribute (list/set/map)
|
|
38
|
+
// whose element is itself a non-complex collection (or simple type).
|
|
39
|
+
//
|
|
40
|
+
// `ownStoredClassType` is this type's own composed storedClassType (e.g. "stringListMapMap"
|
|
41
|
+
// for map(map(list(string)))) and `elementTypeModelType` is the element's typeModelType
|
|
42
|
+
// ("list" | "set" | "map" | "simple" | ...).
|
|
43
|
+
//
|
|
44
|
+
// Resolution order:
|
|
45
|
+
// 1. The own composed name (e.g. "StringListMapMap") - used as-is when it refers to a
|
|
46
|
+
// class that is hand-written in the core package (not collapsed).
|
|
47
|
+
// 2. Otherwise, the nearest typed "Any"-wrapper that still encodes the outer two layers
|
|
48
|
+
// (this type's own layer and its element's layer), e.g. "AnyMapMap" - collapsing the
|
|
49
|
+
// unsupported inner shape into an untyped boundary while preserving typed navigation
|
|
50
|
+
// for the outer layers.
|
|
51
|
+
// 3. Otherwise (defensively; every depth should resolve via 1 or 2), undefined - the
|
|
52
|
+
// caller must fall back to a plain, untyped getter.
|
|
53
|
+
export function resolveStoredClassName(
|
|
54
|
+
ownStoredClassType: string,
|
|
55
|
+
elementTypeModelType: string,
|
|
56
|
+
): { name: string; collapsed: boolean } | undefined {
|
|
57
|
+
const candidate1 = uppercaseFirst(ownStoredClassType);
|
|
58
|
+
if (SUPPORTED_STORED_CLASSES.has(candidate1)) {
|
|
59
|
+
return { name: candidate1, collapsed: false };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let ownLayer: "List" | "Map" | undefined;
|
|
63
|
+
if (ownStoredClassType.endsWith("List")) {
|
|
64
|
+
ownLayer = "List";
|
|
65
|
+
} else if (ownStoredClassType.endsWith("Map")) {
|
|
66
|
+
ownLayer = "Map";
|
|
67
|
+
}
|
|
68
|
+
if (!ownLayer) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let innerLayer: "List" | "Map" | undefined;
|
|
73
|
+
if (elementTypeModelType === "list" || elementTypeModelType === "set") {
|
|
74
|
+
innerLayer = "List";
|
|
75
|
+
} else if (elementTypeModelType === "map") {
|
|
76
|
+
innerLayer = "Map";
|
|
77
|
+
}
|
|
78
|
+
if (!innerLayer) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const candidate2 = `Any${innerLayer}${ownLayer}`;
|
|
83
|
+
if (SUPPORTED_STORED_CLASSES.has(candidate2)) {
|
|
84
|
+
return { name: candidate2, collapsed: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|