drizzle-zero
Version:
Generate Zero schemas from Drizzle ORM schemas
1,004 lines (989 loc) • 37.6 kB
JavaScript
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/cli/index.ts
import { Command } from "commander";
import * as fs3 from "fs/promises";
import * as path3 from "path";
import { pathToFileURL as pathToFileURL3 } from "url";
import { Project as Project4 } from "ts-morph";
// src/cli/config.ts
import * as fs from "fs/promises";
import * as path from "path";
import * as url from "url";
import "ts-morph";
import { tsImport } from "tsx/esm/api";
var defaultConfigFilePath = "drizzle-zero.config.ts";
var getDefaultConfigFilePath = async () => {
const fullConfigPath = path.resolve(process.cwd(), defaultConfigFilePath);
try {
await fs.access(fullConfigPath);
} catch (error) {
return null;
}
return "drizzle-zero.config.ts";
};
var getConfigFromFile = async ({
configFilePath,
tsProject
}) => {
const fullConfigPath = path.resolve(process.cwd(), configFilePath);
try {
await fs.access(fullConfigPath);
} catch (error) {
throw new Error(
`\u274C drizzle-zero: Failed to find config file at ${fullConfigPath}`
);
}
const zeroConfigFilePathUrl = url.pathToFileURL(fullConfigPath).href;
const zeroConfigImport = await tsImport(
zeroConfigFilePathUrl,
import.meta.url
);
const exportName = (zeroConfigImport == null ? void 0 : zeroConfigImport.default) ? "default" : "schema";
const zeroSchema = (zeroConfigImport == null ? void 0 : zeroConfigImport.default) ?? (zeroConfigImport == null ? void 0 : zeroConfigImport.schema);
const typeDeclarations = await getZeroSchemaDefsFromConfig({
tsProject,
configPath: fullConfigPath,
exportName
});
return {
type: "config",
zeroSchema,
exportName,
zeroSchemaTypeDeclarations: typeDeclarations
};
};
async function getZeroSchemaDefsFromConfig({
tsProject,
configPath,
exportName
}) {
const fileName = configPath.slice(configPath.lastIndexOf("/") + 1);
const sourceFile = tsProject.getSourceFile(fileName);
if (!sourceFile) {
throw new Error(
`\u274C drizzle-zero: Failed to find type definitions for ${fileName}`
);
}
const exportDeclarations = sourceFile.getExportedDeclarations();
for (const [name, declarations] of exportDeclarations.entries()) {
for (const declaration of declarations) {
if (exportName === name) {
return [name, declaration];
}
}
}
throw new Error(
`\u274C drizzle-zero: No config type found in the config file - did you export \`default\` or \`schema\`? Found: ${sourceFile.getVariableDeclarations().map((v) => v.getName()).join(", ")}`
);
}
// src/cli/drizzle-kit.ts
import * as fs2 from "fs/promises";
import * as path2 from "path";
import * as url2 from "url";
import "ts-morph";
import { tsImport as tsImport2 } from "tsx/esm/api";
// src/relations.ts
import { createSchema } from "@rocicorp/zero";
import {
createTableRelationsHelpers,
getTableName as getTableName3,
is as is2,
One,
Relations,
Table as Table2
} from "drizzle-orm";
// src/tables.ts
import {
boolean as zeroBoolean,
enumeration as zeroEnumeration,
json as zeroJson,
number as zeroNumber,
string as zeroString,
table as zeroTable
} from "@rocicorp/zero";
import { getTableColumns, getTableName as getTableName2 } from "drizzle-orm";
import { toCamelCase, toSnakeCase } from "drizzle-orm/casing";
// src/db.ts
import { getTableName, is } from "drizzle-orm";
import { getTableConfig, PgTable } from "drizzle-orm/pg-core";
var getTableConfigForDatabase = (table) => {
if (is(table, PgTable)) {
return getTableConfig(table);
}
throw new Error(
`drizzle-zero: Unsupported table type: ${getTableName(table)}. Only Postgres tables are supported.`
);
};
// src/drizzle-to-zero.ts
var drizzleDataTypeToZeroType = {
number: "number",
bigint: "number",
boolean: "boolean",
date: "number"
};
var drizzleColumnTypeToZeroType = {
PgText: "string",
PgChar: "string",
PgVarchar: "string",
PgUUID: "string",
PgEnumColumn: "string",
PgJsonb: "json",
PgJson: "json",
PgNumeric: "number",
PgDateString: "number",
PgTimestampString: "number"
};
// src/util.ts
function typedEntries(obj) {
return Object.entries(obj);
}
function debugLog(debug, message, ...args) {
if (debug) {
console.log(`\u2139\uFE0F drizzle-zero: ${message}`, ...args);
}
}
// src/tables.ts
var createZeroTableBuilder = (tableName, table, columns, debug, casing) => {
const actualTableName = getTableName2(table);
const tableColumns = getTableColumns(table);
const tableConfig = getTableConfigForDatabase(table);
const primaryKeysFromColumns = [];
const columnsMapped = typedEntries(tableColumns).reduce(
(acc, [key, column]) => {
const columnConfig = columns == null ? void 0 : columns[key];
if (columnConfig === false) {
debugLog(
debug,
`Skipping column ${String(key)} because columnConfig is false`
);
return acc;
}
const resolvedColumnName = !column.keyAsName || casing === void 0 ? column.name : casing === "camelCase" ? toCamelCase(column.name) : toSnakeCase(column.name);
if (typeof columnConfig !== "boolean" && typeof columnConfig !== "object" && typeof columnConfig !== "undefined") {
throw new Error(
`drizzle-zero: Invalid column config for column ${resolvedColumnName} - expected boolean or ColumnBuilder but was ${typeof columnConfig}`
);
}
const isColumnBuilder = (value) => typeof value === "object" && value !== null && "schema" in value;
const isColumnConfigOverride = isColumnBuilder(columnConfig);
const type = drizzleColumnTypeToZeroType[column.columnType] ?? drizzleDataTypeToZeroType[column.dataType] ?? null;
if (type === null && !isColumnConfigOverride) {
console.warn(
`\u{1F6A8} drizzle-zero: Unsupported column type: ${resolvedColumnName} - ${column.columnType} (${column.dataType}). It will not be included in the output. Must be supported by Zero, e.g.: ${Object.keys({ ...drizzleDataTypeToZeroType, ...drizzleColumnTypeToZeroType }).join(" | ")}`
);
return acc;
}
const isColumnOptional = typeof columnConfig === "boolean" || typeof columnConfig === "undefined" ? column.hasDefault && column.defaultFn === void 0 ? true : !column.notNull : isColumnConfigOverride ? columnConfig.schema.optional : false;
if (column.primary) {
primaryKeysFromColumns.push(String(key));
}
if (columnConfig && typeof columnConfig !== "boolean") {
return {
...acc,
[key]: columnConfig
};
}
const schemaValue = column.enumValues ? zeroEnumeration() : type === "string" ? zeroString() : type === "number" ? zeroNumber() : type === "json" ? zeroJson() : zeroBoolean();
const schemaValueWithFrom = resolvedColumnName !== key ? schemaValue.from(resolvedColumnName) : schemaValue;
return {
...acc,
[key]: isColumnOptional ? schemaValueWithFrom.optional() : schemaValueWithFrom
};
},
{}
);
const primaryKeys = [
...primaryKeysFromColumns,
...tableConfig.primaryKeys.flatMap(
(k) => k.columns.map(
(c) => getDrizzleColumnKeyFromColumnName({
columnName: c.name,
table: c.table
})
)
)
];
if (!primaryKeys.length) {
throw new Error(
`drizzle-zero: No primary keys found in table - ${actualTableName}. Did you forget to define a primary key?`
);
}
const resolvedTableName = tableConfig.schema ? `${tableConfig.schema}.${actualTableName}` : actualTableName;
const zeroBuilder = zeroTable(tableName);
const zeroBuilderWithFrom = resolvedTableName !== tableName ? zeroBuilder.from(resolvedTableName) : zeroBuilder;
return zeroBuilderWithFrom.columns(columnsMapped).primaryKey(...primaryKeys);
};
var getDrizzleColumnKeyFromColumnName = ({
columnName,
table
}) => {
var _a;
const tableColumns = getTableColumns(table);
return (_a = typedEntries(tableColumns).find(
([_name, column]) => column.name === columnName
)) == null ? void 0 : _a[0];
};
// src/relations.ts
var drizzleZeroConfig = (schema, config) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
let tables = [];
const tableColumnNamesForSourceTable = /* @__PURE__ */ new Map();
const assertRelationNameIsNotAColumnName = ({
sourceTableName,
relationName
}) => {
const tableColumnNames = tableColumnNamesForSourceTable.get(sourceTableName);
if (tableColumnNames == null ? void 0 : tableColumnNames.has(relationName)) {
throw new Error(
`drizzle-zero: Invalid relationship name for ${String(sourceTableName)}.${relationName}: there is already a table column with the name ${relationName} and this cannot be used as a relationship name`
);
}
};
for (const [tableName, tableOrRelations] of typedEntries(schema)) {
if (!tableOrRelations) {
throw new Error(
`drizzle-zero: table or relation with key ${String(tableName)} is not defined`
);
}
if (is2(tableOrRelations, Table2)) {
const table = tableOrRelations;
const tableConfig = (_a = config == null ? void 0 : config.tables) == null ? void 0 : _a[tableName];
if (tableConfig === false) {
debugLog(
config == null ? void 0 : config.debug,
`Skipping table ${String(tableName)} - no config provided`
);
continue;
}
const tableSchema = createZeroTableBuilder(
String(tableName),
table,
tableConfig,
config == null ? void 0 : config.debug,
config == null ? void 0 : config.casing
);
tables.push(tableSchema);
const tableColumnNames = /* @__PURE__ */ new Set();
for (const columnName of Object.keys(tableSchema.schema.columns)) {
tableColumnNames.add(columnName);
}
tableColumnNamesForSourceTable.set(String(tableName), tableColumnNames);
}
}
let relationships = {};
if (config == null ? void 0 : config.manyToMany) {
for (const [sourceTableName, manyConfig] of Object.entries(
config.manyToMany
)) {
if (!manyConfig) continue;
for (const [
relationName,
[junctionTableNameOrObject, destTableNameOrObject]
] of Object.entries(manyConfig)) {
if (typeof junctionTableNameOrObject === "string" && typeof destTableNameOrObject === "string") {
const junctionTableName = junctionTableNameOrObject;
const destTableName = destTableNameOrObject;
const sourceTable = (_b = typedEntries(schema).find(
([tableName, tableOrRelations]) => is2(tableOrRelations, Table2) && tableName === sourceTableName
)) == null ? void 0 : _b[1];
const destTable = (_c = typedEntries(schema).find(
([tableName, tableOrRelations]) => is2(tableOrRelations, Table2) && tableName === destTableName
)) == null ? void 0 : _c[1];
const junctionTable = (_d = typedEntries(schema).find(
([tableName, tableOrRelations]) => is2(tableOrRelations, Table2) && tableName === junctionTableName
)) == null ? void 0 : _d[1];
if (!sourceTable || !destTable || !junctionTable || !is2(sourceTable, Table2) || !is2(destTable, Table2) || !is2(junctionTable, Table2)) {
throw new Error(
`drizzle-zero: Invalid many-to-many configuration for ${String(sourceTableName)}.${relationName}: Could not find ${!sourceTable ? "source" : !destTable ? "destination" : "junction"} table`
);
}
const sourceJunctionFields = findRelationSourceAndDestFields(schema, {
sourceTable,
referencedTableName: getTableName3(junctionTable)
});
const junctionDestFields = findRelationSourceAndDestFields(schema, {
sourceTable: destTable,
referencedTableName: getTableName3(junctionTable)
});
if (!sourceJunctionFields.sourceFieldNames.length || !junctionDestFields.sourceFieldNames.length || !junctionDestFields.destFieldNames.length || !sourceJunctionFields.destFieldNames.length) {
throw new Error(
`drizzle-zero: Invalid many-to-many configuration for ${String(sourceTableName)}.${relationName}: Could not find relationships in junction table ${junctionTableName}`
);
}
if (!((_e = config.tables) == null ? void 0 : _e[junctionTableName]) || !((_f = config.tables) == null ? void 0 : _f[sourceTableName]) || !((_g = config.tables) == null ? void 0 : _g[destTableName])) {
debugLog(
config.debug,
`Skipping many-to-many relationship - tables not in schema config:`,
{ junctionTable, sourceTableName, destTableName }
);
continue;
}
assertRelationNameIsNotAColumnName({
sourceTableName,
relationName
});
relationships[sourceTableName] = {
...(relationships == null ? void 0 : relationships[sourceTableName]) ?? {},
[relationName]: [
{
sourceField: sourceJunctionFields.sourceFieldNames,
destField: sourceJunctionFields.destFieldNames,
destSchema: junctionTableName,
cardinality: "many"
},
{
sourceField: junctionDestFields.destFieldNames,
destField: junctionDestFields.sourceFieldNames,
destSchema: destTableName,
cardinality: "many"
}
]
};
debugLog(config.debug, `Added many-to-many relationship:`, {
sourceTable: sourceTableName,
relationName,
relationship: (_h = relationships[sourceTableName]) == null ? void 0 : _h[relationName]
});
} else {
const junctionTableName = (junctionTableNameOrObject == null ? void 0 : junctionTableNameOrObject.destTable) ?? null;
const junctionSourceField = (junctionTableNameOrObject == null ? void 0 : junctionTableNameOrObject.sourceField) ?? null;
const junctionDestField = (junctionTableNameOrObject == null ? void 0 : junctionTableNameOrObject.destField) ?? null;
const destTableName = (destTableNameOrObject == null ? void 0 : destTableNameOrObject.destTable) ?? null;
const destSourceField = (destTableNameOrObject == null ? void 0 : destTableNameOrObject.sourceField) ?? null;
const destDestField = (destTableNameOrObject == null ? void 0 : destTableNameOrObject.destField) ?? null;
if (!junctionSourceField || !junctionDestField || !destSourceField || !destDestField || !junctionTableName || !destTableName) {
throw new Error(
`drizzle-zero: Invalid many-to-many configuration for ${String(sourceTableName)}.${relationName}: Not all required fields were provided.`
);
}
if (!((_i = config.tables) == null ? void 0 : _i[junctionTableName]) || !((_j = config.tables) == null ? void 0 : _j[sourceTableName]) || !((_k = config.tables) == null ? void 0 : _k[destTableName])) {
continue;
}
assertRelationNameIsNotAColumnName({
sourceTableName,
relationName
});
relationships[sourceTableName] = {
...(relationships == null ? void 0 : relationships[sourceTableName]) ?? {},
[relationName]: [
{
sourceField: junctionSourceField,
destField: junctionDestField,
destSchema: junctionTableName,
cardinality: "many"
},
{
sourceField: destSourceField,
destField: destDestField,
destSchema: destTableName,
cardinality: "many"
}
]
};
}
}
}
}
for (const [relationName, tableOrRelations] of typedEntries(schema)) {
if (!tableOrRelations) {
throw new Error(
`drizzle-zero: table or relation with key ${String(relationName)} is not defined`
);
}
if (is2(tableOrRelations, Relations)) {
const actualTableName = getTableName3(tableOrRelations.table);
const tableName = getDrizzleKeyFromTableName({
schema,
tableName: actualTableName
});
const relationsConfig = getRelationsConfig(tableOrRelations);
for (const relation of Object.values(relationsConfig)) {
let sourceFieldNames = [];
let destFieldNames = [];
if (is2(relation, One)) {
sourceFieldNames = ((_m = (_l = relation == null ? void 0 : relation.config) == null ? void 0 : _l.fields) == null ? void 0 : _m.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f == null ? void 0 : f.name,
table: f.table
})
)) ?? [];
destFieldNames = ((_o = (_n = relation == null ? void 0 : relation.config) == null ? void 0 : _n.references) == null ? void 0 : _o.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f == null ? void 0 : f.name,
table: f.table
})
)) ?? [];
}
if (!sourceFieldNames.length || !destFieldNames.length) {
if (relation.relationName) {
const sourceAndDestFields = findNamedSourceAndDestFields(
schema,
relation
);
sourceFieldNames = sourceAndDestFields.sourceFieldNames;
destFieldNames = sourceAndDestFields.destFieldNames;
} else {
const sourceAndDestFields = findRelationSourceAndDestFields(
schema,
relation
);
sourceFieldNames = sourceAndDestFields.sourceFieldNames;
destFieldNames = sourceAndDestFields.destFieldNames;
}
}
if (!sourceFieldNames.length || !destFieldNames.length) {
throw new Error(
`drizzle-zero: No relationship found for: ${relation.fieldName} (${is2(relation, One) ? "One" : "Many"} from ${String(tableName)} to ${relation.referencedTableName}). Did you forget to define ${relation.relationName ? `a named relation "${relation.relationName}"` : `an opposite ${is2(relation, One) ? "Many" : "One"} relation`}?`
);
}
const referencedTableKey = getDrizzleKeyFromTableName({
schema,
tableName: relation.referencedTableName
});
if (typeof (config == null ? void 0 : config.tables) !== "undefined" && (!((_p = config == null ? void 0 : config.tables) == null ? void 0 : _p[tableName]) || !((_q = config == null ? void 0 : config.tables) == null ? void 0 : _q[referencedTableKey]))) {
debugLog(
config == null ? void 0 : config.debug,
`Skipping relation - tables not in schema config:`,
{
sourceTable: tableName,
referencedTable: referencedTableKey
}
);
continue;
}
if ((_r = relationships[tableName]) == null ? void 0 : _r[relation.fieldName]) {
throw new Error(
`drizzle-zero: Duplicate relationship found for: ${relation.fieldName} (from ${String(tableName)} to ${relation.referencedTableName}).`
);
}
assertRelationNameIsNotAColumnName({
sourceTableName: tableName,
relationName: relation.fieldName
});
relationships[tableName] = {
...(relationships == null ? void 0 : relationships[tableName]) ?? {},
[relation.fieldName]: [
{
sourceField: sourceFieldNames,
destField: destFieldNames,
destSchema: getDrizzleKeyFromTableName({
schema,
tableName: relation.referencedTableName
}),
cardinality: is2(relation, One) ? "one" : "many"
}
]
};
}
}
}
const finalSchema = createSchema({
tables,
relationships: Object.entries(relationships).map(([key, value]) => ({
name: key,
relationships: value
}))
});
debugLog(
config == null ? void 0 : config.debug,
"Output Zero schema",
JSON.stringify(finalSchema, null, 2)
);
return finalSchema;
};
var getReferencedTableName = (rel) => {
if ("referencedTableName" in rel && rel.referencedTableName) {
return rel.referencedTableName;
}
if ("referencedTable" in rel && rel.referencedTable) {
return getTableName3(rel.referencedTable);
}
return void 0;
};
var findRelationSourceAndDestFields = (schema, relation) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const sourceTableName = getTableName3(relation.sourceTable);
const referencedTableName = getReferencedTableName(relation);
for (const tableOrRelations of Object.values(schema)) {
if (is2(tableOrRelations, Relations)) {
const relationsConfig = getRelationsConfig(tableOrRelations);
for (const relationConfig of Object.values(relationsConfig)) {
if (!is2(relationConfig, One)) continue;
const foundSourceName = getTableName3(relationConfig.sourceTable);
const foundReferencedName = getReferencedTableName(relationConfig);
if (foundSourceName === referencedTableName && foundReferencedName === sourceTableName) {
const sourceFieldNames = ((_b = (_a = relationConfig.config) == null ? void 0 : _a.references) == null ? void 0 : _b.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f.name,
table: f.table
})
)) ?? [];
const destFieldNames = ((_d = (_c = relationConfig.config) == null ? void 0 : _c.fields) == null ? void 0 : _d.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f.name,
table: f.table
})
)) ?? [];
if (sourceFieldNames.length && destFieldNames.length) {
return {
sourceFieldNames,
destFieldNames
};
}
}
if (foundSourceName === sourceTableName && foundReferencedName === referencedTableName) {
const sourceFieldNames = ((_f = (_e = relationConfig.config) == null ? void 0 : _e.fields) == null ? void 0 : _f.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f.name,
table: f.table
})
)) ?? [];
const destFieldNames = ((_h = (_g = relationConfig.config) == null ? void 0 : _g.references) == null ? void 0 : _h.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f.name,
table: f.table
})
)) ?? [];
if (sourceFieldNames.length && destFieldNames.length) {
return {
sourceFieldNames,
destFieldNames
};
}
}
}
}
}
return {
sourceFieldNames: [],
destFieldNames: []
};
};
var findNamedSourceAndDestFields = (schema, relation) => {
var _a, _b, _c, _d;
for (const tableOrRelations of Object.values(schema)) {
if (is2(tableOrRelations, Relations)) {
const relationsConfig = getRelationsConfig(tableOrRelations);
for (const relationConfig of Object.values(relationsConfig)) {
if (is2(relationConfig, One) && relationConfig.relationName === relation.relationName) {
return {
destFieldNames: ((_b = (_a = relationConfig.config) == null ? void 0 : _a.fields) == null ? void 0 : _b.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f.name,
table: f.table
})
)) ?? [],
sourceFieldNames: ((_d = (_c = relationConfig.config) == null ? void 0 : _c.references) == null ? void 0 : _d.map(
(f) => getDrizzleColumnKeyFromColumnName({
columnName: f.name,
table: f.table
})
)) ?? []
};
}
}
}
}
return {
sourceFieldNames: [],
destFieldNames: []
};
};
var getRelationsConfig = (relations) => {
return relations.config(
createTableRelationsHelpers(relations.table)
);
};
var getDrizzleKeyFromTableName = ({
schema,
tableName
}) => {
var _a;
return (_a = typedEntries(schema).find(
([_name, tableOrRelations]) => is2(tableOrRelations, Table2) && getTableName3(tableOrRelations) === tableName
)) == null ? void 0 : _a[0];
};
// src/cli/drizzle-kit.ts
var getDefaultConfig = async ({
drizzleSchemaPath,
drizzleKitConfigPath,
tsProject,
debug
}) => {
const {
drizzleSchemaPath: resolvedDrizzleSchemaPath,
casing: drizzleCasing
} = await getFullDrizzleSchemaFilePath({
drizzleSchemaPath,
drizzleKitConfigPath
});
const resolvedDrizzleSchemaPathUrl = url2.pathToFileURL(
resolvedDrizzleSchemaPath
).href;
const drizzleSchema = await tsImport2(
resolvedDrizzleSchemaPathUrl,
import.meta.url
);
const zeroSchema = drizzleZeroConfig(drizzleSchema, {
casing: drizzleCasing ?? void 0,
debug: Boolean(debug)
});
return {
type: "drizzle-kit",
zeroSchema,
drizzleSchemaSourceFile: await getDrizzleSchemaSourceFile({
tsProject,
drizzleSchemaPath: resolvedDrizzleSchemaPath
}),
drizzleCasing
};
};
var getFullDrizzleSchemaFilePath = async ({
drizzleKitConfigPath,
drizzleSchemaPath
}) => {
if (drizzleSchemaPath) {
const fullPath = path2.resolve(process.cwd(), drizzleSchemaPath);
try {
await fs2.access(fullPath);
return {
drizzleSchemaPath: fullPath,
casing: null
};
} catch (error) {
console.error(
`\u274C drizzle-zero: could not find Drizzle schema file at ${fullPath}`
);
process.exit(1);
}
}
if (drizzleKitConfigPath) {
const fullPath = path2.resolve(process.cwd(), drizzleKitConfigPath);
try {
await fs2.access(fullPath);
const drizzleKitConfigFilePathUrl = url2.pathToFileURL(fullPath).href;
const drizzleKitConfigImport = await tsImport2(
drizzleKitConfigFilePathUrl,
import.meta.url
);
const drizzleKitConfig = drizzleKitConfigImport == null ? void 0 : drizzleKitConfigImport.default;
try {
if (Array.isArray(drizzleKitConfig.schema)) {
throw new Error(
"\u274C drizzle-zero: Drizzle Kit config schema is an array. Please specify a single schema file for imports to be able to work correctly."
);
}
if (drizzleKitConfig.schema) {
const fullPath2 = path2.resolve(process.cwd(), drizzleKitConfig.schema);
await fs2.access(fullPath2);
return {
drizzleSchemaPath: fullPath2,
casing: drizzleKitConfig.casing ?? null
};
}
} catch (error) {
console.error(
`\u274C drizzle-zero: could not find Drizzle file pulled from Drizzle Kit config at ${JSON.stringify(drizzleKitConfig)}`,
error
);
process.exit(1);
}
return {
drizzleSchemaPath: fullPath,
casing: drizzleKitConfig.casing ?? null
};
} catch (error) {
console.error(
`\u274C drizzle-zero: could not find Drizzle Kit config file at ${drizzleKitConfigPath}`,
error
);
process.exit(1);
}
}
console.error(`\u274C drizzle-zero: could not find Drizzle Kit config file`);
process.exit(1);
};
async function getDrizzleSchemaSourceFile({
tsProject,
drizzleSchemaPath
}) {
const sourceFile = tsProject.getSourceFile(drizzleSchemaPath);
if (!sourceFile) {
throw new Error(
`\u274C drizzle-zero: Failed to find type definitions for ${drizzleSchemaPath}`
);
}
return sourceFile;
}
// src/cli/shared.ts
import { VariableDeclarationKind } from "ts-morph";
async function getGeneratedSchema({
tsProject,
result,
outputFilePath,
jsFileExtension = false
}) {
const schemaObjectName = "schema";
const typename = "Schema";
const zeroSchemaGenerated = tsProject.createSourceFile(outputFilePath, "", {
overwrite: true
});
zeroSchemaGenerated.addImportDeclaration({
moduleSpecifier: "drizzle-zero",
namedImports: [{ name: "ZeroCustomType" }],
isTypeOnly: true
});
let zeroSchemaSpecifier;
if (result.type === "config") {
const moduleSpecifier = zeroSchemaGenerated.getRelativePathAsModuleSpecifierTo(
result.zeroSchemaTypeDeclarations[1].getSourceFile()
);
zeroSchemaGenerated.addImportDeclaration({
moduleSpecifier: jsFileExtension ? `${moduleSpecifier}.js` : moduleSpecifier,
namedImports: [{ name: result.exportName, alias: "zeroSchema" }],
isTypeOnly: true
});
zeroSchemaSpecifier = "typeof zeroSchema";
} else {
const moduleSpecifier = zeroSchemaGenerated.getRelativePathAsModuleSpecifierTo(
result.drizzleSchemaSourceFile
);
zeroSchemaGenerated.addImportDeclaration({
moduleSpecifier: jsFileExtension ? `${moduleSpecifier}.js` : moduleSpecifier,
namespaceImport: "drizzleSchema",
isTypeOnly: true
});
zeroSchemaGenerated.addImportDeclaration({
moduleSpecifier: "drizzle-zero",
namedImports: [{ name: "DrizzleToZeroSchema" }],
isTypeOnly: true
});
zeroSchemaGenerated.addTypeAlias({
name: "ZeroSchema",
isExported: false,
type: `DrizzleToZeroSchema<typeof drizzleSchema${result.drizzleCasing ? `, "${result.drizzleCasing}"` : ""}>`
});
zeroSchemaSpecifier = "ZeroSchema";
}
const schemaVariable = zeroSchemaGenerated.addVariableStatement({
declarationKind: VariableDeclarationKind.Const,
isExported: true,
declarations: [
{
name: schemaObjectName,
initializer: (writer) => {
const writeValue = (value, keys = [], indent = 0) => {
const indentStr = " ".repeat(indent);
if (!value || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value)) {
writer.write(JSON.stringify(value));
} else if (typeof value === "object" && value !== null) {
writer.write("{");
const entries = Object.entries(value);
if (entries.length > 0) {
writer.newLine();
for (let i = 0; i < entries.length; i++) {
const [key, propValue] = entries[i] ?? [];
if (!key) {
continue;
}
writer.write(indentStr + " " + JSON.stringify(key) + ": ");
if (key === "customType" && propValue === null) {
const tableIndex = 1;
const columnIndex = 3;
writer.write(
`null as unknown as ZeroCustomType<${zeroSchemaSpecifier}, "${keys[tableIndex]}", "${keys[columnIndex]}">`
);
} else {
writeValue(propValue, [...keys, key], indent + 2);
}
if (i < entries.length - 1) {
writer.write(",");
}
writer.newLine();
}
writer.write(indentStr);
}
writer.write("}");
}
};
writeValue(result.zeroSchema);
writer.write(` as const`);
}
}
]
});
schemaVariable.addJsDoc({
description: "\nThe Zero schema object.\nThis type is auto-generated from your Drizzle schema definition."
});
const schemaTypeAlias = zeroSchemaGenerated.addTypeAlias({
name: typename,
isExported: true,
type: `typeof ${schemaObjectName}`
});
schemaTypeAlias.addJsDoc({
description: "\nRepresents the Zero schema type.\nThis type is auto-generated from your Drizzle schema definition."
});
zeroSchemaGenerated.formatText();
const organizedFile = zeroSchemaGenerated.organizeImports();
const file = organizedFile.getText();
return `/* eslint-disable */
/* tslint:disable */
// noinspection JSUnusedGlobalSymbols
// biome-ignore-all
/*
* ------------------------------------------------------------
* ## This file was automatically generated by drizzle-zero. ##
* ## Any changes you make to this file will be overwritten. ##
* ## ##
* ## Additionally, you should also exclude this file from ##
* ## your linter and/or formatter to prevent it from being ##
* ## checked or modified. ##
* ## ##
* ## SOURCE: https://github.com/0xcadams/drizzle-zero ##
* ------------------------------------------------------------
*/
${file}`;
}
// src/cli/index.ts
var defaultConfigFile = "./drizzle-zero.config.ts";
var defaultOutputFile = "./zero-schema.gen.ts";
var defaultTsConfigFile = "./tsconfig.json";
var defaultDrizzleKitConfigPath = "./drizzle.config.ts";
async function loadPrettier() {
try {
return await import("prettier");
} catch (_) {
}
try {
const path4 = __require.resolve("prettier", { paths: [process.cwd()] });
return await import(pathToFileURL3(path4).href);
} catch {
throw new Error(
"\u26A0\uFE0F drizzle-zero: prettier could not be found. Install it locally with\n npm i -D prettier"
);
}
}
async function formatSchema(schema) {
try {
const prettier = await loadPrettier();
return prettier.format(schema, {
parser: "typescript"
});
} catch (error) {
console.warn("\u26A0\uFE0F drizzle-zero: prettier not found, skipping formatting");
return schema;
}
}
async function main(opts = {}) {
const {
config,
tsConfigPath,
format,
outputFilePath,
drizzleSchemaPath,
drizzleKitConfigPath,
debug,
jsFileExtension
} = { ...opts };
const resolvedTsConfigPath = tsConfigPath ?? defaultTsConfigFile;
const resolvedOutputFilePath = outputFilePath ?? defaultOutputFile;
const defaultConfigFilePath2 = await getDefaultConfigFilePath();
const configFilePath = config ?? defaultConfigFilePath2;
if (!configFilePath) {
console.log(
"\u{1F636}\u200D\u{1F32B}\uFE0F drizzle-zero: Using all tables/columns from Drizzle schema"
);
}
const tsProject = new Project4({
tsConfigFilePath: resolvedTsConfigPath
});
const result = configFilePath ? await getConfigFromFile({
configFilePath,
tsProject
}) : await getDefaultConfig({
drizzleSchemaPath,
drizzleKitConfigPath,
tsProject,
debug: Boolean(debug)
});
if (!(result == null ? void 0 : result.zeroSchema)) {
console.error(
"\u274C drizzle-zero: No config found in the config file - did you export `default` or `schema`?"
);
process.exit(1);
}
let zeroSchemaGenerated = await getGeneratedSchema({
tsProject,
result,
outputFilePath: resolvedOutputFilePath,
jsFileExtension: Boolean(jsFileExtension)
});
if (format) {
zeroSchemaGenerated = await formatSchema(zeroSchemaGenerated);
}
return zeroSchemaGenerated;
}
async function cli() {
const program = new Command();
program.name("drizzle-zero").description("The CLI for converting Drizzle ORM schemas to Zero schemas");
program.command("generate").option(
"-c, --config <input-file>",
`Path to the ${defaultConfigFile} configuration file`
).option("-s, --schema <input-file>", `Path to the Drizzle schema file`).option(
"-k, --drizzle-kit-config <input-file>",
`Path to the Drizzle Kit config file`,
defaultDrizzleKitConfigPath
).option(
"-o, --output <output-file>",
`Path to the generated output file`,
defaultOutputFile
).option(
"-t, --tsconfig <tsconfig-file>",
`Path to the custom tsconfig file`,
defaultTsConfigFile
).option("-f, --format", `Format the generated schema`, false).option("-d, --debug", `Enable debug mode`).option(
"-j, --js-file-extension",
`Add a .js file extension to the output (for usage without "bundler" module resolution)`,
false
).action(async (command) => {
console.log(`\u2699\uFE0F drizzle-zero: Generating zero schema...`);
const zeroSchema = await main({
config: command.config,
tsConfigPath: command.tsconfig,
format: command.format,
outputFilePath: command.output,
drizzleSchemaPath: command.schema,
drizzleKitConfigPath: command.drizzleKitConfig,
debug: command.debug,
jsFileExtension: command.jsFileExtension
});
if (command.output) {
await fs3.writeFile(
path3.resolve(process.cwd(), command.output),
zeroSchema
);
console.log(
`\u2705 drizzle-zero: Zero schema written to ${command.output}`
);
} else {
console.log("drizzle-zero: ", {
schema: zeroSchema
});
}
});
program.parse();
}
cli().catch((error) => {
console.error("\u274C drizzle-zero error:", error);
process.exit(1);
});
export {
formatSchema,
loadPrettier
};