@ovotech/avro-ts
Advanced tools
Comparing version 3.1.0 to 4.0.0
@@ -1,65 +0,2 @@ | ||
import { Schema, schema } from 'avsc'; | ||
import * as ts from 'typescript'; | ||
export interface Registry { | ||
[key: string]: ts.InterfaceDeclaration; | ||
} | ||
declare type UnionRegistry = { | ||
[key: string]: { | ||
members: string[]; | ||
namespace: string; | ||
}; | ||
}; | ||
export interface Context { | ||
recordAlias: string; | ||
namesAlias: string; | ||
namespacedPrefix: string; | ||
registry: Registry; | ||
unionRegistry: UnionRegistry; | ||
recordNeedsNS: boolean; | ||
parentName?: string; | ||
namespace?: string; | ||
namespaces: { | ||
[key: string]: ts.TypeReferenceNode; | ||
}; | ||
logicalTypes: { | ||
[key: string]: ts.TypeReferenceNode; | ||
}; | ||
visitedLogicalTypes: Array<string>; | ||
} | ||
export interface Result<TsType = ts.TypeNode> { | ||
type: TsType; | ||
context: Context; | ||
} | ||
export declare type Convert<TType = Schema> = (context: Context, type: TType) => Result<any>; | ||
export declare const result: <TsType = ts.TypeNode>(context: Context, type: TsType) => Result<TsType>; | ||
export declare const mapContext: <T = any, TsType = ts.TypeNode>(context: Context, items: T[], callbackfn: (context: Context, item: T) => Result<TsType>) => { | ||
items: TsType[]; | ||
context: Context; | ||
}; | ||
export declare const withContexts: (context: Context, items: Context[]) => Context; | ||
export declare const withEntry: (context: Context, entry: ts.InterfaceDeclaration) => Context; | ||
export declare const withNamespace: (context: Context, record: schema.RecordType) => Context; | ||
export interface State { | ||
output: string[]; | ||
repository: { | ||
[key: string]: string; | ||
}; | ||
logicalTypes: { | ||
[key: string]: string; | ||
}; | ||
} | ||
declare type LogicalTypeWithImport = { | ||
import: string; | ||
type: string; | ||
}; | ||
declare type LogicalTypeDefinition = string | LogicalTypeWithImport; | ||
declare type AvroTsOptions = { | ||
logicalTypes?: { | ||
[key: string]: LogicalTypeDefinition; | ||
}; | ||
recordAlias?: string; | ||
namespacedPrefix?: string; | ||
namesAlias?: string; | ||
}; | ||
export declare function avroTs(recordType: schema.RecordType, options?: AvroTsOptions): string; | ||
export {}; | ||
export { toTypeScript, convertType } from './convert'; | ||
export { Context, CustomLogicalType } from './types'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ts = require("typescript"); | ||
exports.result = (context, type) => ({ | ||
context, | ||
type, | ||
}); | ||
exports.mapContext = (context, items, callbackfn) => items.reduce((all, item) => { | ||
const current = callbackfn(all.context, item); | ||
return { | ||
items: [...all.items, current.type], | ||
context: current.context, | ||
}; | ||
}, { items: [], context }); | ||
exports.withContexts = (context, items) => items.reduce((all, itemContext) => ({ | ||
...all, | ||
registry: { ...all.registry, ...itemContext.registry }, | ||
namespaces: { ...all.namespaces, ...itemContext.namespaces }, | ||
}), context); | ||
exports.withEntry = (context, entry) => ({ | ||
...context, | ||
registry: { ...context.registry, [entry.name.text]: entry }, | ||
}); | ||
exports.withNamespace = (context, record) => ({ | ||
...context, | ||
namespace: record.namespace, | ||
namespaces: { | ||
...context.namespaces, | ||
[fullyQualifiedName(context, record)]: ts.createTypeReferenceNode(record.name, undefined), | ||
}, | ||
}); | ||
const docToJSDoc = (doc) => `*\n${doc | ||
.split('\n') | ||
.map(line => ` * ${line}`) | ||
.join('\n')}\n `; | ||
const convertRecord = (context, type) => { | ||
const namespaceContext = type.namespace ? exports.withNamespace(context, type) : context; | ||
const fieldContext = { ...namespaceContext, recordNeedsNS: false }; | ||
const fields = type.fields.map(fieldType => { | ||
const field = convertType({ ...fieldContext, parentName: type.name }, fieldType.type); | ||
const prop = ts.createPropertySignature(undefined, fieldType.name, isOptional(fieldType.type) ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined, field.type, undefined); | ||
const propWithDoc = fieldType.doc | ||
? ts.addSyntheticLeadingComment(prop, ts.SyntaxKind.MultiLineCommentTrivia, docToJSDoc(fieldType.doc), true) | ||
: prop; | ||
return exports.result(field.context, propWithDoc); | ||
}); | ||
const interfaceType = ts.createInterfaceDeclaration(undefined, [ts.createToken(ts.SyntaxKind.ExportKeyword)], type.name, undefined, undefined, fields.map(field => field.type)); | ||
const recordContext = exports.withContexts(exports.withEntry(fieldContext, interfaceType), fields.map(item => item.context)); | ||
if (context.recordNeedsNS) { | ||
const namespaced = fullyQualifiedName(context, type); | ||
const props = [ | ||
ts.createPropertySignature(undefined, ts.createStringLiteral(namespaced), undefined, ts.createTypeReferenceNode(type.name, undefined), undefined), | ||
]; | ||
const registryKey = getUnionRegistryKey(type, context); | ||
if (registryKey) { | ||
const registryEntry = context.unionRegistry[registryKey] || {}; | ||
props.push(...(registryEntry.members || []) | ||
.filter((name) => name !== type.name) | ||
.map(name => ts.createPropertySignature(undefined, ts.createStringLiteral(`${registryEntry.namespace}.${name}`), ts.createToken(ts.SyntaxKind.QuestionToken), ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword), undefined))); | ||
} | ||
const namespacedInterfaceType = ts.createInterfaceDeclaration(undefined, [ts.createToken(ts.SyntaxKind.ExportKeyword)], `${context.namespacedPrefix}${type.name}`, undefined, undefined, props); | ||
return exports.result(exports.withEntry(recordContext, namespacedInterfaceType), ts.createTypeReferenceNode(namespacedInterfaceType.name.text, undefined)); | ||
} | ||
return exports.result(recordContext, ts.createTypeReferenceNode(type.name, undefined)); | ||
}; | ||
const convertType = (context, type) => { | ||
if (typeof type === 'string') { | ||
return convertPredefinedType(context, type); | ||
} | ||
else if (Array.isArray(type)) { | ||
return convertArrayType(context, type); | ||
} | ||
else if (isLogicalType(type)) { | ||
return convertLogicalType(context, type); | ||
} | ||
else if (isRecordType(type)) { | ||
return convertRecord(context, type); | ||
} | ||
else if (isArrayType(type)) { | ||
const itemType = convertType(context, type.items); | ||
return exports.result(itemType.context, ts.createArrayTypeNode(itemType.type)); | ||
} | ||
else if (isMapType(type)) { | ||
return convertMapType(context, type); | ||
} | ||
else if (isEnumType(type)) { | ||
return convertEnum(context, type); | ||
} | ||
else { | ||
throw new Error(`Cannot work out type ${type}`); | ||
} | ||
}; | ||
const convertPrimitive = (context, avroType) => { | ||
switch (avroType) { | ||
case 'long': | ||
case 'int': | ||
case 'double': | ||
case 'float': | ||
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)); | ||
case 'bytes': | ||
return exports.result(context, ts.createTypeReferenceNode('Buffer', undefined)); | ||
case 'null': | ||
return exports.result(context, ts.createNull()); | ||
case 'boolean': | ||
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)); | ||
case 'string': | ||
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)); | ||
default: | ||
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); | ||
} | ||
}; | ||
const convertEnum = (context, enumType) => exports.result(context, ts.createUnionTypeNode(enumType.symbols.map(symbol => ts.createLiteralTypeNode(ts.createLiteral(symbol))))); | ||
const convertLogicalType = (context, type) => { | ||
if (context.logicalTypes[type.logicalType]) { | ||
if (!context.visitedLogicalTypes.includes(type.logicalType)) | ||
context.visitedLogicalTypes.push(type.logicalType); | ||
return exports.result(context, context.logicalTypes[type.logicalType]); | ||
} | ||
return convertPrimitive(context, type.type); | ||
}; | ||
const convertPredefinedType = (context, type) => context.namespaces[type] ? exports.result(context, context.namespaces[type]) : convertPrimitive(context, type); | ||
const recordNeedsNS = (items) => { | ||
return items.filter(item => typeof item === 'object' && !Array.isArray(item) && isRecordType(item)).length > 1; | ||
}; | ||
const convertArrayType = (context, type) => { | ||
const map = exports.mapContext(context, type, (itemContext, item) => { | ||
if (typeof item === 'object' && !Array.isArray(item) && isRecordType(item)) { | ||
return convertType({ ...itemContext, recordNeedsNS: recordNeedsNS(type) }, item); | ||
} | ||
else { | ||
return convertType(itemContext, item); | ||
} | ||
}); | ||
return exports.result(map.context, ts.createUnionTypeNode(map.items)); | ||
}; | ||
const convertMapType = (context, type) => { | ||
const map = convertType(context, type.values); | ||
return exports.result(map.context, ts.createTypeLiteralNode([ | ||
ts.createIndexSignature(undefined, undefined, [ | ||
ts.createParameter(undefined, undefined, undefined, 'index', undefined, ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), undefined), | ||
], map.type), | ||
])); | ||
}; | ||
const isRecordType = (type) => typeof type === 'object' && 'type' in type && type.type === 'record'; | ||
const isArrayType = (type) => typeof type === 'object' && 'type' in type && type.type === 'array'; | ||
const isMapType = (type) => typeof type === 'object' && 'type' in type && type.type === 'map'; | ||
const isEnumType = (type) => typeof type === 'object' && 'type' in type && type.type === 'enum'; | ||
const isLogicalType = (type) => typeof type === 'object' && 'logicalType' in type; | ||
const isUnion = (type) => typeof type === 'object' && Array.isArray(type); | ||
const isRecordParent = (type) => typeof type === 'object' && typeof type.type === 'object'; | ||
const isUnionParent = (type) => Array.isArray(type.type); | ||
const isOptional = (type) => { | ||
if (isUnion(type)) { | ||
const t1 = type[0]; | ||
if (typeof t1 === 'string') { | ||
return t1 === 'null'; | ||
} | ||
} | ||
return false; | ||
}; | ||
const fullyQualifiedName = (context, type) => { | ||
const currentNamespace = type.namespace || context.namespace; | ||
return currentNamespace ? `${currentNamespace}.${type.name}` : type.name; | ||
}; | ||
const printAstNode = (nodes, { importLines }) => { | ||
const resultFile = ts.createSourceFile('someFileName.ts', '', ts.ScriptTarget.Latest); | ||
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); | ||
return importLines.concat(nodes.map(n => printer.printNode(ts.EmitHint.Unspecified, n, resultFile))).join('\n\n'); | ||
}; | ||
const defaultOptions = { | ||
recordAlias: 'Record', | ||
namespacedPrefix: 'Namespaced', | ||
namesAlias: 'Names', | ||
}; | ||
function avroTs(recordType, options = {}) { | ||
const logicalTypes = options.logicalTypes || {}; | ||
const isRootUnion = Array.isArray(recordType) && recordNeedsNS(recordType); | ||
const context = { | ||
...options, | ||
recordAlias: options.recordAlias || defaultOptions.recordAlias, | ||
namesAlias: options.namesAlias || defaultOptions.namesAlias, | ||
namespacedPrefix: options.namespacedPrefix || defaultOptions.namespacedPrefix, | ||
recordNeedsNS: isRootUnion, | ||
registry: {}, | ||
unionRegistry: buildUnionRegistry({}, recordType, { namespace: recordType.namespace, recordNeedsNS: isRootUnion }), | ||
namespaces: {}, | ||
visitedLogicalTypes: [], | ||
logicalTypes: Object.entries(logicalTypes).reduce((all, [name, type]) => { | ||
const typeStr = type.type ? type.type : type; | ||
return { | ||
...all, | ||
[name]: ts.createTypeReferenceNode(typeStr, undefined), | ||
}; | ||
}, {}), | ||
}; | ||
const mainNode = convertType(context, recordType); | ||
const importLines = context.visitedLogicalTypes | ||
.map(visitedType => logicalTypes[visitedType].import) | ||
.filter(Boolean); | ||
const nodes = [ | ||
ts.createTypeAliasDeclaration(undefined, [ts.createToken(ts.SyntaxKind.ExportKeyword)], context.recordAlias, undefined, mainNode.type), | ||
].concat(Object.values(mainNode.context.registry)); | ||
const namesNamespace = unionRegisryToNamespace(context.unionRegistry, context.namesAlias); | ||
if (namesNamespace) { | ||
nodes.unshift(namesNamespace); | ||
} | ||
return printAstNode(nodes, { importLines }); | ||
} | ||
exports.avroTs = avroTs; | ||
function unionRegisryToNamespace(registry, namespaceName) { | ||
const names = Object.keys(registry).reduce((nodes, key) => { | ||
const { namespace, members } = registry[key]; | ||
return nodes.concat(members.map(name => ts.createVariableStatement([ts.createToken(ts.SyntaxKind.ExportKeyword)], ts.createVariableDeclarationList([ts.createVariableDeclaration(name, undefined, ts.createLiteral(`${namespace}.${name}`))], ts.NodeFlags.Const)))); | ||
}, []); | ||
if (!names.length) { | ||
return; | ||
} | ||
const nsNode = ts.createModuleDeclaration([], [ts.createToken(ts.SyntaxKind.ExportKeyword)], ts.createIdentifier(namespaceName), ts.createModuleBlock(names), ts.NodeFlags.Namespace); | ||
return nsNode; | ||
} | ||
const getUnionRegistryKey = (type, { parentName, namespace }) => { | ||
const currentNamespace = type.namespace || namespace; | ||
if (currentNamespace) { | ||
return parentName ? `${currentNamespace}.${parentName}` : currentNamespace; | ||
} | ||
}; | ||
function buildUnionRegistry(registry, schema, context) { | ||
if (Array.isArray(schema)) { | ||
return schema.reduce((acc, schema) => buildUnionRegistry(acc, schema, context), registry); | ||
} | ||
if (isRecordParent(schema)) { | ||
return buildUnionRegistry(registry, schema.type, { | ||
...context, | ||
recordNeedsNS: isUnionParent(schema) && recordNeedsNS(schema.type), | ||
}); | ||
} | ||
if (isRecordType(schema)) { | ||
const { name, fields } = schema; | ||
const namespace = schema.namespace || context.namespace; | ||
const registryKey = getUnionRegistryKey(schema, context); | ||
if (registryKey && context.recordNeedsNS) { | ||
if (!registry[registryKey]) { | ||
registry[registryKey] = { namespace: namespace, members: [] }; | ||
} | ||
registry[registryKey].members.push(name); | ||
} | ||
fields.reduce((acc, field) => | ||
// @ts-ignore This works, but a bit too dynamic for TS | ||
buildUnionRegistry(acc, field, { | ||
...context, | ||
unionMember: false, | ||
parentName: schema.name, | ||
namespace: schema.namespace || context.namespace, | ||
}), registry); | ||
} | ||
return registry; | ||
} | ||
//# sourceMappingURL=index.js.map | ||
var convert_1 = require("./convert"); | ||
exports.toTypeScript = convert_1.toTypeScript; | ||
exports.convertType = convert_1.convertType; |
{ | ||
"name": "@ovotech/avro-ts", | ||
"description": "Convert avro schemas into typescript interfaces", | ||
"version": "3.1.0", | ||
"version": "4.0.0", | ||
"main": "dist/index.js", | ||
@@ -11,28 +11,31 @@ "source": "src/index.ts", | ||
"scripts": { | ||
"test-js": "jest --runInBand", | ||
"test-ts": "tsc test/integration.ts --strict --noEmit && ! tsc test/integration-should-fail.ts --strict --noEmit", | ||
"test": "yarn test-js && yarn test-ts", | ||
"lint-prettier": "prettier --list-different {src,test}/**/*.ts", | ||
"lint-tslint": "tslint --config tslint.json '{src,test}/**/*.ts'", | ||
"lint": "yarn lint-prettier && yarn lint-tslint", | ||
"build": "tsc --outDir dist --declaration" | ||
"test:js": "jest --runInBand", | ||
"test:ts": "tsc test/integration.ts --strict --noEmit && ! tsc test/integration-should-fail.ts --strict --noEmit", | ||
"test": "yarn test:js && yarn test:ts", | ||
"lint:prettier": "prettier --list-different {src,test}/**/*.ts", | ||
"lint:eslint": "eslint '{src,test}/**/*.ts'", | ||
"lint": "yarn lint:prettier && yarn lint:eslint", | ||
"build": "tsc --outDir dist --declaration", | ||
"build:docs": "build-docs README.md" | ||
}, | ||
"devDependencies": { | ||
"@ovotech/avro-logical-types": "^1.0.0", | ||
"@types/jest": "^24.0.13", | ||
"@types/node": "^11.11.4", | ||
"avsc": "^5.4.10", | ||
"jest": "^24.8.0", | ||
"prettier": "^1.17.1", | ||
"ts-jest": "^24.0.2", | ||
"tslint": "^5.17.0", | ||
"tslint-config-prettier": "^1.18.0" | ||
"@types/jest": "^24.0.24", | ||
"@types/node": "^13.1.0", | ||
"avsc": "^5.4.18", | ||
"eslint-config-prettier": "^6.7.0", | ||
"jest": "^24.9.0", | ||
"moment": "^2.24.0", | ||
"prettier": "^1.19.1", | ||
"ts-jest": "^24.3.0", | ||
"ts-node": "^8.5.4" | ||
}, | ||
"jest": { | ||
"preset": "../../jest-preset.json" | ||
"preset": "../../jest.json" | ||
}, | ||
"dependencies": { | ||
"typescript": "^3.5.1" | ||
"@ovotech/ts-compose": "^0.10.0", | ||
"typescript": "^3.7.4" | ||
}, | ||
"gitHead": "25d55039d4369fb71b8573486d2942e365f85ba5" | ||
"gitHead": "821a55c53b17a4055675132e7a272710a3dc6a5a" | ||
} |
225
README.md
@@ -5,3 +5,3 @@ # Avro TS | ||
It consists of a very quick sequential, functional parser. Uses typescript's compiler api to convert avro to typescript AST, and pretty prints the results. No dependencies apart from typescript. | ||
Uses typescript's compiler api to convert avro to typescript AST, and pretty prints the results. | ||
@@ -16,19 +16,60 @@ ### Using | ||
> [examples/simple.ts](examples/simple.ts) | ||
```typescript | ||
import { schema } from 'avsc'; | ||
import { avroTs } from '@ovotech/avro-ts'; | ||
import { toTypeScript } from '@ovotech/avro-ts'; | ||
import { Schema } from 'avsc'; | ||
const avro: schema.RecordType = JSON.parse(String(readFileSync(join(__dirname, 'avro', file)))); | ||
const ts = avroTs(avro, { | ||
const avro: Schema = { | ||
type: 'record', | ||
name: 'User', | ||
fields: [ | ||
{ name: 'id', type: 'int' }, | ||
{ name: 'username', type: 'string' }, | ||
], | ||
}; | ||
const ts = toTypeScript(avro); | ||
console.log(ts); | ||
``` | ||
Resulting TypeScript: | ||
```typescript | ||
export type AvroType = User; | ||
export interface User { | ||
id: number; | ||
username: string; | ||
} | ||
``` | ||
### Logical Types | ||
Avro has [logical types](https://github.com/mtth/avsc/wiki/Advanced-usage#logical-types). In their docs: | ||
> The built-in types provided by Avro are sufficient for many use-cases, but it can often be much more convenient to work with native JavaScript objects. | ||
To support them we need to modify the typescript generation to use the typescript type instead of the logical type. If we don't avro-ts will fall back on the original underlying type. | ||
> [examples/logical-types.ts](examples/logical-types.ts) | ||
```typescript | ||
import { toTypeScript } from '@ovotech/avro-ts'; | ||
import { Schema } from 'avsc'; | ||
const avro: Schema = { | ||
type: 'record', | ||
name: 'Event', | ||
fields: [ | ||
{ name: 'id', type: 'int' }, | ||
{ name: 'createdAt', type: { type: 'long', logicalType: 'timestamp-millis' } }, | ||
], | ||
}; | ||
const ts = toTypeScript(avro, { | ||
logicalTypes: { | ||
'timestamp-millis': 'string', | ||
date: 'string', | ||
decimal: { | ||
type: 'Decimal', | ||
import: "import { Decimal } from 'decimal.js'", | ||
}, | ||
}, | ||
recordAlias: 'Record', | ||
namesAlias: 'Names', | ||
namespacedPrefix: 'Namespaced', | ||
}); | ||
@@ -39,29 +80,153 @@ | ||
## Support | ||
Resulting TypeScript: | ||
This converter currently supports | ||
```typescript | ||
export type AvroType = Event; | ||
- Record | ||
- Union | ||
- Map | ||
- Logical Types | ||
- Enum | ||
- Map | ||
- Array | ||
- Root-level union types | ||
export interface Event { | ||
id: number; | ||
createdAt: string; | ||
} | ||
``` | ||
## Union types helpers. | ||
We can also use custom classes for our logical types. It will also add the code to import the module. | ||
When complex union types are defined, the output will include a namespace (named `Names` by default), containing the namespaced address of properties. | ||
> [examples/custom-logical-types.ts](examples/custom-logical-types.ts) | ||
This allows usecases as such: | ||
```typescript | ||
import { toTypeScript } from '@ovotech/avro-ts'; | ||
import { Schema } from 'avsc'; | ||
const avro: Schema = { | ||
type: 'record', | ||
name: 'Event', | ||
fields: [ | ||
{ name: 'id', type: 'int' }, | ||
{ name: 'decimalValue', type: { type: 'long', logicalType: 'decimal' } }, | ||
{ name: 'anotherDecimal', type: { type: 'long', logicalType: 'decimal' } }, | ||
], | ||
}; | ||
const ts = toTypeScript(avro, { | ||
logicalTypes: { | ||
decimal: { module: 'decimal.js', named: 'Decimal' }, | ||
}, | ||
}); | ||
console.log(ts); | ||
``` | ||
Resulting TypeScript: | ||
```typescript | ||
import { Names, WeatherEvent } from './my-type'; | ||
const event: WeatherEvent = {}; | ||
event[Names.RainEvent]; | ||
import { Decimal } from 'decimal.js'; | ||
export type AvroType = Event; | ||
export interface Event { | ||
id: number; | ||
decimalValue: Decimal; | ||
anotherDecimal: Decimal; | ||
} | ||
``` | ||
The union members uses [`never`](https://www.typescriptlang.org/docs/handbook/basic-types.html#never) to prevent erroneously creating/accessing a union member with mutiple keys. | ||
## Wrapped Unions | ||
Avro Ts attempts to generate the types of the "auto" setting for wrapped unions. https://github.com/mtth/avsc/wiki/API#typeforschemaschema-opts This would mean that unions of records would be wrapped in an object with namespaced keys. | ||
The typescript interfaces are also namespaced appropriately. Avro namespaces like 'com.example.avro' are converted into `ComExampleAvro` namespaces in TS. | ||
> [examples/wrapped-union.ts](examples/wrapped-union.ts) | ||
```typescript | ||
import { toTypeScript } from '@ovotech/avro-ts'; | ||
import { Schema } from 'avsc'; | ||
const avro: Schema = { | ||
type: 'record', | ||
name: 'Event', | ||
namespace: 'com.example.avro', | ||
fields: [ | ||
{ name: 'id', type: 'int' }, | ||
{ | ||
name: 'event', | ||
type: [ | ||
{ | ||
type: 'record', | ||
name: 'ElectricityEvent', | ||
fields: [ | ||
{ name: 'accountId', type: 'string' }, | ||
{ name: 'MPAN', type: 'string' }, | ||
], | ||
}, | ||
{ | ||
type: 'record', | ||
name: 'GasEvent', | ||
fields: [ | ||
{ name: 'accountId', type: 'string' }, | ||
{ name: 'MPRN', type: 'string' }, | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}; | ||
const ts = toTypeScript(avro); | ||
console.log(ts); | ||
``` | ||
Which would result in this typescript: | ||
```typescript | ||
/* eslint-disable @typescript-eslint/no-namespace */ | ||
export type Event = ComExampleAvro.Event; | ||
export namespace ComExampleAvro { | ||
export const ElectricityEventName = 'com.example.avro.ElectricityEvent'; | ||
export interface ElectricityEvent { | ||
accountId: string; | ||
MPAN: string; | ||
} | ||
export const GasEventName = 'com.example.avro.GasEvent'; | ||
export interface GasEvent { | ||
accountId: string; | ||
MPRN: string; | ||
} | ||
export const EventName = 'com.example.avro.Event'; | ||
export interface Event { | ||
id: number; | ||
event: | ||
| { | ||
'com.example.avro.ElectricityEvent': ComExampleAvro.ElectricityEvent; | ||
'com.example.avro.GasEvent'?: never; | ||
} | ||
| { | ||
'com.example.avro.ElectricityEvent'?: never; | ||
'com.example.avro.GasEvent': ComExampleAvro.GasEvent; | ||
}; | ||
} | ||
} | ||
``` | ||
Notice that not only the interfaces themselves are exported, but their fully qualified names as well. This should help to improve readability. | ||
We also breakout the root type from its namespace for ease of use. | ||
```typescript | ||
import { ComExampleAvro as NS, Event } from '...'; | ||
const elecEvent: Event = { | ||
id: 10, | ||
event: { [NS.ElectricityEventName]: { MPAN: '111', accountId: '123' } }, | ||
}; | ||
const gasEvent: Event = { | ||
id: 10, | ||
event: { [NS.GasEventName]: { MPRN: '222', accountId: '123' } }, | ||
}; | ||
``` | ||
## Running the tests | ||
@@ -68,0 +233,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29
258
21925
2
10
298
1
+ Added@ovotech/ts-compose@^0.10.0
+ Added@ovotech/ts-compose@0.10.0(transitive)
Updatedtypescript@^3.7.4