11import ts from 'typescript' ;
2- import {
3- DynamodbPrimitiveTypes ,
4- dynamodbPrimitiveTypeFromTypeFlag ,
5- dynamodbPrimitiveTypeFromName ,
6- } from './dynamodb_primitive_types' ;
7- import {
8- ArrayField ,
9- DynamodbItemField ,
10- KeyValuePairMapField ,
11- MapField ,
12- PrimitiveField ,
13- SetField ,
14- } from './dynamodb_item_field' ;
15- import { warn } from './logger' ;
2+ import { FieldsCollector } from './fields_collector' ;
163
174export class DynamodbRecordTransformer {
18- public static readonly dynamodbRecordFuncName = 'dynamodbRecord' ; // TODO
5+ public static readonly funcName = 'dynamodbRecord' ;
196 private static readonly shouldLenientTypeCheck = ! ! process . env [ 'TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK' ] ; // TODO
207
218 public static visitNode ( node : ts . CallExpression , typeChecker : ts . TypeChecker ) : ts . Node | undefined {
229 if ( node . typeArguments === undefined || node . typeArguments . length !== 1 || ! node . typeArguments [ 0 ] ) {
2310 throw new Error (
24- `No type argument on ${ DynamodbRecordTransformer . dynamodbRecordFuncName } (). Please put a type argument on the function` ,
11+ `No type argument on ${ DynamodbRecordTransformer . funcName } (). Please put a type argument on the function` ,
2512 ) ;
2613 }
2714
2815 const typeName = node . typeArguments [ 0 ] . getText ( ) ;
29-
3016 if ( node . arguments . length !== 1 || ! node . arguments [ 0 ] ) {
3117 throw new Error (
32- `No argument on ${ DynamodbRecordTransformer . dynamodbRecordFuncName } (). Please put an argument that has ${ typeName } type on the function` ,
18+ `No argument on ${ DynamodbRecordTransformer . funcName } (). Please put an argument that has ${ typeName } type on the function` ,
3319 ) ;
3420 }
3521
3622 const argVarNameIdent = ts . factory . createIdentifier ( 'arg' ) ;
37- const type = typeChecker . getTypeFromTypeNode ( node . typeArguments [ 0 ] ) ;
38- const properties = typeChecker . getPropertiesOfType ( type ) ;
39- const objectProps = properties
40- . map ( ( prop ) : DynamodbItemField | undefined => {
41- if (
42- ! prop . valueDeclaration ||
43- ! ts . canHaveModifiers ( prop . valueDeclaration ) ||
44- ! DynamodbRecordTransformer . isPropertyModifierInArgumentSuitableForDynamodbAttr (
45- ts . getModifiers ( prop . valueDeclaration ) ,
46- )
47- ) {
48- // skip it
49- return undefined ;
50- }
51-
52- const propName = prop . name ;
53-
54- if (
55- prop . valueDeclaration . kind !== ts . SyntaxKind . Parameter &&
56- prop . valueDeclaration . kind !== ts . SyntaxKind . PropertySignature
57- ) {
58- const msg = `a property "${ propName } " of the type "${ typeName } " doesn't have parameter kind; maybe the ${ typeName } is not class of interface` ;
59- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
60- warn ( msg ) ;
61- return undefined ;
62- }
63- throw new Error ( msg ) ;
64- }
65-
66- const valueDeclSymbol = typeChecker . getTypeAtLocation ( prop . valueDeclaration ) . symbol ;
67- const valueDeclSymbolName = valueDeclSymbol ?. name ;
68-
69- if ( valueDeclSymbolName === 'Array' ) {
70- const typeArgs = typeChecker . getTypeArguments (
71- typeChecker . getTypeAtLocation ( prop . valueDeclaration ) as ts . TypeReference ,
72- ) ;
73- const valueType = dynamodbPrimitiveTypeFromTypeFlag ( typeArgs [ 0 ] ?. flags ) ;
74- if ( valueType === undefined ) {
75- const valueTypeName = typeArgs [ 0 ] ?. symbol ?. name ;
76- if ( valueTypeName === 'Uint8Array' ) {
77- return new ArrayField ( propName , DynamodbPrimitiveTypes . Binary ) ;
78- }
79- if ( valueTypeName === 'BigInt' ) {
80- return new ArrayField ( propName , DynamodbPrimitiveTypes . Number ) ;
81- }
82-
83- const msg = `a property "${ propName } " of the type "${ typeName } " has unsupported type: "${ valueTypeName } "` ;
84- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
85- warn ( msg ) ;
86- return undefined ;
87- }
88- throw new Error ( msg ) ;
89- }
90- return new ArrayField ( propName , valueType ) ;
91- }
92- if ( valueDeclSymbolName == 'Set' ) {
93- const typeArgs = typeChecker . getTypeArguments (
94- typeChecker . getTypeAtLocation ( prop . valueDeclaration ) as ts . TypeReference ,
95- ) ;
96- const valueType = dynamodbPrimitiveTypeFromTypeFlag ( typeArgs [ 0 ] ?. flags ) ;
97- if ( valueType === undefined ) {
98- const valueTypeName = typeArgs [ 0 ] ?. symbol ?. name ;
99- if ( valueTypeName === 'Uint8Array' ) {
100- return new SetField (
101- propName ,
102- DynamodbPrimitiveTypes . Binary ,
103- DynamodbRecordTransformer . shouldLenientTypeCheck ,
104- ) ;
105- }
106- if ( valueTypeName === 'BigInt' ) {
107- return new SetField (
108- propName ,
109- DynamodbPrimitiveTypes . Number ,
110- DynamodbRecordTransformer . shouldLenientTypeCheck ,
111- ) ;
112- }
113-
114- const msg = `a property "${ propName } " of the type "${ typeName } " has unsupported type: ${ valueTypeName } ` ;
115- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
116- warn ( msg ) ;
117- return undefined ;
118- }
119- throw new Error ( msg ) ;
120- }
121- return new SetField ( propName , valueType , DynamodbRecordTransformer . shouldLenientTypeCheck ) ;
122- }
123- if ( valueDeclSymbolName == 'Map' ) {
124- const typeArgs = typeChecker . getTypeArguments (
125- typeChecker . getTypeAtLocation ( prop . valueDeclaration ) as ts . TypeReference ,
126- ) ;
127-
128- const keyType = dynamodbPrimitiveTypeFromTypeFlag ( typeArgs [ 0 ] ?. flags ) ;
129- if ( keyType === undefined ) {
130- const msg = `a Map type property "${ propName } " of the type "${ typeName } " has non-string key type` ;
131- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
132- warn ( msg ) ;
133- return undefined ;
134- }
135- throw new Error ( msg ) ;
136- }
137-
138- const valueType = dynamodbPrimitiveTypeFromTypeFlag ( typeArgs [ 1 ] ?. flags ) ;
139- if ( valueType === undefined ) {
140- const valueTypeName = typeArgs [ 1 ] ?. symbol ?. name ;
141- if ( valueTypeName === 'Uint8Array' ) {
142- return new MapField (
143- propName ,
144- keyType ,
145- DynamodbPrimitiveTypes . Binary ,
146- DynamodbRecordTransformer . shouldLenientTypeCheck ,
147- ) ;
148- }
149- if ( valueTypeName === 'BigInt' ) {
150- return new MapField (
151- propName ,
152- keyType ,
153- DynamodbPrimitiveTypes . Number ,
154- DynamodbRecordTransformer . shouldLenientTypeCheck ,
155- ) ;
156- }
157-
158- const msg = `a property "${ propName } " of the type "${ typeName } " has unsupported type: "${ valueTypeName } "` ;
159- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
160- warn ( msg ) ;
161- return undefined ;
162- }
163- throw new Error ( msg ) ;
164- }
165-
166- return new MapField ( propName , keyType , valueType , DynamodbRecordTransformer . shouldLenientTypeCheck ) ;
167- }
168- if ( ( valueDeclSymbol ?. flags & ts . SymbolFlags . TypeLiteral ) === ts . SymbolFlags . TypeLiteral ) {
169- // for key-value pair map notation
170- const kvType = DynamodbRecordTransformer . extractKeyValueTypesFromKeyValuePairMapSyntax (
171- prop . valueDeclaration . getChildren ( ) ,
172- ) ;
173-
174- const keyTypeName = kvType ?. [ 0 ] ;
175- const keyType = dynamodbPrimitiveTypeFromName ( keyTypeName ) ;
176- if ( keyType === undefined ) {
177- const msg = `a Map type property "${ propName } " of the type "${ typeName } " has non-string key type: "${ keyTypeName } "` ;
178- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
179- warn ( msg ) ;
180- return undefined ;
181- }
182- throw new Error ( msg ) ;
183- }
184-
185- const valueTypeName = kvType ?. [ 1 ] ;
186- const valueType = dynamodbPrimitiveTypeFromName ( valueTypeName ) ;
187- if ( valueType === undefined ) {
188- const msg = `a property "${ propName } " of the type "${ typeName } " has unsupported type: "${ valueTypeName } "` ;
189- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
190- warn ( msg ) ;
191- return undefined ;
192- }
193- throw new Error ( msg ) ;
194- }
195-
196- return new KeyValuePairMapField (
197- propName ,
198- keyType ,
199- valueType ,
200- DynamodbRecordTransformer . shouldLenientTypeCheck ,
201- ) ;
202- }
203-
204- // primitive types
205- if ( valueDeclSymbolName === 'Uint8Array' ) {
206- return new PrimitiveField ( propName , DynamodbPrimitiveTypes . Binary ) ;
207- }
208-
209- let colonTokenCame = false ;
210- for ( const propNode of prop . valueDeclaration . getChildren ( ) ) {
211- if ( colonTokenCame ) {
212- const fieldType = dynamodbPrimitiveTypeFromName ( propNode . getText ( ) ) ;
213- if ( fieldType === undefined ) {
214- const msg = `a property "${ propName } " of the type "${ typeName } " has unsupported type: "${ propNode . getText ( ) } "` ;
215- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
216- warn ( msg ) ;
217- return undefined ;
218- }
219- throw new Error ( msg ) ;
220- }
221- return new PrimitiveField ( propName , fieldType ) ;
222- }
223- colonTokenCame = propNode . kind == ts . SyntaxKind . ColonToken ;
224- }
225-
226- // should never reach here
227-
228- const msg = `unexpected error: a property "${ propName } " of the type "${ typeName } " has unsupported type` ;
229- if ( DynamodbRecordTransformer . shouldLenientTypeCheck ) {
230- warn ( msg ) ;
231- return undefined ;
232- }
233- throw new Error ( msg ) ;
234- } )
23+ const objectProps = new FieldsCollector (
24+ DynamodbRecordTransformer . funcName ,
25+ DynamodbRecordTransformer . shouldLenientTypeCheck ,
26+ )
27+ . collectFields ( node , typeChecker )
23528 . map ( field => {
23629 return field ?. generateCode ( argVarNameIdent . text ) ;
23730 } )
@@ -250,37 +43,4 @@ export class DynamodbRecordTransformer {
25043 node . arguments [ 0 ] ,
25144 ) ;
25245 }
253-
254- private static extractKeyValueTypesFromKeyValuePairMapSyntax ( nodes : ts . Node [ ] ) : [ string , string ] | undefined {
255- for ( const node of nodes ) {
256- if ( node . kind === ts . SyntaxKind . TypeLiteral && node . getChildCount ( ) === 3 ) {
257- const kvTypeDeclNode = node . getChildAt ( 1 ) ;
258- if ( kvTypeDeclNode . kind === ts . SyntaxKind . SyntaxList && kvTypeDeclNode . getChildCount ( ) === 1 ) {
259- const kvTypeSignatureNode = kvTypeDeclNode . getChildAt ( 0 ) ;
260- if ( kvTypeSignatureNode . kind === ts . SyntaxKind . IndexSignature && kvTypeSignatureNode . getChildCount ( ) === 5 ) {
261- const valueType = kvTypeSignatureNode . getChildAt ( 4 ) . getText ( ) ;
262-
263- const keyTypeDeclNode = kvTypeSignatureNode . getChildAt ( 1 ) ;
264- if ( keyTypeDeclNode . kind === ts . SyntaxKind . SyntaxList && keyTypeDeclNode . getChildCount ( ) === 1 ) {
265- const keyTypeSignatureNode = keyTypeDeclNode . getChildAt ( 0 ) ;
266- if ( keyTypeSignatureNode . kind === ts . SyntaxKind . Parameter && keyTypeSignatureNode . getChildCount ( ) === 3 ) {
267- return [ keyTypeSignatureNode . getChildAt ( 2 ) . getText ( ) , valueType ] ;
268- }
269- }
270- }
271- }
272- break ;
273- }
274- }
275- return undefined ;
276- }
277-
278- private static isPropertyModifierInArgumentSuitableForDynamodbAttr (
279- modifiers : readonly ts . Modifier [ ] | undefined ,
280- ) : boolean {
281- if ( modifiers === undefined ) {
282- return true ;
283- }
284- return ! modifiers . map ( m => m . kind ) . includes ( ts . SyntaxKind . PrivateKeyword ) ;
285- }
28646}
0 commit comments