Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions docs/generators/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ label: Generators
---

# Generators
Generators, or preset's are the core of The Codegen Project, that determines what is generated for your project.
Generators, or preset's are the core of **The Codegen Project**, that determines what is generated for your project.

Each language and inputs have specific generators;

All available generators, across languages and inputs:
- [`payloads`](./payloads.md)
- [`parameters`](./parameters.md)
- [`headers`](./headers.md)
- [`types`](./types.md)
- [`channels`](./channels.md)
- [`client`](./client.md)
- [`custom`](./custom.md)

| **Inputs** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`custom`](./custom.md) |
|---|---|---|---|---|---|---|
| AsyncAPI | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| **Inputs** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`custom`](./custom.md) |
|---|---|---|---|---|---|---|---|
| AsyncAPI | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |

| **Languages** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`custom`](./custom.md) |
|---|---|---|---|---|---|---|
| TypeScript | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| **Languages** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`custom`](./custom.md) |
|---|---|---|---|---|---|---|---|
| TypeScript | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
34 changes: 34 additions & 0 deletions docs/generators/types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
sidebar_position: 99
---

# Types

```js
export default {
...,
generators: [
{
preset: 'types',
outputPath: './src/types',
language: 'typescript',
}
]
};
```

`types` preset is for generating simple types and utility functions that change based on the AsyncAPI document.

This is supported through the following inputs: `asyncapi`

It supports the following languages; `typescript`

## What it generates
Here is what each language generate with this generator.

### TypeScript

- A type that represents all the channel addresses in the document
- A type that represents all the channel IDs in the document
- A function that converts channel IDs to channel addresses
- A function that converts channel addresses to channel IDs
11 changes: 10 additions & 1 deletion src/codegen/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import {
} from './generators/typescript/client';
import path from 'path';
import {loadAsyncapi} from './inputs/asyncapi';
import {defaultTypeScriptHeadersOptions} from './generators/typescript';
import {
defaultTypeScriptHeadersOptions,
defaultTypeScriptTypesOptions
} from './generators/typescript';
const moduleName = 'codegen';
const explorer = cosmiconfig(moduleName, {
searchPlaces: [
Expand Down Expand Up @@ -201,6 +204,12 @@ export function getDefaultConfiguration(
return defaultTypeScriptHeadersOptions;
}
break;
case 'types':
switch (language) {
case 'typescript':
return defaultTypeScriptTypesOptions;
}
break;
case 'channels':
switch (language) {
case 'typescript':
Expand Down
6 changes: 6 additions & 0 deletions src/codegen/generators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export {
TypescriptHeadersGenerator,
defaultTypeScriptHeadersOptions,
generateTypescriptHeaders,
TypescriptHeadersGeneratorInternal,
TypescriptTypesContext,
TypescriptTypesGenerator,
TypescriptTypesGeneratorInternal,
defaultTypeScriptTypesOptions,
generateTypescriptTypes,
ChannelFunctionTypes
} from './typescript';
export {defaultCustomGenerator, CustomGenerator} from './generic/custom';
Expand Down
10 changes: 9 additions & 1 deletion src/codegen/generators/typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,13 @@ export {
generateTypescriptHeaders,
TypescriptHeadersContext,
TypescriptHeadersGenerator,
defaultTypeScriptHeadersOptions
defaultTypeScriptHeadersOptions,
TypescriptHeadersGeneratorInternal
} from './headers';
export {
generateTypescriptTypes,
TypescriptTypesContext,
TypescriptTypesGenerator,
defaultTypeScriptTypesOptions,
TypescriptTypesGeneratorInternal
} from './types';
96 changes: 96 additions & 0 deletions src/codegen/generators/typescript/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {AsyncAPIDocumentInterface} from '@asyncapi/parser';
import {GenericCodegenContext, TypesRenderType} from '../../types';
import {z} from 'zod';
import path from 'path';
import {mkdir, writeFile} from 'fs/promises';

export const zodTypescriptTypesGenerator = z.object({
id: z.string().optional().default('types-typescript'),
dependencies: z.array(z.string()).optional().default([]),
preset: z.literal('types').default('types'),
outputPath: z.string().default('src/__gen__'),
language: z.literal('typescript').optional().default('typescript')
});

export type TypescriptTypesGenerator = z.input<
typeof zodTypescriptTypesGenerator
>;
export type TypescriptTypesGeneratorInternal = z.infer<
typeof zodTypescriptTypesGenerator
>;

export const defaultTypeScriptTypesOptions: TypescriptTypesGeneratorInternal =
zodTypescriptTypesGenerator.parse({});

export interface TypescriptTypesContext extends GenericCodegenContext {
inputType: 'asyncapi';
asyncapiDocument?: AsyncAPIDocumentInterface;
generator: TypescriptTypesGeneratorInternal;
}

export type TypeScriptTypesRenderType =
TypesRenderType<TypescriptTypesGeneratorInternal>;

export async function generateTypescriptTypes(
context: TypescriptTypesContext
): Promise<TypeScriptTypesRenderType> {
const {asyncapiDocument, inputType, generator} = context;
if (inputType === 'asyncapi' && asyncapiDocument === undefined) {
throw new Error('Expected AsyncAPI input, was not given');
}
const allChannels = asyncapiDocument!.allChannels().all();
const channelAddressUnion = allChannels
.map((channel) => {
return `'${channel.address()}'`;
})
.join(' | ');
const channelIdUnion = allChannels
.map((channel) => {
return `'${channel.id()}'`;
})
.join(' | ');
const channelIdSwitch = allChannels
.map((channel) => {
return `case '${channel.id()}':
return '${channel.address()}';`;
})
.join('\n ');
const channelAddressSwitch = allChannels
.map((channel) => {
return `case '${channel.address()}':
return '${channel.id()}';`;
})
.join('\n ');

await mkdir(context.generator.outputPath, {recursive: true});
let result = `export type Topics = ${channelAddressUnion};\n`;
// For version 2.x we only need to generate topics
if (!asyncapiDocument!.version().startsWith('2.')) {
const topicIdsPart = `export type TopicIds = ${channelIdUnion};\n`;
const toTopicIdsPart = `export function ToTopicIds(topic: Topics): TopicIds {
switch (topic) {
${channelAddressSwitch}
default:
throw new Error('Unknown topic: ' + topic);
}
}\n`;
const toTopicsPart = `export function ToTopics(topicId: TopicIds): Topics {
switch (topicId) {
${channelIdSwitch}
default:
throw new Error('Unknown topic ID: ' + topicId);
}
}\n`;

result += topicIdsPart + toTopicIdsPart + toTopicsPart;
}
await writeFile(
path.resolve(context.generator.outputPath, 'Types.ts'),
result,
{}
);
return {
result,
generator
};
}
25 changes: 24 additions & 1 deletion src/codegen/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
generateTypeScriptClient,
generateTypescriptParameters,
generateTypescriptPayload,
generateTypescriptHeaders
generateTypescriptHeaders,
generateTypescriptTypes
} from './generators';
import path from 'path';
import Graph from 'graphology';
Expand Down Expand Up @@ -104,6 +105,28 @@ export async function renderGenerator(
}
}

case 'types': {
switch (language) {
case 'typescript': {
return generateTypescriptTypes({
asyncapiDocument,
generator: {
...generator,
outputPath
},
inputType: configuration.inputType,
dependencyOutputs: renderedContext
});
}

default: {
throw new Error(
'Unable to determine language generator for types preset'
);
}
}
}

case 'channels': {
switch (language) {
case 'typescript': {
Expand Down
16 changes: 16 additions & 0 deletions src/codegen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ import {
zodTypescriptHeadersGenerator
} from './generators/typescript/headers';
import {TypeScriptClientRenderType} from './generators/typescript/client/types';
import {
TypescriptTypesGenerator,
TypescriptTypesGeneratorInternal,
TypeScriptTypesRenderType,
zodTypescriptTypesGenerator
} from './generators/typescript/types';
export type PresetTypes =
| 'payloads'
| 'parameters'
| 'headers'
| 'types'
| 'channels'
| 'channel-type'
| 'custom'
| 'client';
export interface LoadArgument {
Expand All @@ -59,6 +67,7 @@ export const zodAsyncAPITypeScriptGenerators = z.discriminatedUnion('preset', [
zodTypescriptChannelsGenerator,
zodTypescriptClientGenerator,
zodTypescriptHeadersGenerator,
zodTypescriptTypesGenerator,
zodCustomGenerator
]);

Expand All @@ -68,6 +77,7 @@ export const zodAsyncAPIGenerators = z.union([

export type Generators =
| TypescriptHeadersGenerator
| TypescriptTypesGenerator
| TypeScriptPayloadGenerator
| TypescriptParametersGenerator
| TypeScriptChannelsGenerator
Expand All @@ -80,13 +90,15 @@ export type GeneratorsInternal =
| TypeScriptChannelsGeneratorInternal
| TypeScriptClientGeneratorInternal
| TypescriptHeadersGeneratorInternal
| TypescriptTypesGeneratorInternal
| CustomGeneratorInternal;

export type RenderTypes =
| TypeScriptChannelRenderType
| TypeScriptPayloadRenderType
| TypeScriptParameterRenderType
| TypeScriptHeadersRenderType
| TypeScriptTypesRenderType
| TypeScriptClientRenderType
| CustomGenerator;
export interface ParameterRenderType<GeneratorType> {
Expand All @@ -97,6 +109,10 @@ export interface HeadersRenderType<GeneratorType> {
channelModels: Record<string, OutputModel | undefined>;
generator: GeneratorType;
}
export interface TypesRenderType<GeneratorType> {
result: string;
generator: GeneratorType;
}
export interface ChannelPayload {
messageModel: OutputModel;
messageType: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`types typescript should work with basic AsyncAPI 2.x inputs 1`] = `
"export type Topics = 'user/signedup';
"
`;

exports[`types typescript should work with basic AsyncAPI 3.x inputs 1`] = `
"export type Topics = 'user/signedup';
export type TopicIds = 'userSignedup';
export function ToTopicIds(topic: Topics): TopicIds {
switch (topic) {
case 'user/signedup':
return 'userSignedup';
default:
throw new Error('Unknown topic: ' + topic);
}
}
export function ToTopics(topicId: TopicIds): Topics {
switch (topicId) {
case 'userSignedup':
return 'user/signedup';
default:
throw new Error('Unknown topic ID: ' + topicId);
}
}
"
`;
42 changes: 42 additions & 0 deletions test/codegen/generators/typescript/types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import path from "node:path";
import { loadAsyncapi } from "../../../helpers";
import { generateTypescriptTypes } from "../../../../src/codegen/generators";

describe('types', () => {
describe('typescript', () => {
it('should work with basic AsyncAPI 2.x inputs', async () => {
const parsedAsyncAPIDocument = await loadAsyncapi(path.resolve(__dirname, '../../../configs/asyncapi.yaml'));

const renderedContent = await generateTypescriptTypes({
generator: {
outputPath: path.resolve(__dirname, './output'),
preset: 'types',
language: 'typescript',
dependencies: [],
id: 'test'
},
inputType: 'asyncapi',
asyncapiDocument: parsedAsyncAPIDocument,
dependencyOutputs: { }
});
expect(renderedContent.result).toMatchSnapshot();
});
it('should work with basic AsyncAPI 3.x inputs', async () => {
const parsedAsyncAPIDocument = await loadAsyncapi(path.resolve(__dirname, '../../../configs/asyncapi-3.yaml'));

const renderedContent = await generateTypescriptTypes({
generator: {
outputPath: path.resolve(__dirname, './output'),
preset: 'types',
language: 'typescript',
dependencies: [],
id: 'test'
},
inputType: 'asyncapi',
asyncapiDocument: parsedAsyncAPIDocument,
dependencyOutputs: { }
});
expect(renderedContent.result).toMatchSnapshot();
});
});
});
Loading