@axinom/mosaic-cli 0.18.4 → 0.18.5-rc.1
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/commands/msg-codegen/codegen.d.ts +4 -4
- package/dist/commands/msg-codegen/codegen.js +66 -229
- package/dist/commands/msg-codegen/codegen.js.map +1 -1
- package/dist/commands/msg-codegen/presets.d.ts +21 -0
- package/dist/commands/msg-codegen/presets.js +119 -0
- package/dist/commands/msg-codegen/presets.js.map +1 -0
- package/dist/commands/msg-codegen/utils.d.ts +56 -0
- package/dist/commands/msg-codegen/utils.js +220 -0
- package/dist/commands/msg-codegen/utils.js.map +1 -0
- package/dist/commands/msg-diff/msg-diff.d.ts +3 -3
- package/dist/commands/msg-diff/msg-diff.js +28 -15
- package/dist/commands/msg-diff/msg-diff.js.map +1 -1
- package/package.json +7 -7
- package/src/commands/msg-codegen/codegen.ts +84 -248
- package/src/commands/msg-codegen/presets.ts +138 -0
- package/src/commands/msg-codegen/utils.ts +258 -0
- package/src/commands/msg-diff/msg-diff.spec.ts +44 -27
- package/src/commands/msg-diff/msg-diff.ts +48 -22
- package/src/commands/msg-diff/test-resources/0/1-asyncapi.yml +2 -2
- package/src/commands/msg-diff/test-resources/0/2-asyncapi.yml +2 -2
- package/src/commands/msg-diff/test-resources/1/1-asyncapi.yml +1 -1
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
CommonNamingConvention,
|
|
5
4
|
FormatHelpers,
|
|
6
5
|
OutputModel,
|
|
7
|
-
|
|
6
|
+
TS_DESCRIPTION_PRESET,
|
|
7
|
+
typeScriptDefaultPropertyKeyConstraints,
|
|
8
8
|
TypeScriptGenerator,
|
|
9
9
|
} from '@asyncapi/modelina';
|
|
10
|
-
import
|
|
10
|
+
import Parser, {
|
|
11
|
+
AsyncAPIDocumentInterface,
|
|
12
|
+
fromFile,
|
|
13
|
+
MessageInterface,
|
|
14
|
+
} from '@asyncapi/parser';
|
|
11
15
|
import endent from 'endent';
|
|
12
16
|
import * as fs from 'fs';
|
|
13
17
|
import * as path from 'path';
|
|
14
18
|
import { MessageCodegenOptions } from './message-codegen-options';
|
|
19
|
+
import {
|
|
20
|
+
ADDITIONAL_PROPERTIES_PRESET,
|
|
21
|
+
ANY_TO_UNKNOWN_PRESET,
|
|
22
|
+
EXPORT_TYPES_PRESET,
|
|
23
|
+
IMPORTS_PRESET,
|
|
24
|
+
NULLABLE_PROPERTY_TO_UNION_PRESET,
|
|
25
|
+
} from './presets';
|
|
26
|
+
import {
|
|
27
|
+
getChannelAction,
|
|
28
|
+
getMessageTitle,
|
|
29
|
+
getModelPathPrefix,
|
|
30
|
+
getPayloadTitle,
|
|
31
|
+
getRelativeDir,
|
|
32
|
+
getServiceId,
|
|
33
|
+
getServiceTitle,
|
|
34
|
+
removeXParserProperties,
|
|
35
|
+
toPosixPath,
|
|
36
|
+
} from './utils';
|
|
15
37
|
|
|
16
38
|
/**
|
|
17
39
|
* Processes AsyncAPI document and generates Typescript classes from it.
|
|
@@ -152,30 +174,38 @@ export class Codegen {
|
|
|
152
174
|
* @param asyncApiFile - path to AsyncAPI document
|
|
153
175
|
*/
|
|
154
176
|
async processAsyncAPIDocument(asyncApiFile: string): Promise<void> {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
177
|
+
const parser = new Parser();
|
|
178
|
+
const { document, diagnostics } = await fromFile(
|
|
179
|
+
parser,
|
|
180
|
+
asyncApiFile,
|
|
181
|
+
).parse();
|
|
182
|
+
|
|
183
|
+
if (document === undefined) {
|
|
184
|
+
console.error(`Failed to parse ${asyncApiFile}.`);
|
|
185
|
+
console.error(diagnostics);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
158
188
|
|
|
159
189
|
//export Typescript models
|
|
160
190
|
const tsModelsOutputPath = path.join(
|
|
161
191
|
this.typesOutputRoot,
|
|
162
192
|
getRelativeDir(this.schemaRoot, asyncApiFile),
|
|
163
193
|
);
|
|
164
|
-
await this.exportTsModels(
|
|
194
|
+
await this.exportTsModels(document, tsModelsOutputPath);
|
|
165
195
|
|
|
166
196
|
//export JSON Schemas
|
|
167
197
|
const schemasOutputPath = path.join(
|
|
168
198
|
this.schemasOutputRoot,
|
|
169
199
|
getRelativeDir(this.schemaRoot, asyncApiFile),
|
|
170
200
|
);
|
|
171
|
-
await this.exportSchemas(
|
|
201
|
+
await this.exportSchemas(document, schemasOutputPath);
|
|
172
202
|
|
|
173
203
|
//export message settings
|
|
174
204
|
const messagingSettingsOutputPath = path.join(
|
|
175
205
|
this.messagingSettingsOutputRoot,
|
|
176
206
|
getRelativeDir(this.schemaRoot, asyncApiFile),
|
|
177
207
|
);
|
|
178
|
-
await this.exportSettings(
|
|
208
|
+
await this.exportSettings(document, messagingSettingsOutputPath);
|
|
179
209
|
}
|
|
180
210
|
|
|
181
211
|
/**
|
|
@@ -184,28 +214,37 @@ export class Codegen {
|
|
|
184
214
|
* @param outputPath - output path for generated files
|
|
185
215
|
*/
|
|
186
216
|
async exportTsModels(
|
|
187
|
-
asyncAPIDocument:
|
|
217
|
+
asyncAPIDocument: AsyncAPIDocumentInterface,
|
|
188
218
|
outputPath: string,
|
|
189
219
|
): Promise<void> {
|
|
190
220
|
const generator = new TypeScriptGenerator({
|
|
191
221
|
modelType: 'interface',
|
|
192
|
-
namingConvention: CustomNamingConvention,
|
|
193
222
|
enumType: 'union',
|
|
223
|
+
mapType: 'indexedObject',
|
|
224
|
+
constraints: {
|
|
225
|
+
propertyKey: typeScriptDefaultPropertyKeyConstraints({
|
|
226
|
+
NAMING_FORMATTER: (name) => FormatHelpers.toSnakeCase(name),
|
|
227
|
+
NO_RESERVED_KEYWORDS: (name) => {
|
|
228
|
+
// TODO: This is a hack to avoid reserved keywords in property names.
|
|
229
|
+
// Currently many non-reserved keywords are also being replaced: https://github.com/asyncapi/modelina/issues/1053
|
|
230
|
+
return name;
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
},
|
|
234
|
+
// Order of presets matters!
|
|
194
235
|
presets: [
|
|
195
|
-
ADDITIONAL_PROPERTIES_PRESET,
|
|
196
236
|
NULLABLE_PROPERTY_TO_UNION_PRESET,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
ANY_TO_UNKNOWN_PRESET,
|
|
238
|
+
ADDITIONAL_PROPERTIES_PRESET,
|
|
239
|
+
EXPORT_TYPES_PRESET,
|
|
240
|
+
TS_DESCRIPTION_PRESET,
|
|
200
241
|
IMPORTS_PRESET,
|
|
201
242
|
],
|
|
202
243
|
});
|
|
203
244
|
|
|
204
|
-
let outputModels = await
|
|
205
|
-
for (const
|
|
206
|
-
const referencedModels = await generator.generate(
|
|
207
|
-
asyncAPIDocument.components().schemas()[key].json(),
|
|
208
|
-
);
|
|
245
|
+
let outputModels = await generator.generate(asyncAPIDocument);
|
|
246
|
+
for (const schema of asyncAPIDocument.components().schemas()) {
|
|
247
|
+
const referencedModels = await generator.generate(schema.json());
|
|
209
248
|
outputModels = outputModels.map(
|
|
210
249
|
(om) =>
|
|
211
250
|
referencedModels.find((rm) => om.modelName === rm.modelName) || om,
|
|
@@ -256,12 +295,12 @@ export class Codegen {
|
|
|
256
295
|
* @param outputPath - output path for generated files
|
|
257
296
|
*/
|
|
258
297
|
async exportSchemas(
|
|
259
|
-
asyncAPIDocument:
|
|
298
|
+
asyncAPIDocument: AsyncAPIDocumentInterface,
|
|
260
299
|
outputPath: string,
|
|
261
300
|
): Promise<void> {
|
|
262
|
-
const groupedSchemas: { [key: string]:
|
|
301
|
+
const groupedSchemas: { [key: string]: MessageInterface[] } = {};
|
|
263
302
|
|
|
264
|
-
for (const
|
|
303
|
+
for (const message of asyncAPIDocument.messages()) {
|
|
265
304
|
const schemaPathPrefix = getModelPathPrefix(getMessageTitle(message));
|
|
266
305
|
if (groupedSchemas[schemaPathPrefix] === undefined) {
|
|
267
306
|
groupedSchemas[schemaPathPrefix] = [];
|
|
@@ -280,12 +319,16 @@ export class Codegen {
|
|
|
280
319
|
recursive: true,
|
|
281
320
|
});
|
|
282
321
|
for (const msg of messages) {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
322
|
+
const payload = msg.payload();
|
|
323
|
+
if (payload !== undefined && payload.json() !== undefined) {
|
|
324
|
+
const filteredPayload = removeXParserProperties(payload.json());
|
|
325
|
+
const outputSchemaPath = this.buildSchemaOutPath(
|
|
326
|
+
groupOutPutPath,
|
|
327
|
+
getMessageTitle(msg),
|
|
328
|
+
);
|
|
329
|
+
await this.bundleSchema(filteredPayload, outputSchemaPath);
|
|
330
|
+
schemaFiles.push(outputSchemaPath);
|
|
331
|
+
}
|
|
289
332
|
}
|
|
290
333
|
|
|
291
334
|
//generate barrel export for all model in AsyncAPI document
|
|
@@ -303,33 +346,30 @@ export class Codegen {
|
|
|
303
346
|
* @param outputPath - output path for generated files
|
|
304
347
|
*/
|
|
305
348
|
async exportSettings(
|
|
306
|
-
asyncAPIDocument:
|
|
349
|
+
asyncAPIDocument: AsyncAPIDocumentInterface,
|
|
307
350
|
outputPath: string,
|
|
308
351
|
): Promise<void> {
|
|
309
352
|
const serviceTitle = getServiceTitle(asyncAPIDocument);
|
|
310
353
|
const serviceId = getServiceId(asyncAPIDocument);
|
|
311
354
|
const channelsData: ChannelData[] = [];
|
|
312
355
|
|
|
313
|
-
for (const
|
|
314
|
-
const
|
|
315
|
-
if (channel.hasBinding('amqp')) {
|
|
316
|
-
const queueName = channel.binding('amqp')['queue']['name'];
|
|
317
|
-
|
|
318
|
-
const payloadName = channel.hasSubscribe()
|
|
319
|
-
? channel.subscribe().message().payload().title()
|
|
320
|
-
: channel.hasPublish()
|
|
321
|
-
? channel.publish().message().payload().title()
|
|
322
|
-
: 'undefined';
|
|
356
|
+
for (const channel of asyncAPIDocument.channels()) {
|
|
357
|
+
const queueName = channel.bindings().get('amqp')?.value().queue.name;
|
|
323
358
|
|
|
359
|
+
if (queueName !== undefined) {
|
|
360
|
+
const routingKey = channel.address();
|
|
361
|
+
const action = getChannelAction(channel);
|
|
362
|
+
const payloadName = getPayloadTitle(channel) ?? 'undefined';
|
|
324
363
|
channelsData.push({
|
|
325
|
-
routingKey
|
|
326
|
-
queueName
|
|
327
|
-
payloadName
|
|
328
|
-
acceptedAction:
|
|
364
|
+
routingKey,
|
|
365
|
+
queueName,
|
|
366
|
+
payloadName,
|
|
367
|
+
acceptedAction: action,
|
|
329
368
|
isMultiTenant: routingKey.includes('*.*'),
|
|
330
369
|
});
|
|
331
370
|
}
|
|
332
371
|
}
|
|
372
|
+
|
|
333
373
|
await this.generateMessagingSettings(
|
|
334
374
|
serviceId,
|
|
335
375
|
serviceTitle,
|
|
@@ -685,207 +725,3 @@ interface ChannelData {
|
|
|
685
725
|
/** Channel multi tenancy */
|
|
686
726
|
isMultiTenant: boolean;
|
|
687
727
|
}
|
|
688
|
-
|
|
689
|
-
function getRelativeDir(from: string, to: string): string {
|
|
690
|
-
return fs.statSync(to).isFile()
|
|
691
|
-
? path.dirname(path.relative(from, to))
|
|
692
|
-
: path.relative(from, to);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function getMessageTitle(message: Message): string {
|
|
696
|
-
return (
|
|
697
|
-
message.payload().title() ??
|
|
698
|
-
message.extension('x-parser-message-name') ??
|
|
699
|
-
message.payload().extension('x-parser-schema-id')
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* Retrieve Service Title from AsyncAPI document.
|
|
705
|
-
* As Title returned value set in info.title property of document.
|
|
706
|
-
* @param asyncAPIDocument - AsyncAPI document.
|
|
707
|
-
*/
|
|
708
|
-
function getServiceTitle(asyncAPIDocument: AsyncAPIDocument): string {
|
|
709
|
-
return asyncAPIDocument.info().title();
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Retrieve Service Id from AsyncAPI document.
|
|
714
|
-
* As Id returned value set in extension property `x-service-id`.
|
|
715
|
-
* If extension property is not set - returned Service Title in lower-kebab-case.
|
|
716
|
-
* @param asyncAPIDocument - AsyncAPI document.
|
|
717
|
-
*/
|
|
718
|
-
function getServiceId(asyncAPIDocument: AsyncAPIDocument): string {
|
|
719
|
-
return (
|
|
720
|
-
(asyncAPIDocument.extension('x-service-id') as string) ??
|
|
721
|
-
FormatHelpers.toParamCase(getServiceTitle(asyncAPIDocument))
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Converts a path into a POSIX path by replacing current path separator with `/`.
|
|
727
|
-
* @param p - Path to convert.
|
|
728
|
-
*/
|
|
729
|
-
function toPosixPath(p: string): string {
|
|
730
|
-
return path.posix.join(...p.split(path.sep));
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Generating the path prefix for model.
|
|
735
|
-
* model name ends with "command" - prefix "commands"
|
|
736
|
-
* model name ends with "event" - prefix "events"
|
|
737
|
-
* default prefix "types"
|
|
738
|
-
* @param modelName - name of the model, defined in json schema, or AsyncAPI document
|
|
739
|
-
*/
|
|
740
|
-
function getModelPathPrefix(modelName: string): string {
|
|
741
|
-
return modelName
|
|
742
|
-
? modelName.toLowerCase().endsWith('command')
|
|
743
|
-
? 'commands'
|
|
744
|
-
: modelName.toLowerCase().endsWith('event')
|
|
745
|
-
? 'events'
|
|
746
|
-
: 'types'
|
|
747
|
-
: 'types';
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* Adding 'export' for interfaces
|
|
752
|
-
*/
|
|
753
|
-
const EXPORT_INTERFACE_PRESET = {
|
|
754
|
-
interface: {
|
|
755
|
-
self({ content }) {
|
|
756
|
-
return `export ${content}`;
|
|
757
|
-
},
|
|
758
|
-
property({ content }) {
|
|
759
|
-
return content;
|
|
760
|
-
},
|
|
761
|
-
},
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Adding 'export' for union enumeration types
|
|
766
|
-
*/
|
|
767
|
-
const EXPORT_UNION_TYPE_PRESET = {
|
|
768
|
-
type: {
|
|
769
|
-
async self({ renderer }) {
|
|
770
|
-
return `export ${await renderer.defaultSelf()}`;
|
|
771
|
-
},
|
|
772
|
-
},
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* Preset for TypeScriptGenerator, adds descriptions to types and properties.
|
|
777
|
-
*/
|
|
778
|
-
const DESCRIPTION_PRESET = {
|
|
779
|
-
interface: {
|
|
780
|
-
self({ renderer, content, model }) {
|
|
781
|
-
const desc = model.getFromOriginalInput('description');
|
|
782
|
-
if (desc) {
|
|
783
|
-
const renderedDesc = renderer.renderComments(desc);
|
|
784
|
-
return `${renderedDesc}\n${content}`;
|
|
785
|
-
}
|
|
786
|
-
return content;
|
|
787
|
-
},
|
|
788
|
-
property({ renderer, model, content, propertyName }) {
|
|
789
|
-
if (model.getFromOriginalInput('properties') !== undefined) {
|
|
790
|
-
const property = model.getFromOriginalInput('properties')[propertyName];
|
|
791
|
-
if (property !== undefined) {
|
|
792
|
-
const desc = property['description'];
|
|
793
|
-
if (desc) {
|
|
794
|
-
const renderedDesc = renderer.renderComments(desc);
|
|
795
|
-
return `${renderedDesc}\n${content}`;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
return content;
|
|
800
|
-
},
|
|
801
|
-
},
|
|
802
|
-
};
|
|
803
|
-
|
|
804
|
-
/**
|
|
805
|
-
* Preset for TypeScriptGenerator, additionalProperties added to models as "[k: string]: unknown;"
|
|
806
|
-
*/
|
|
807
|
-
const ADDITIONAL_PROPERTIES_PRESET = {
|
|
808
|
-
interface: {
|
|
809
|
-
self({ content }) {
|
|
810
|
-
return content;
|
|
811
|
-
},
|
|
812
|
-
property({ renderer, propertyName, property, type }) {
|
|
813
|
-
if (type === PropertyType.additionalProperty) {
|
|
814
|
-
return property.originalInput === true
|
|
815
|
-
? `\n[k: string]: unknown;`
|
|
816
|
-
: renderer
|
|
817
|
-
.renderProperty(propertyName, property)
|
|
818
|
-
.replace('additionalProperties?:', '[k: string]:');
|
|
819
|
-
}
|
|
820
|
-
return renderer.renderProperty(propertyName, property, type);
|
|
821
|
-
},
|
|
822
|
-
},
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Preset for TypeScriptGenerator, adds imports to resulting model.
|
|
827
|
-
*/
|
|
828
|
-
const IMPORTS_PRESET = {
|
|
829
|
-
interface: {
|
|
830
|
-
self({ content, model }) {
|
|
831
|
-
const interfaceName = model.getFromOriginalInput('title');
|
|
832
|
-
const interfacePrefix = getModelPathPrefix(interfaceName);
|
|
833
|
-
const dependencies = model.getNearestDependencies(model);
|
|
834
|
-
const imports = dependencies.map((dep) => ({
|
|
835
|
-
class: FormatHelpers.toPascalCase(dep),
|
|
836
|
-
location:
|
|
837
|
-
interfacePrefix !== getModelPathPrefix(dep)
|
|
838
|
-
? `../${getModelPathPrefix(dep)}/${FormatHelpers.toParamCase(dep)}`
|
|
839
|
-
: `./${FormatHelpers.toParamCase(dep)}`,
|
|
840
|
-
}));
|
|
841
|
-
return endent`
|
|
842
|
-
${imports
|
|
843
|
-
.sort((a, b) => a.location.localeCompare(b.location))
|
|
844
|
-
.map((i) => `import { ${i.class} } from '${i.location}';`)
|
|
845
|
-
.join('\n')}
|
|
846
|
-
${content}`;
|
|
847
|
-
},
|
|
848
|
-
property({ content }) {
|
|
849
|
-
return content;
|
|
850
|
-
},
|
|
851
|
-
},
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
/**
|
|
855
|
-
* Preset for TypeScriptGenerator, sets nullable properties as unions in resulted models(compatibility with zapatos generated models).
|
|
856
|
-
*/
|
|
857
|
-
const NULLABLE_PROPERTY_TO_UNION_PRESET = {
|
|
858
|
-
interface: {
|
|
859
|
-
self({ content }) {
|
|
860
|
-
return content;
|
|
861
|
-
},
|
|
862
|
-
property({ renderer, model, content, propertyName }) {
|
|
863
|
-
if (model.getFromOriginalInput('properties') !== undefined) {
|
|
864
|
-
const property = model.getFromOriginalInput('properties')[propertyName];
|
|
865
|
-
if (property !== undefined) {
|
|
866
|
-
if (Array.isArray(property['type'])) {
|
|
867
|
-
const renderProp = renderer.renderProperty(propertyName, property);
|
|
868
|
-
return renderProp.replace(
|
|
869
|
-
'object',
|
|
870
|
-
content.split(':')[1].replace(';', ''),
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return content;
|
|
877
|
-
},
|
|
878
|
-
},
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Extension for @asyncapi/modelina, to make sure that the type name is in pascal.
|
|
883
|
-
*/
|
|
884
|
-
const CustomNamingConvention: CommonNamingConvention = {
|
|
885
|
-
type: (name) => {
|
|
886
|
-
if (!name) {
|
|
887
|
-
return '';
|
|
888
|
-
}
|
|
889
|
-
return FormatHelpers.toPascalCase(name);
|
|
890
|
-
},
|
|
891
|
-
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { FormatHelpers, TypeScriptPreset } from '@asyncapi/modelina';
|
|
2
|
+
import endent from 'endent';
|
|
3
|
+
import {
|
|
4
|
+
ensureNullable,
|
|
5
|
+
getModelPathPrefix,
|
|
6
|
+
indexOfNullArrayProperty,
|
|
7
|
+
indexOfNullObjectProperty,
|
|
8
|
+
replaceAnyType,
|
|
9
|
+
} from './utils';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exports types.
|
|
13
|
+
*/
|
|
14
|
+
export const EXPORT_TYPES_PRESET: TypeScriptPreset = {
|
|
15
|
+
interface: {
|
|
16
|
+
self({ content }) {
|
|
17
|
+
return `export ${content}`;
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
enum: {
|
|
21
|
+
self({ content }) {
|
|
22
|
+
return `export ${content}`;
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
type: {
|
|
26
|
+
self({ content }) {
|
|
27
|
+
return `export ${content}`;
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Preset for TypeScriptGenerator, additionalProperties added to models as "[k: string]: unknown;"
|
|
34
|
+
*/
|
|
35
|
+
export const ADDITIONAL_PROPERTIES_PRESET: TypeScriptPreset = {
|
|
36
|
+
interface: {
|
|
37
|
+
self({ content }) {
|
|
38
|
+
return content;
|
|
39
|
+
},
|
|
40
|
+
property({ property, content }) {
|
|
41
|
+
if (property.unconstrainedPropertyName === 'additionalProperties') {
|
|
42
|
+
return `\n[k: string]: unknown;`;
|
|
43
|
+
}
|
|
44
|
+
return content;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Preset for TypeScriptGenerator, adds imports to resulting model.
|
|
51
|
+
*/
|
|
52
|
+
export const IMPORTS_PRESET: TypeScriptPreset = {
|
|
53
|
+
interface: {
|
|
54
|
+
self({ content, model }) {
|
|
55
|
+
const interfacePrefix = getModelPathPrefix(model.name);
|
|
56
|
+
const dependencies = model.getNearestDependencies();
|
|
57
|
+
|
|
58
|
+
const imports = dependencies.map((dep) => ({
|
|
59
|
+
class: FormatHelpers.toPascalCase(dep.name),
|
|
60
|
+
location:
|
|
61
|
+
interfacePrefix !== getModelPathPrefix(dep.name)
|
|
62
|
+
? `../${getModelPathPrefix(dep.name)}/${FormatHelpers.toParamCase(
|
|
63
|
+
dep.name,
|
|
64
|
+
)}`
|
|
65
|
+
: `./${FormatHelpers.toParamCase(dep.name)}`,
|
|
66
|
+
}));
|
|
67
|
+
return endent`
|
|
68
|
+
${imports
|
|
69
|
+
.sort((a, b) => a.location.localeCompare(b.location))
|
|
70
|
+
.map((i) => `import { ${i.class} } from '${i.location}';`)
|
|
71
|
+
.join('\n')}
|
|
72
|
+
${content}`;
|
|
73
|
+
},
|
|
74
|
+
property({ content }) {
|
|
75
|
+
return content;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Preset for TypeScriptGenerator, sets nullable properties as unions in resulted models(compatibility with zapatos generated models).
|
|
82
|
+
*/
|
|
83
|
+
export const NULLABLE_PROPERTY_TO_UNION_PRESET: TypeScriptPreset = {
|
|
84
|
+
interface: {
|
|
85
|
+
self({ content }) {
|
|
86
|
+
return content;
|
|
87
|
+
},
|
|
88
|
+
property({ renderer, property, content }) {
|
|
89
|
+
let index = indexOfNullObjectProperty(property);
|
|
90
|
+
|
|
91
|
+
// Regular object property, not array.
|
|
92
|
+
if (index > -1) {
|
|
93
|
+
property.property.type = ensureNullable(property.property.type, index);
|
|
94
|
+
return renderer.renderProperty(property);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Array property.
|
|
98
|
+
index = indexOfNullArrayProperty(property);
|
|
99
|
+
if (index > -1) {
|
|
100
|
+
// This pattern is not perfect but it's good enough for this use case.
|
|
101
|
+
const regex = /((?<single>\w+)|\((?<multiple>[\w\s|]+)\))\[\]/;
|
|
102
|
+
const match = regex.exec(property.property.type);
|
|
103
|
+
if (match && (match?.groups?.single || match?.groups?.multiple)) {
|
|
104
|
+
const newType = ensureNullable(
|
|
105
|
+
match.groups.single ?? match.groups.multiple,
|
|
106
|
+
index,
|
|
107
|
+
);
|
|
108
|
+
// At this point we assume that the type will be a union of at least two types - null and something else.
|
|
109
|
+
property.property.type = `(${newType})[]`;
|
|
110
|
+
return renderer.renderProperty(property);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return content;
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Converts all occurrences of 'any' to 'unknown'.
|
|
121
|
+
*/
|
|
122
|
+
export const ANY_TO_UNKNOWN_PRESET: TypeScriptPreset = {
|
|
123
|
+
interface: {
|
|
124
|
+
property({ renderer, property }) {
|
|
125
|
+
property.property.type = replaceAnyType(
|
|
126
|
+
property.property.type,
|
|
127
|
+
'unknown',
|
|
128
|
+
);
|
|
129
|
+
return renderer.renderProperty(property);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
type: {
|
|
133
|
+
self({ content }) {
|
|
134
|
+
// TODO: This is inconsistent with interface but not sure how to use type renderer.
|
|
135
|
+
return replaceAnyType(content, 'unknown');
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|