@omnysecurity/cognite-codegen
Version:
Core TypeScript code generation library for Cognite Data Fusion data models
180 lines (179 loc) • 9.39 kB
JavaScript
import {} from '@cognite/sdk';
import ts from 'typescript';
import {} from './types.js';
function resolveTypeNode(propSpec) {
if ('list' in propSpec.type) {
if (propSpec.type.list) {
const typeNode = resolveTypeNode({
...propSpec,
type: { ...propSpec.type, list: false },
});
return ts.factory.createArrayTypeNode(typeNode);
}
}
switch (propSpec.type.type) {
case 'boolean':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
case 'float32':
case 'float64':
case 'int32':
case 'int64':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
case 'timestamp':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case 'date':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case 'text':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case 'json':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
case 'direct':
return ts.factory.createTypeReferenceNode(`DirectReference<${propSpec.type.source?.externalId ?? 'unknown'}>`);
case 'enum':
if (propSpec.type.values) {
const enumKeys = Object.keys(propSpec.type.values);
// Create a union type from the enum keys
const unionTypeNodes = enumKeys.map((key) => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(key)));
return ts.factory.createUnionTypeNode(unionTypeNodes);
}
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
case 'file':
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case 'sequence':
case 'timeseries':
default:
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
}
}
function isPropertyDefinition(prop) {
return 'container' in prop;
}
function createType(name, props, generics = []) {
return ts.factory.createTypeAliasDeclaration(ts.factory.createModifiersFromModifierFlags(ts.ModifierFlags.Export), ts.factory.createIdentifier(name), generics.map((T) => ts.factory.createTypeParameterDeclaration(undefined, T, undefined, undefined)), props);
}
function createProperty(name, type) {
return ts.factory.createPropertySignature(undefined, name, undefined, type);
}
function generateTypesForBuiltInViews() {
const stringType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
const numberType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
const directReference = createType('DirectReference', ts.factory.createIntersectionTypeNode([
ts.factory.createTypeReferenceNode('Omit<T, keyof T>', undefined),
ts.factory.createTypeLiteralNode([
createProperty('space', stringType),
createProperty('externalId', stringType),
]),
]), ['T extends __Schema[keyof __Schema] | unknown = unknown']);
const directReferenceType = ts.factory.createTypeReferenceNode('DirectReference', undefined);
const node = createType('Node', ts.factory.createTypeLiteralNode([
createProperty('space', stringType),
createProperty('externalId', stringType),
createProperty('createdTime', numberType),
createProperty('lastUpdatedTime', numberType),
]));
const edge = createType('Edge', ts.factory.createTypeLiteralNode([
createProperty('space', stringType),
createProperty('externalId', stringType),
createProperty('type', directReferenceType),
createProperty('startNode', directReferenceType),
createProperty('endNode', directReferenceType),
]));
// TODO: Add build-in types File, Sequence and Timeseries
return [node, edge, directReference];
}
export function nullishFilter(element) {
return element !== undefined && element !== null;
}
function generateTypesForViews(views) {
return views.flatMap(generateTypeForView);
}
// Function to generate TypeScript AST from the spec
function generateTypeForView(spec) {
const members = Object.entries(spec.properties)
.map(([propName, propSpec]) => {
if (isPropertyDefinition(propSpec) &&
propSpec.container.space == spec.space &&
propSpec.container.externalId == spec.externalId) {
return ts.factory.createPropertySignature(undefined, propName, propSpec['nullable']
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
: undefined, resolveTypeNode(propSpec));
}
else {
// TODO: Direct relation-types are not exported in the SDK :shrug:
}
return undefined;
})
.filter(nullishFilter);
const typeLiteral = ts.factory.createTypeLiteralNode(members);
const typeExtends = (spec.implements ?? []).map((view) => ts.factory.createTypeReferenceNode(view.externalId, undefined));
const typeNode = ts.factory.createIntersectionTypeNode([
typeLiteral,
...typeExtends,
]);
return [
ts.factory.createJSDocComment(spec.description),
ts.factory.createTypeAliasDeclaration(ts.factory.createModifiersFromModifierFlags(ts.ModifierFlags.Export), ts.factory.createIdentifier(spec.externalId), undefined, typeNode // ts.factory.createTypeLiteralNode(members.filter(Boolean))
),
// generate a string literal const value `as const` for the type
];
}
function generateTypeForSchema(views) {
return createType('__Schema', ts.factory.createTypeLiteralNode(views.map((view) => createProperty(view.externalId, ts.factory.createTypeReferenceNode(view.externalId)))));
}
export function generateTypescriptFile(model, views) {
// Generate TypeScript AST for each spec
const sourceFile = ts.createSourceFile('temp.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
const sourceNodes = ts.factory.createNodeArray([
generateTypeForSchema(views),
...generateTypesForBuiltInViews(),
...generateTypesForViews(views),
createViewPropertyMap(views),
createDataModelConstant(model),
]);
// Generate TypeScript code from the AST
const printer = ts.createPrinter();
const tsCode = printer.printList(ts.ListFormat.MultiLine, sourceNodes, sourceFile);
return tsCode;
}
function createViewPropertyMap(views) {
const viewNameToPropertyNames = views.map((view) => {
const propertyNameLiterals = ts.factory.createArrayLiteralExpression(Object.entries(view.properties).map(([propName, _propSpec]) => ts.factory.createStringLiteral(propName)));
return ts.factory.createPropertyAssignment(view.externalId, propertyNameLiterals);
});
const objectLiteral = ts.factory.createObjectLiteralExpression(viewNameToPropertyNames, true);
const declaration = ts.factory.createVariableDeclaration('__VIEWS', undefined, undefined, ts.factory.createAsExpression(objectLiteral, ts.factory.createTypeReferenceNode('const')));
const declarationList = ts.factory.createVariableDeclarationList([declaration], ts.NodeFlags.Const);
const statement = ts.factory.createVariableStatement(ts.factory.createModifiersFromModifierFlags(ts.ModifierFlags.Export), declarationList);
return statement;
}
function createDataModelConstant(model) {
const properties = ts.factory.createObjectLiteralExpression([
ts.factory.createPropertyAssignment('space', ts.factory.createStringLiteral(model.space)),
ts.factory.createPropertyAssignment('externalId', ts.factory.createStringLiteral(model.externalId)),
ts.factory.createPropertyAssignment('version', ts.factory.createStringLiteral(model.version)),
], true);
const declaration = ts.factory.createVariableDeclaration('__DATA_MODEL', undefined, undefined, ts.factory.createAsExpression(properties, ts.factory.createTypeReferenceNode('const')));
const declarationList = ts.factory.createVariableDeclarationList([declaration], ts.NodeFlags.Const);
const statement = ts.factory.createVariableStatement(ts.factory.createModifiersFromModifierFlags(ts.ModifierFlags.Export), declarationList);
return statement;
}
export const generate = (options) => {
const typescriptFileContents = generateTypescriptFile(options.dataModel, [...options.views].sort((a, b) => a.externalId.localeCompare(b.externalId)));
const disclaimer = `/*
* This file was generated by @omnysec/cognite-codegen.
* It is based on the data model '${options.dataModel.externalId}' version '${options.dataModel.version}.'
*/`;
const viewDefinitions = `
const _VIEW_DEFINITIONS = ${JSON.stringify([...options.views].sort((a, b) => a.externalId.localeCompare(b.externalId)), null, 2)} as const;
type _Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type _Mutable<T> = _Expand<{ -readonly [K in keyof T]: _Mutable<T[K]> }>;
export const VIEW_DEFINITIONS = _VIEW_DEFINITIONS as _Mutable<
typeof _VIEW_DEFINITIONS
>;
`;
return {
fileName: `${options.dataModel.externalId}@${options.dataModel.version}.ts`,
fileContent: [disclaimer, typescriptFileContents, viewDefinitions].join('\n'),
};
};
//# sourceMappingURL=index.js.map