UNPKG

@baqhub/cli

Version:

The official command line interface for the BAQ federated app platform.

177 lines (176 loc) 7.67 kB
import { isDefined, unreachable } from "@baqhub/sdk"; import camelCase from "lodash/camelCase.js"; import identity from "lodash/identity.js"; import { pascalCase } from "../helpers/case.js"; export function toRefNamespaceName(refName) { return `Ref${pascalCase(refName)}`; } export function toPropNamespaceName(propName) { return `Prop${pascalCase(propName)}`; } export function toSchemaNamespaceName(index) { return `Schema${index}`; } function enumOrDefault(enumValues, defaultType) { if (enumValues && enumValues.length > 0) { return [[], enumValues.map(v => JSON.stringify(v)).join(" | ")]; } return [[], defaultType]; } function valuesOrDefault(values, defaultType) { if (values && values.length > 0) { return [[], values.join(" | ")]; } return [[], defaultType]; } var SchemaResultMode; (function (SchemaResultMode) { SchemaResultMode["SIMPLE"] = "SIMPLE"; SchemaResultMode["COMPLEX"] = "COMPLEX"; })(SchemaResultMode || (SchemaResultMode = {})); export function schemaToTs(schema, name = "Type") { function combineSubSchemas(subSchemas, operator, wrapper = identity) { const subTypes = subSchemas.map(s => schemaToTsInternal(s)); const subTypeNamespaces = subTypes .map(([mode, subType], index) => { if (mode === SchemaResultMode.SIMPLE) { return undefined; } const namespace = toSchemaNamespaceName(index); return `export namespace ${namespace} { ${subType} }`; }) .filter(isDefined); const returnType = subTypes .map(([mode, subType], index) => mode === SchemaResultMode.SIMPLE ? subType : `${toSchemaNamespaceName(index)}.Type`) .join(` ${operator} `); return [subTypeNamespaces, `(${wrapper(returnType)})`]; } function schemaToTsInternal(schema, requestedMode, name = "Type") { const definitions = Object.entries(schema.definitions || {}).map(([key, subSchema]) => { const [, subType] = schemaToTsInternal(subSchema, SchemaResultMode.COMPLEX); return `export namespace ${toRefNamespaceName(key)} { ${subType} }`; }); const [subTypes, returnType] = (() => { switch (schema.type) { case "object": { const subTypes = Object.entries(schema.properties) .map(([key, subSchema]) => { if (subSchema.removed) { return undefined; } return [key, subSchema, schemaToTsInternal(subSchema)]; }) .filter(isDefined); const subSchemas = subTypes .map(([key, , [mode, subType]]) => { if (mode === SchemaResultMode.SIMPLE) { return undefined; } const namespace = toPropNamespaceName(key); return `export namespace ${namespace} { ${subType} }`; }) .filter(isDefined); const returnType = subTypes .map(([key, subSchema, [mode, subType]]) => { if (subSchema.removed) { return undefined; } const optionalSign = subSchema.optional ? "?" : ""; const propType = mode === SchemaResultMode.SIMPLE ? subType : `${toPropNamespaceName(key)}.Type`; const description = subSchema.description?.trim(); if (!description) { return `${camelCase(key)}${optionalSign}: ${propType};`; } return ` /** ${description} */ ${camelCase(key)}${optionalSign}: ${propType}; `; }) .filter(isDefined) .join(""); return [subSchemas, `{${returnType}}`]; } case "array": { const [mode, itemsType] = schemaToTsInternal(schema.items); if (mode === SchemaResultMode.SIMPLE) { return [[], `ReadonlyArray<${itemsType}>`]; } return [ [`export namespace Items { ${itemsType} }`], "ReadonlyArray<Items.Type>", ]; } case "ref": return [[], `${toRefNamespaceName(schema.ref)}.Type`]; case "ref_name": return [[], "string"]; case "self": case "schema": return [[], "unknown"]; case "never": return [[], "never"]; case "union": { const exclusiveWrapper = (t) => `ExclusiveUnion<${t}>`; const wrapper = schema.strict ? exclusiveWrapper : undefined; return combineSubSchemas(schema.schemas, "|", wrapper); } case "intersection": return combineSubSchemas(schema.schemas, "&"); case "map": { const [mode, valuesType] = schemaToTsInternal(schema.values); if (mode === SchemaResultMode.SIMPLE) { return [[], `{[K: string]: ${valuesType}}`]; } return [ [`export namespace Values { ${valuesType} }`], "{[K: string]: Values.Type}", ]; } case "boolean": return enumOrDefault(schema.enum, "boolean"); case "string": return enumOrDefault(schema.enum, "string"); case "int": case "number": return enumOrDefault(schema.enum, "number"); case "tag_link": { const tagValues = schema.enum?.map(v => `TagLink<"${v}">`); return valuesOrDefault(tagValues, "AnyTagLink"); } case "blob_link": { const types = schema.contentTypes?.map(v => `BlobLink<"${v}">`); return valuesOrDefault(types, "AnyBlobLink"); } case "entity_link": return [[], "EntityLink"]; case "record_link": { const recordTypes = schema.recordTypes?.map(v => `RecordLinkOf<"${v.entity}", "${v.recordId}">`); return valuesOrDefault(recordTypes, "AnyRecordLink"); } case "version_link": throw new Error("Not supported."); default: unreachable(schema); } })(); if (definitions.length === 0 && subTypes.length === 0 && requestedMode !== SchemaResultMode.COMPLEX) { return [SchemaResultMode.SIMPLE, returnType]; } return [ SchemaResultMode.COMPLEX, ` ${[...definitions, ""].join("\n\n")}\ ${[...subTypes, ""].join("\n\n")}\ export type ${name} = ${returnType}; `, ]; } const [, result] = schemaToTsInternal(schema, SchemaResultMode.COMPLEX, name); return result; }