Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ovotech/avro-ts

Package Overview
Dependencies
Maintainers
89
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ovotech/avro-ts - npm Package Compare versions

Comparing version 1.0.1 to 1.1.0

34

dist/index.d.ts

@@ -1,2 +0,29 @@

import { RecordType } from './types';
import { Schema, schema } from 'avsc';
import * as ts from 'typescript';
export interface Registry {
[key: string]: ts.InterfaceDeclaration;
}
export interface Context {
registry: Registry;
root: boolean;
namespace?: string;
namespaces: {
[key: string]: ts.TypeReferenceNode;
};
logicalTypes: {
[key: string]: ts.TypeReferenceNode;
};
}
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 withEntry: (context: Context, entry: ts.InterfaceDeclaration) => Context;
export declare const withNamespace: (context: Context, record: schema.RecordType) => Context;
export interface State {

@@ -11,2 +38,5 @@ output: string[];

}
export declare function avroTs(recordType: RecordType, logicalTypes?: State['logicalTypes']): string;
export declare const printAstNode: (node: Result<ts.TypeNode>) => string;
export declare function avroTs(recordType: schema.RecordType, logicalTypes?: {
[key: string]: string;
}): string;

205

dist/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function avroTs(recordType, logicalTypes = {}) {
const state = { output: [], repository: {}, logicalTypes };
convertRecord(recordType, state);
return state.output.join('\n');
}
exports.avroTs = avroTs;
function convertRecord(recordType, state, namespace) {
if (recordType.namespace) {
state.repository[fullyQualifiedName(recordType)] = recordType.name;
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.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 fields = exports.mapContext({ ...namespaceContext, root: false }, type.fields, (fieldContext, fieldType) => {
const field = convertType(fieldContext, 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.items);
if (context.root) {
return exports.result(fields.context, interfaceType);
}
let buffer = `export interface ${recordType.name} {\n`;
for (const field of recordType.fields) {
buffer += convertFieldDec(field, state, recordType.namespace) + '\n';
else {
return exports.result(exports.withEntry(fields.context, interfaceType), ts.createTypeReferenceNode(type.name, undefined));
}
buffer += '}\n';
state.output.push(buffer);
return recordType.name;
}
function convertFieldDec(field, state, namespace) {
const optional = isOptional(field.type) ? '?' : '';
const doc = field.doc ? `/**\n * ${field.doc}\n */\n` : '';
return indent(`${doc}${field.name}${optional}: ${convertType(field.type, state, namespace)};`);
}
function convertType(type, state, namespace) {
};
const convertType = (context, type) => {
if (typeof type === 'string') {
return convertPredefinedType(type, state) || convertPrimitive(type) || type;
return convertPredefinedType(context, type);
}
else if (type instanceof Array) {
return convertArrayType(type, state, namespace);
else if (Array.isArray(type)) {
return convertArrayType(context, type);
}
else if (isLogicalType(type)) {
return convertLogicalType(type, state);
return convertLogicalType(context, type);
}
else if (isRecordType(type)) {
return convertRecord(type, state, namespace);
return convertRecord(context, type);
}
else if (isArrayType(type)) {
return convertType(type.items, state, namespace) + '[]';
const itemType = convertType(context, type.items);
return exports.result(itemType.context, ts.createArrayTypeNode(itemType.type));
}
else if (isMapType(type)) {
return convertMapType(type, state, namespace);
return convertMapType(context, type);
}
else if (isEnumType(type)) {
return convertEnum(type);
return convertEnum(context, type);
}

@@ -51,4 +75,4 @@ else {

}
}
function convertPrimitive(avroType) {
};
const convertPrimitive = (context, avroType) => {
switch (avroType) {

@@ -59,58 +83,49 @@ case 'long':

case 'float':
return 'number';
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword));
case 'bytes':
return 'Buffer';
return exports.result(context, ts.createTypeReferenceNode('Buffer', undefined));
case 'null':
return 'null';
return exports.result(context, ts.createNull());
case 'boolean':
return 'boolean';
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword));
case 'string':
return 'string';
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));
default:
return 'any';
return exports.result(context, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
}
}
function convertEnum(enumType) {
return enumType.symbols.map(symbol => JSON.stringify(symbol)).join(' | ');
}
function convertLogicalType(type, state) {
return state.logicalTypes[type.logicalType] || convertPrimitive(type.type);
}
function convertPredefinedType(type, state) {
return state.repository[type];
}
function convertArrayType(type, state, namespace) {
return type
.map(t => {
if (typeof t === 'object' && !(t instanceof Array) && isRecordType(t)) {
return `{ '${fullyQualifiedName(t, namespace)}' : ${convertType(t, state, namespace)} }`;
};
const convertEnum = (context, enumType) => exports.result(context, ts.createUnionTypeNode(enumType.symbols.map(symbol => ts.createLiteralTypeNode(ts.createLiteral(symbol)))));
const convertLogicalType = (context, type) => context.logicalTypes[type.logicalType]
? exports.result(context, context.logicalTypes[type.logicalType])
: convertPrimitive(context, type.type);
const convertPredefinedType = (context, type) => context.namespaces[type] ? exports.result(context, context.namespaces[type]) : convertPrimitive(context, type);
const convertArrayType = (context, type) => {
const map = exports.mapContext(context, type, (itemContext, item) => {
if (typeof item === 'object' && !Array.isArray(item) && isRecordType(item)) {
const itemType = convertType(itemContext, item);
return exports.result(itemType.context, ts.createTypeLiteralNode([
ts.createPropertySignature(undefined, ts.createStringLiteral(fullyQualifiedName(context, item)), undefined, itemType.type, undefined),
]));
}
else {
return convertType(t, state);
return convertType(itemContext, item);
}
})
.join(' | ');
}
function convertMapType(type, state, namespace) {
return `{ [index:string]:${convertType(type.values, state, namespace)} }`;
}
function isRecordType(type) {
return type.type === 'record';
}
function isArrayType(type) {
return type.type === 'array';
}
function isMapType(type) {
return type.type === 'map';
}
function isEnumType(type) {
return type.type === 'enum';
}
function isLogicalType(type) {
return 'logicalType' in type;
}
function isUnion(type) {
return type instanceof Array;
}
function isOptional(type) {
});
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 isOptional = (type) => {
if (isUnion(type)) {

@@ -123,13 +138,27 @@ const t1 = type[0];

return false;
}
function fullyQualifiedName(type, namespace) {
const currentNamespace = type.namespace || namespace;
};
const fullyQualifiedName = (context, type) => {
const currentNamespace = type.namespace || context.namespace;
return currentNamespace ? `${currentNamespace}.${type.name}` : type.name;
};
exports.printAstNode = (node) => {
const resultFile = ts.createSourceFile('someFileName.ts', '', ts.ScriptTarget.Latest);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const entries = Object.values(node.context.registry);
const fullSourceFile = ts.updateSourceFileNode(resultFile, entries);
return [
printer.printNode(ts.EmitHint.Unspecified, node.type, fullSourceFile),
...entries.map(entry => printer.printNode(ts.EmitHint.Unspecified, entry, fullSourceFile)),
].join('\n\n');
};
function avroTs(recordType, logicalTypes = {}) {
const context = {
root: true,
registry: {},
namespaces: {},
logicalTypes: Object.entries(logicalTypes).reduce((all, [name, type]) => ({ ...all, [name]: ts.createTypeReferenceNode(type, undefined) }), {}),
};
return exports.printAstNode(convertRecord(context, recordType));
}
function indent(text, prefix = ' ') {
return text
.split('\n')
.map(row => prefix + row)
.join('\n');
}
exports.avroTs = avroTs;
//# sourceMappingURL=index.js.map
{
"name": "@ovotech/avro-ts",
"description": "Convert avro schemas into typescript interfaces",
"version": "1.0.1",
"version": "1.1.0",
"main": "dist/index.js",

@@ -28,4 +28,3 @@ "source": "src/index.ts",

"tslint": "^5.14.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.3.4000"
"tslint-config-prettier": "^1.18.0"
},

@@ -35,3 +34,6 @@ "jest": {

},
"gitHead": "f68210eef2100a3c8e844c843e8745aa450cb62d"
"dependencies": {
"typescript": "^3.4.3"
},
"gitHead": "33fbdb8057520cf1d3f229059a0231089febbaee"
}

@@ -5,3 +5,3 @@ # Avro TS

It consists of a very quick sequential, functional parser. No dependencies.
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.

@@ -17,5 +17,6 @@ ### Using

```typescript
import { schema } from 'avsc';
import { avroTs } from '@ovotech/avro-ts';
const avro: RecordType = JSON.parse(String(readFileSync(join(__dirname, 'avro', file))));
const avro: schema.RecordType = JSON.parse(String(readFileSync(join(__dirname, 'avro', file))));
const ts = avroTs(avro, { 'timestamp-millis': 'string', date: 'string' });

@@ -22,0 +23,0 @@

@@ -1,3 +0,58 @@

import { ArrayType, BaseType, EnumType, Field, LogicalType, MapType, NamedType, RecordType, Type } from './types';
import { Schema, schema } from 'avsc';
import * as ts from 'typescript';
export interface Registry {
[key: string]: ts.InterfaceDeclaration;
}
export interface Context {
registry: Registry;
root: boolean;
namespace?: string;
namespaces: { [key: string]: ts.TypeReferenceNode };
logicalTypes: { [key: string]: ts.TypeReferenceNode };
}
export interface Result<TsType = ts.TypeNode> {
type: TsType;
context: Context;
}
export type Convert<TType = Schema> = (context: Context, type: TType) => Result<any>;
export const result = <TsType = ts.TypeNode>(context: Context, type: TsType): Result<TsType> => ({
context,
type,
});
export const mapContext = <T = any, TsType = ts.TypeNode>(
context: Context,
items: T[],
callbackfn: (context: Context, item: T) => Result<TsType>,
) =>
items.reduce<{ items: TsType[]; context: Context }>(
(all, item) => {
const current = callbackfn(all.context, item);
return {
items: [...all.items, current.type],
context: current.context,
};
},
{ items: [], context },
);
export const withEntry = (context: Context, entry: ts.InterfaceDeclaration): Context => ({
...context,
registry: { ...context.registry, [entry.name.text]: entry },
});
export const withNamespace = (context: Context, record: schema.RecordType): Context => ({
...context,
namespace: record.namespace,
namespaces: {
...context.namespaces,
[fullyQualifiedName(context, record)]: ts.createTypeReferenceNode(record.name, undefined),
},
});
export interface State {

@@ -9,51 +64,66 @@ output: string[];

export function avroTs(recordType: RecordType, logicalTypes: State['logicalTypes'] = {}): string {
const state: State = { output: [], repository: {}, logicalTypes };
const docToJSDoc = (doc: string) =>
`*\n${doc
.split('\n')
.map(line => ` * ${line}`)
.join('\n')}\n `;
convertRecord(recordType, state);
return state.output.join('\n');
}
const convertRecord: Convert<schema.RecordType> = (context, type) => {
const namespaceContext = type.namespace ? withNamespace(context, type) : context;
function convertRecord(recordType: RecordType, state: State, namespace?: string): string {
if (recordType.namespace) {
state.repository[fullyQualifiedName(recordType)] = recordType.name;
}
const fields = mapContext({ ...namespaceContext, root: false }, type.fields, (fieldContext, fieldType) => {
const field = convertType(fieldContext, fieldType.type);
const prop = ts.createPropertySignature(
undefined,
fieldType.name,
isOptional(fieldType.type) ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined,
field.type,
undefined,
);
let buffer = `export interface ${recordType.name} {\n`;
for (const field of recordType.fields) {
buffer += convertFieldDec(field, state, recordType.namespace) + '\n';
}
buffer += '}\n';
state.output.push(buffer);
return recordType.name;
}
const propWithDoc = fieldType.doc
? ts.addSyntheticLeadingComment(prop, ts.SyntaxKind.MultiLineCommentTrivia, docToJSDoc(fieldType.doc), true)
: prop;
function convertFieldDec(field: Field, state: State, namespace?: string): string {
const optional = isOptional(field.type) ? '?' : '';
const doc = field.doc ? `/**\n * ${field.doc}\n */\n` : '';
return result(field.context, propWithDoc);
});
return indent(`${doc}${field.name}${optional}: ${convertType(field.type, state, namespace)};`);
}
const interfaceType = ts.createInterfaceDeclaration(
undefined,
[ts.createToken(ts.SyntaxKind.ExportKeyword)],
type.name,
undefined,
undefined,
fields.items,
);
function convertType(type: Type, state: State, namespace?: string): string {
if (context.root) {
return result(fields.context, interfaceType);
} else {
return result(withEntry(fields.context, interfaceType), ts.createTypeReferenceNode(type.name, undefined));
}
};
const convertType: Convert = (context, type) => {
if (typeof type === 'string') {
return convertPredefinedType(type, state) || convertPrimitive(type) || type;
} else if (type instanceof Array) {
return convertArrayType(type, state, namespace);
return convertPredefinedType(context, type);
} else if (Array.isArray(type)) {
return convertArrayType(context, type);
} else if (isLogicalType(type)) {
return convertLogicalType(type, state);
return convertLogicalType(context, type);
} else if (isRecordType(type)) {
return convertRecord(type, state, namespace);
return convertRecord(context, type);
} else if (isArrayType(type)) {
return convertType(type.items, state, namespace) + '[]';
const itemType = convertType(context, type.items);
return result(itemType.context, ts.createArrayTypeNode(itemType.type));
} else if (isMapType(type)) {
return convertMapType(type, state, namespace);
return convertMapType(context, type);
} else if (isEnumType(type)) {
return convertEnum(type);
return convertEnum(context, type);
} else {
throw new Error(`Cannot work out type ${type}`);
}
}
};
function convertPrimitive(avroType: string): string {
const convertPrimitive: Convert = (context, avroType) => {
switch (avroType) {

@@ -64,69 +134,96 @@ case 'long':

case 'float':
return 'number';
return result(context, ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword));
case 'bytes':
return 'Buffer';
return result(context, ts.createTypeReferenceNode('Buffer', undefined));
case 'null':
return 'null';
return result(context, ts.createNull());
case 'boolean':
return 'boolean';
return result(context, ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword));
case 'string':
return 'string';
return result(context, ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));
default:
return 'any';
return result(context, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
}
}
};
function convertEnum(enumType: EnumType): string {
return enumType.symbols.map(symbol => JSON.stringify(symbol)).join(' | ');
}
const convertEnum: Convert<schema.EnumType> = (context, enumType) =>
result(
context,
ts.createUnionTypeNode(enumType.symbols.map(symbol => ts.createLiteralTypeNode(ts.createLiteral(symbol)))),
);
function convertLogicalType(type: LogicalType, state: State): string {
return state.logicalTypes[type.logicalType] || convertPrimitive(type.type);
}
const convertLogicalType: Convert<schema.LogicalType> = (context, type) =>
context.logicalTypes[type.logicalType]
? result(context, context.logicalTypes[type.logicalType])
: convertPrimitive(context, type.type);
function convertPredefinedType(type: string, state: State): string {
return state.repository[type];
}
const convertPredefinedType: Convert<string> = (context, type) =>
context.namespaces[type] ? result(context, context.namespaces[type]) : convertPrimitive(context, type);
function convertArrayType(type: Type[], state: State, namespace?: string): string {
return type
.map(t => {
if (typeof t === 'object' && !(t instanceof Array) && isRecordType(t)) {
return `{ '${fullyQualifiedName(t, namespace)}' : ${convertType(t, state, namespace)} }`;
} else {
return convertType(t, state);
}
})
.join(' | ');
}
const convertArrayType: Convert<any[]> = (context, type) => {
const map = mapContext(context, type, (itemContext, item) => {
if (typeof item === 'object' && !Array.isArray(item) && isRecordType(item)) {
const itemType = convertType(itemContext, item);
return result(
itemType.context,
ts.createTypeLiteralNode([
ts.createPropertySignature(
undefined,
ts.createStringLiteral(fullyQualifiedName(context, item)),
undefined,
itemType.type,
undefined,
),
]),
);
} else {
return convertType(itemContext, item);
}
});
function convertMapType(type: MapType, state: State, namespace?: string): string {
return `{ [index:string]:${convertType(type.values, state, namespace)} }`;
}
return result(map.context, ts.createUnionTypeNode(map.items));
};
function isRecordType(type: BaseType): type is RecordType {
return type.type === 'record';
}
const convertMapType: Convert<schema.MapType> = (context, type) => {
const map = convertType(context, type.values);
return result(
map.context,
ts.createTypeLiteralNode([
ts.createIndexSignature(
undefined,
undefined,
[
ts.createParameter(
undefined,
undefined,
undefined,
'index',
undefined,
ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
undefined,
),
],
map.type,
),
]),
);
};
function isArrayType(type: BaseType): type is ArrayType {
return type.type === 'array';
}
const isRecordType = (type: Schema): type is schema.RecordType =>
typeof type === 'object' && 'type' in type && type.type === 'record';
function isMapType(type: BaseType): type is MapType {
return type.type === 'map';
}
const isArrayType = (type: Schema): type is schema.ArrayType =>
typeof type === 'object' && 'type' in type && type.type === 'array';
function isEnumType(type: BaseType): type is EnumType {
return type.type === 'enum';
}
const isMapType = (type: Schema): type is schema.MapType =>
typeof type === 'object' && 'type' in type && type.type === 'map';
function isLogicalType(type: BaseType): type is LogicalType {
return 'logicalType' in type;
}
const isEnumType = (type: Schema): type is schema.EnumType =>
typeof type === 'object' && 'type' in type && type.type === 'enum';
function isUnion(type: Type): type is NamedType[] {
return type instanceof Array;
}
const isLogicalType = (type: Schema): type is schema.LogicalType => typeof type === 'object' && 'logicalType' in type;
function isOptional(type: Type): boolean {
const isUnion = (type: Schema): type is schema.NamedType[] => typeof type === 'object' && Array.isArray(type);
const isOptional = (type: Schema): boolean => {
if (isUnion(type)) {

@@ -139,14 +236,33 @@ const t1 = type[0];

return false;
}
};
function fullyQualifiedName(type: RecordType, namespace?: string) {
const currentNamespace = type.namespace || namespace;
const fullyQualifiedName = (context: Context, type: schema.RecordType) => {
const currentNamespace = type.namespace || context.namespace;
return currentNamespace ? `${currentNamespace}.${type.name}` : type.name;
}
};
function indent(text: string, prefix = ' ') {
return text
.split('\n')
.map(row => prefix + row)
.join('\n');
export const printAstNode = (node: Result): string => {
const resultFile = ts.createSourceFile('someFileName.ts', '', ts.ScriptTarget.Latest);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const entries = Object.values(node.context.registry);
const fullSourceFile = ts.updateSourceFileNode(resultFile, entries);
return [
printer.printNode(ts.EmitHint.Unspecified, node.type, fullSourceFile),
...entries.map(entry => printer.printNode(ts.EmitHint.Unspecified, entry, fullSourceFile)),
].join('\n\n');
};
export function avroTs(recordType: schema.RecordType, logicalTypes: { [key: string]: string } = {}): string {
const context: Context = {
root: true,
registry: {},
namespaces: {},
logicalTypes: Object.entries(logicalTypes).reduce(
(all, [name, type]) => ({ ...all, [name]: ts.createTypeReferenceNode(type, undefined) }),
{},
),
};
return printAstNode(convertRecord(context, recordType));
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc