@storm-software/untyped
Version:
A package containing `untyped` utilities for building Storm Software libraries and applications
502 lines (491 loc) • 13.6 kB
JavaScript
import {
writeError,
writeTrace
} from "./chunk-4U6ARZBV.js";
import {
getOutputFile
} from "./chunk-NTN5YK5X.js";
// src/generators/dts.ts
import { writeFile } from "node:fs/promises";
// ../../node_modules/.pnpm/untyped@2.0.0/node_modules/untyped/dist/shared/untyped.Br_uXjZG.mjs
import { pascalCase } from "scule";
function getType(val) {
const type = typeof val;
if (type === "undefined" || val === null) {
return void 0;
}
if (Array.isArray(val)) {
return "array";
}
return type;
}
function isObject(val) {
return val !== null && !Array.isArray(val) && typeof val === "object";
}
function nonEmpty(arr) {
return arr.filter(Boolean);
}
function unique(arr) {
return [...new Set(arr)];
}
function joinPath(a, b = "", sep = ".") {
return a ? a + sep + b : b;
}
function setValue(obj, path, val) {
const keys = path.split(".");
const _key = keys.pop();
for (const key of keys) {
if (!obj || typeof obj !== "object") {
return;
}
if (!(key in obj)) {
obj[key] = {};
}
obj = obj[key];
}
if (_key) {
if (!obj || typeof obj !== "object") {
return;
}
obj[_key] = val;
}
}
function getValue(obj, path) {
for (const key of path.split(".")) {
if (!obj || typeof obj !== "object" || !(key in obj)) {
return;
}
obj = obj[key];
}
return obj;
}
function mergedTypes(...types) {
types = types.filter(Boolean);
if (types.length === 0) {
return {};
}
if (types.length === 1) {
return types[0];
}
const tsTypes = normalizeTypes(
types.flatMap((t) => t.tsType).filter(Boolean)
);
return {
type: normalizeTypes(
types.flatMap((t) => t.type).filter(Boolean)
),
tsType: Array.isArray(tsTypes) ? tsTypes.join(" | ") : tsTypes,
items: mergedTypes(
...types.flatMap((t) => t.items).filter(Boolean)
)
};
}
function normalizeTypes(val) {
const arr = unique(val.filter(Boolean));
if (arr.length === 0 || arr.includes("any")) {
return;
}
return arr.length > 1 ? arr : arr[0];
}
function cachedFn(fn) {
let val;
let resolved = false;
const cachedFn2 = () => {
if (!resolved) {
val = fn();
resolved = true;
}
return val;
};
return cachedFn2;
}
var jsTypes = /* @__PURE__ */ new Set([
"string",
"number",
"bigint",
"boolean",
"symbol",
"function",
"object",
"any",
"array"
]);
function isJSType(val) {
return jsTypes.has(val);
}
var FRIENDLY_TYPE_RE = /(typeof )?import\(["'](?<importName>[^"']+)["']\)(\[["']|\.)(?<firstType>[^\s"']+)(["']])?/g;
function getTypeDescriptor(type) {
if (!type) {
return {};
}
let markdownType = type;
for (const match of type.matchAll(FRIENDLY_TYPE_RE) || []) {
const { importName, firstType } = match.groups || {};
if (importName && firstType) {
markdownType = markdownType.replace(
match[0],
pascalCase(importName) + pascalCase(firstType)
);
}
}
return {
...isJSType(type) ? { type } : {},
tsType: type,
...markdownType === type ? {} : { markdownType }
};
}
// ../../node_modules/.pnpm/untyped@2.0.0/node_modules/untyped/dist/shared/untyped.BTwOq8Jl.mjs
async function resolveSchema(obj, defaults, options = {}) {
const schema = await _resolveSchema(obj, "", {
root: obj,
defaults,
resolveCache: {},
ignoreDefaults: !!options.ignoreDefaults
});
return schema;
}
async function _resolveSchema(input, id, ctx) {
if (id in ctx.resolveCache) {
return ctx.resolveCache[id];
}
const schemaId = "#" + id.replace(/\./g, "/");
if (!isObject(input)) {
const safeInput = Array.isArray(input) ? [...input] : input;
const schema2 = {
type: getType(input),
id: schemaId,
default: ctx.ignoreDefaults ? void 0 : safeInput
};
normalizeSchema(schema2, { ignoreDefaults: ctx.ignoreDefaults });
ctx.resolveCache[id] = schema2;
if (ctx.defaults && getValue(ctx.defaults, id) === void 0) {
setValue(ctx.defaults, id, schema2.default);
}
return schema2;
}
const node = { ...input };
const schema = ctx.resolveCache[id] = {
...node.$schema,
id: schemaId
};
for (const key in node) {
if (key === "$resolve" || key === "$schema" || key === "$default") {
continue;
}
schema.properties = schema.properties || {};
if (!schema.properties[key]) {
const child = schema.properties[key] = await _resolveSchema(
node[key],
joinPath(id, key),
ctx
);
if (Array.isArray(child.tags) && child.tags.includes("@required")) {
schema.required = schema.required || [];
if (!schema.required.includes(key)) {
schema.required.push(key);
}
}
}
}
if (!ctx.ignoreDefaults) {
if (ctx.defaults) {
schema.default = getValue(ctx.defaults, id);
}
if (schema.default === void 0 && "$default" in node) {
schema.default = node.$default;
}
if (typeof node.$resolve === "function") {
schema.default = await node.$resolve(schema.default, async (key) => {
return (await _resolveSchema(getValue(ctx.root, key), key, ctx)).default;
});
}
}
if (ctx.defaults) {
setValue(ctx.defaults, id, schema.default);
}
if (!schema.type) {
schema.type = getType(schema.default) || (schema.properties ? "object" : "any");
}
normalizeSchema(schema, { ignoreDefaults: ctx.ignoreDefaults });
if (ctx.defaults && getValue(ctx.defaults, id) === void 0) {
setValue(ctx.defaults, id, schema.default);
}
return schema;
}
function normalizeSchema(schema, options) {
if (schema.type === "array" && !("items" in schema)) {
schema.items = {
type: nonEmpty(unique(schema.default.map((i) => getType(i))))
};
if (schema.items.type) {
if (schema.items.type.length === 0) {
schema.items.type = "any";
} else if (schema.items.type.length === 1) {
schema.items.type = schema.items.type[0];
}
}
}
if (!options.ignoreDefaults && schema.default === void 0 && ("properties" in schema || schema.type === "object" || schema.type === "any")) {
const propsWithDefaults = Object.entries(schema.properties || {}).filter(([, prop]) => "default" in prop).map(([key, value]) => [key, value.default]);
schema.default = Object.fromEntries(propsWithDefaults);
}
}
// ../../node_modules/.pnpm/untyped@2.0.0/node_modules/untyped/dist/index.mjs
import { genObjectKey } from "knitwork";
import "scule";
var GenerateTypesDefaults = {
interfaceName: "Untyped",
addExport: true,
addDefaults: true,
allowExtraKeys: void 0,
partial: false,
indentation: 0
};
var TYPE_MAP = {
array: "any[]",
bigint: "bigint",
boolean: "boolean",
number: "number",
object: "",
// Will be precisely defined
any: "any",
string: "string",
symbol: "Symbol",
function: "Function"
};
var SCHEMA_KEYS = /* @__PURE__ */ new Set([
"items",
"default",
"resolve",
"properties",
"title",
"description",
"$schema",
"type",
"tsType",
"markdownType",
"tags",
"args",
"id",
"returns"
]);
var DECLARATION_RE = /typeof import\(["'](?<source>[^)]+)["']\)(\.(?<type>\w+)|\[["'](?<type1>\w+)["']])/g;
function extractTypeImports(declarations) {
const typeImports = {};
const aliases = /* @__PURE__ */ new Set();
const imports = [];
for (const match of declarations.matchAll(DECLARATION_RE)) {
const { source, type1, type = type1 } = match.groups || {};
typeImports[source] = typeImports[source] || /* @__PURE__ */ new Set();
typeImports[source].add(type);
}
for (const source in typeImports) {
const sourceImports = [];
for (const type of typeImports[source]) {
let count = 0;
let alias = type;
while (aliases.has(alias)) {
alias = `${type}${count++}`;
}
aliases.add(alias);
sourceImports.push(alias === type ? type : `${type} as ${alias}`);
declarations = declarations.replace(
new RegExp(
`typeof import\\(['"]${source}['"]\\)(\\.${type}|\\[['"]${type}['"]\\])`,
"g"
),
alias
);
}
imports.push(
`import type { ${sourceImports.join(", ")} } from '${source}'`
);
}
return [...imports, declarations].join("\n");
}
function generateTypes(schema, opts = {}) {
opts = { ...GenerateTypesDefaults, ...opts };
const baseIden = " ".repeat(opts.indentation || 0);
const interfaceCode = `interface ${opts.interfaceName} {
` + _genTypes(schema, baseIden + " ", opts).map((l) => l.trim().length > 0 ? l : "").join("\n") + `
${baseIden}}`;
if (!opts.addExport) {
return baseIden + interfaceCode;
}
return extractTypeImports(baseIden + `export ${interfaceCode}`);
}
function _genTypes(schema, spaces, opts) {
const buff = [];
if (!schema) {
return buff;
}
for (const key in schema.properties) {
const val = schema.properties[key];
buff.push(...generateJSDoc(val, opts));
if (val.tsType) {
buff.push(
`${genObjectKey(key)}${isRequired(schema, key, opts) ? "" : "?"}: ${val.tsType},
`
);
} else if (val.type === "object") {
buff.push(
`${genObjectKey(key)}${isRequired(schema, key, opts) ? "" : "?"}: {`,
..._genTypes(val, spaces, opts),
"},\n"
);
} else {
let type;
if (val.type === "array") {
type = `Array<${getTsType(val.items || [], opts)}>`;
} else if (val.type === "function") {
type = genFunctionType(val, opts);
} else {
type = getTsType(val, opts);
}
buff.push(
`${genObjectKey(key)}${isRequired(schema, key, opts) ? "" : "?"}: ${type},
`
);
}
}
if (buff.length > 0) {
const last = buff.pop() || "";
buff.push(last.slice(0, Math.max(0, last.length - 1)));
}
if (opts.allowExtraKeys === true || buff.length === 0 && opts.allowExtraKeys !== false) {
buff.push("[key: string]: any");
}
return buff.flatMap((l) => l.split("\n")).map((l) => spaces + l);
}
function getTsType(type, opts) {
if (Array.isArray(type)) {
return [normalizeTypes(type.map((t) => getTsType(t, opts)))].flat().join("|") || "any";
}
if (!type) {
return "any";
}
if (type.tsType) {
return type.tsType;
}
if (!type.type) {
return "any";
}
if (Array.isArray(type.type)) {
return type.type.map((t) => {
if (t === "object" && type.type.length > 1) {
return `{
` + _genTypes(type, " ", opts).join("\n") + `
}`;
}
return TYPE_MAP[t];
}).join("|");
}
if (type.type === "array") {
return `Array<${getTsType(type.items || [], opts)}>`;
}
if (type.type === "object") {
return `{
` + _genTypes(type, " ", opts).join("\n") + `
}`;
}
return TYPE_MAP[type.type] || type.type;
}
function genFunctionType(schema, opts) {
return `(${genFunctionArgs(schema.args, opts)}) => ${getTsType(
schema.returns || [],
opts
)}`;
}
function genFunctionArgs(args, opts) {
return args?.map((arg) => {
let argStr = arg.name;
if (arg.optional || arg.default) {
argStr += "?";
}
if (arg.type || arg.tsType) {
argStr += `: ${getTsType(arg, opts)}`;
}
return argStr;
}).join(", ") || "";
}
function generateJSDoc(schema, opts) {
opts.defaultDescription = opts.defaultDescription || opts.defaultDescrption;
let buff = [];
if (schema.title) {
buff.push(schema.title, "");
}
if (schema.description) {
buff.push(schema.description, "");
} else if (opts.defaultDescription && schema.type !== "object") {
buff.push(opts.defaultDescription, "");
}
if (opts.addDefaults && schema.type !== "object" && schema.type !== "any" && !(Array.isArray(schema.default) && schema.default.length === 0)) {
const stringified = JSON.stringify(schema.default);
if (stringified) {
buff.push(`@default ${stringified.replace(/\*\//g, String.raw`*\/`)}`);
}
}
for (const key in schema) {
if (!SCHEMA_KEYS.has(key)) {
buff.push("", `@${key} ${schema[key]}`);
}
}
if (Array.isArray(schema.tags)) {
for (const tag of schema.tags) {
if (tag !== "@untyped") {
buff.push("", tag);
}
}
}
buff = buff.flatMap((i) => i.split("\n"));
if (buff.length > 0) {
return buff.length === 1 ? ["/** " + buff[0] + " */"] : ["/**", ...buff.map((i) => ` * ${i}`), "*/"];
}
return [];
}
function isRequired(schema, key, opts) {
if (Array.isArray(schema.required) && schema.required.includes(key)) {
return true;
}
return !opts.partial;
}
// src/generators/dts.ts
function generateDeclaration(schema, generatedBy = "@storm-software/untyped") {
return `
// Generated by ${generatedBy}
// Do not edit this file directly
${generateTypes(schema, {
addExport: true,
partial: true,
interfaceName: `${schema.title?.replaceAll(" ", "") || "Type"}Schema`
})}
`;
}
function generateDeclarationFile(schema, file, generatedBy = "@storm-software/untyped", config) {
try {
const declarations = getOutputFile(file, "d.ts");
writeTrace(`Writing type declaration file ${declarations}`, config);
return writeFile(declarations, generateDeclaration(schema, generatedBy));
} catch (error) {
writeError(
`Error writing declaration file for ${file.name}
Error:
${error?.message ? error.message : JSON.stringify(error)}${error?.stack ? `
Stack Trace: ${error.stack}` : ""}
Parsed schema:
${JSON.stringify(schema)}
`,
config
);
throw error;
}
}
export {
mergedTypes,
normalizeTypes,
cachedFn,
getTypeDescriptor,
resolveSchema,
generateDeclaration,
generateDeclarationFile
};