cogsbox-shape 0.5.70 → 0.5.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +23 -13
- package/dist/generateSQL.d.ts +5 -4
- package/dist/generateSQL.js +78 -20
- package/dist/schema.d.ts +13 -1
- package/dist/schema.js +6 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,38 +6,48 @@ import { generateSQL } from "./generateSQL.js";
|
|
|
6
6
|
import { unlink, writeFile } from "fs/promises";
|
|
7
7
|
import { spawnSync } from "child_process";
|
|
8
8
|
const program = new Command();
|
|
9
|
-
program
|
|
9
|
+
program
|
|
10
|
+
.name("cogsbox-shape")
|
|
11
|
+
.description("CLI for cogsbox-shape schema tools")
|
|
12
|
+
.version("0.5.70");
|
|
13
|
+
program
|
|
14
|
+
.command("generate-sql <file>")
|
|
15
|
+
.description("Generate SQL from your schema definitions")
|
|
16
|
+
.option("-o, --output <path>", "Output SQL file path", "./cogsbox-shape-sql.sql")
|
|
17
|
+
.option("--no-foreign-keys", "Generate SQL without foreign key constraints")
|
|
18
|
+
.action(async (file, options) => {
|
|
10
19
|
try {
|
|
11
20
|
const fullPath = path.resolve(process.cwd(), file);
|
|
12
21
|
if (file.endsWith(".ts")) {
|
|
13
|
-
// Create a virtual module that imports and
|
|
22
|
+
// Create a virtual module that imports and USES the schema directly
|
|
14
23
|
const virtualModule = `
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
import { schemas } from '${pathToFileURL(fullPath).href}';
|
|
25
|
+
import { generateSQL } from '${pathToFileURL(path.join(process.cwd(), "dist/generateSQL.js")).href}';
|
|
26
|
+
|
|
27
|
+
generateSQL(schemas, '${options.output}', { includeForeignKeys: ${options.foreignKeys !== false} })
|
|
28
|
+
.then(() => console.log('Done'))
|
|
29
|
+
.catch(err => console.error(err));
|
|
30
|
+
`;
|
|
18
31
|
// Write this to a temporary file
|
|
19
32
|
const tmpFile = path.join(path.dirname(fullPath), ".tmp-schema-loader.ts");
|
|
20
33
|
await writeFile(tmpFile, virtualModule, "utf8");
|
|
21
34
|
const result = spawnSync("npx", ["tsx", tmpFile], {
|
|
22
35
|
encoding: "utf8",
|
|
23
|
-
stdio:
|
|
36
|
+
stdio: "inherit",
|
|
24
37
|
});
|
|
25
38
|
// Clean up temp file
|
|
26
39
|
await unlink(tmpFile).catch(() => { });
|
|
27
40
|
if (result.error) {
|
|
28
41
|
throw result.error;
|
|
29
42
|
}
|
|
30
|
-
if (result.stderr) {
|
|
31
|
-
console.error("stderr:", result.stderr);
|
|
32
|
-
}
|
|
33
|
-
const schema = JSON.parse(result.stdout);
|
|
34
|
-
await generateSQL(schema);
|
|
35
43
|
}
|
|
36
44
|
else {
|
|
37
45
|
const schema = await import(pathToFileURL(fullPath).href);
|
|
38
|
-
await generateSQL(schema.schemas
|
|
46
|
+
await generateSQL(schema.schemas, options.output, {
|
|
47
|
+
includeForeignKeys: options.foreignKeys,
|
|
48
|
+
});
|
|
39
49
|
}
|
|
40
|
-
console.log(
|
|
50
|
+
console.log(`Generated SQL successfully at ${options.output}`);
|
|
41
51
|
}
|
|
42
52
|
catch (error) {
|
|
43
53
|
console.error("Error:", error);
|
package/dist/generateSQL.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
schemas: Record<string, Schema<any>>;
|
|
1
|
+
type SchemaInput = Record<string, any> | {
|
|
2
|
+
schemas: Record<string, any>;
|
|
4
3
|
};
|
|
5
|
-
export declare function generateSQL(input: SchemaInput, outputPath?: string
|
|
4
|
+
export declare function generateSQL(input: SchemaInput, outputPath?: string, options?: {
|
|
5
|
+
includeForeignKeys?: boolean;
|
|
6
|
+
}): Promise<string>;
|
|
6
7
|
export {};
|
package/dist/generateSQL.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
|
-
// SQL Type mapping
|
|
3
2
|
const sqlTypeMap = {
|
|
4
3
|
int: "INTEGER",
|
|
5
4
|
varchar: (length = 255) => `VARCHAR(${length})`,
|
|
@@ -17,46 +16,105 @@ function isWrappedSchema(input) {
|
|
|
17
16
|
input.schemas !== null &&
|
|
18
17
|
typeof input.schemas === "object");
|
|
19
18
|
}
|
|
20
|
-
export async function generateSQL(input, outputPath = "cogsbox-shape-sql.sql") {
|
|
19
|
+
export async function generateSQL(input, outputPath = "cogsbox-shape-sql.sql", options = { includeForeignKeys: true }) {
|
|
21
20
|
if (!input) {
|
|
22
21
|
throw new Error("No schema input provided");
|
|
23
22
|
}
|
|
24
|
-
// Extract schemas using type guard
|
|
25
23
|
const schemas = isWrappedSchema(input) ? input.schemas : input;
|
|
26
24
|
if (!schemas || typeof schemas !== "object") {
|
|
27
25
|
throw new Error("Invalid schemas input");
|
|
28
26
|
}
|
|
29
27
|
const sql = [];
|
|
30
|
-
// Generate SQL for each schema
|
|
31
28
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
32
29
|
const tableName = schema._tableName;
|
|
30
|
+
if (!tableName) {
|
|
31
|
+
console.warn(`Skipping schema '${name}' - no _tableName found`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
33
34
|
const fields = [];
|
|
34
35
|
const foreignKeys = [];
|
|
35
|
-
// Process each field in the schema
|
|
36
36
|
for (const [fieldName, field] of Object.entries(schema)) {
|
|
37
|
-
|
|
37
|
+
// Skip metadata fields
|
|
38
|
+
const f = field; // Just cast once
|
|
39
|
+
console.log(`Processing field: ${fieldName}`, f);
|
|
40
|
+
// Skip metadata fields
|
|
41
|
+
if (fieldName === "_tableName" ||
|
|
42
|
+
fieldName === "SchemaWrapperBrand" ||
|
|
43
|
+
fieldName.startsWith("__") ||
|
|
44
|
+
typeof f !== "object" ||
|
|
45
|
+
!f)
|
|
46
|
+
continue;
|
|
47
|
+
// Handle reference fields
|
|
48
|
+
if (f.type === "reference" && f.to) {
|
|
49
|
+
const referencedField = f.to();
|
|
50
|
+
const targetTableName = referencedField.__parentTableType._tableName;
|
|
51
|
+
const targetFieldName = referencedField.__meta._key;
|
|
52
|
+
console.log(`Found reference field: ${fieldName} -> ${targetTableName}.${targetFieldName}`);
|
|
53
|
+
fields.push(` ${fieldName} INTEGER NOT NULL`);
|
|
54
|
+
if (options.includeForeignKeys) {
|
|
55
|
+
foreignKeys.push(` FOREIGN KEY (${fieldName}) REFERENCES ${targetTableName}(${targetFieldName})`);
|
|
56
|
+
}
|
|
38
57
|
continue;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
}
|
|
59
|
+
// Get the actual field definition from enriched structure
|
|
60
|
+
let fieldDef = f;
|
|
61
|
+
// If it's an enriched field, extract the original field definition
|
|
62
|
+
if (f.__meta && f.__meta._fieldType) {
|
|
63
|
+
fieldDef = f.__meta._fieldType;
|
|
64
|
+
}
|
|
65
|
+
// Now check if fieldDef has config
|
|
66
|
+
if (fieldDef && fieldDef.config && fieldDef.config.sql) {
|
|
67
|
+
const sqlConfig = fieldDef.config.sql;
|
|
68
|
+
// Handle relation configs (hasMany, hasOne, etc.)
|
|
69
|
+
if (["hasMany", "hasOne", "belongsTo", "manyToMany"].includes(sqlConfig.type)) {
|
|
70
|
+
// Only belongsTo creates a column
|
|
71
|
+
if (sqlConfig.type === "belongsTo" &&
|
|
72
|
+
sqlConfig.fromKey &&
|
|
73
|
+
sqlConfig.schema) {
|
|
74
|
+
fields.push(` ${sqlConfig.fromKey} INTEGER`);
|
|
75
|
+
if (options.includeForeignKeys) {
|
|
76
|
+
const targetSchema = sqlConfig.schema();
|
|
77
|
+
foreignKeys.push(` FOREIGN KEY (${sqlConfig.fromKey}) REFERENCES ${targetSchema._tableName}(id)`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Handle regular SQL types
|
|
83
|
+
const { type, nullable, pk, length, default: defaultValue } = sqlConfig;
|
|
84
|
+
if (!sqlTypeMap[type]) {
|
|
85
|
+
console.warn(`Unknown SQL type: ${type} for field ${fieldName}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
42
88
|
const sqlType = typeof sqlTypeMap[type] === "function"
|
|
43
89
|
? sqlTypeMap[type](length)
|
|
44
90
|
: sqlTypeMap[type];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
91
|
+
let fieldDefStr = ` ${fieldName} ${sqlType}`;
|
|
92
|
+
if (pk)
|
|
93
|
+
fieldDefStr += " PRIMARY KEY AUTO_INCREMENT";
|
|
94
|
+
if (!nullable && !pk)
|
|
95
|
+
fieldDefStr += " NOT NULL";
|
|
96
|
+
// Handle defaults
|
|
97
|
+
if (defaultValue !== undefined &&
|
|
98
|
+
defaultValue !== "CURRENT_TIMESTAMP") {
|
|
99
|
+
fieldDefStr += ` DEFAULT ${typeof defaultValue === "string" ? `'${defaultValue}'` : defaultValue}`;
|
|
100
|
+
}
|
|
101
|
+
else if (defaultValue === "CURRENT_TIMESTAMP") {
|
|
102
|
+
fieldDefStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
53
103
|
}
|
|
104
|
+
fields.push(fieldDefStr);
|
|
54
105
|
}
|
|
55
106
|
}
|
|
56
|
-
// Combine fields and foreign keys
|
|
57
|
-
const allFields =
|
|
107
|
+
// Combine fields and foreign keys based on option
|
|
108
|
+
const allFields = options.includeForeignKeys
|
|
109
|
+
? [...fields, ...foreignKeys]
|
|
110
|
+
: fields;
|
|
58
111
|
// Create table SQL
|
|
59
|
-
|
|
112
|
+
if (allFields.length > 0) {
|
|
113
|
+
sql.push(`CREATE TABLE ${tableName} (\n${allFields.join(",\n")}\n);\n`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.warn(`Warning: Table ${tableName} has no fields`);
|
|
117
|
+
}
|
|
60
118
|
}
|
|
61
119
|
// Write to file
|
|
62
120
|
const sqlContent = sql.join("\n");
|
package/dist/schema.d.ts
CHANGED
|
@@ -55,12 +55,24 @@ type SQLToZodType<T extends SQLType, TDefault extends boolean> = T["pk"] extends
|
|
|
55
55
|
default: "CURRENT_TIMESTAMP";
|
|
56
56
|
} ? TDefault extends true ? never : z.ZodDate : z.ZodDate : never;
|
|
57
57
|
type ZodTypeFromPrimitive<T> = T extends string ? z.ZodString : T extends number ? z.ZodNumber : T extends boolean ? z.ZodBoolean : T extends Date ? z.ZodDate : z.ZodAny;
|
|
58
|
+
type IsLiteralType<T> = T extends string ? string extends T ? false : true : T extends number ? number extends T ? false : true : T extends boolean ? boolean extends T ? false : true : false;
|
|
58
59
|
interface IBuilderMethods<T extends SQLType | RelationConfig<any>, TSql extends z.ZodTypeAny, TNew extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> {
|
|
59
60
|
initialState: {
|
|
60
61
|
<const TResult>(defaultValue: TResult): TResult extends () => infer R ? R extends z.ZodTypeAny ? Prettify<Builder<"new", T, TSql, R, z.infer<R>, InferSmartClientType<TSql, R>, InferSmartClientType<TSql, R>>> : Prettify<Builder<"new", T, TSql, z.ZodLiteral<R>, R, z.ZodUnion<[TSql, z.ZodLiteral<R>]>, z.ZodUnion<[TSql, z.ZodLiteral<R>]>>> : TResult extends z.ZodTypeAny ? Prettify<Builder<"new", T, TSql, TResult, z.infer<TResult>, InferSmartClientType<TSql, TResult>, InferSmartClientType<TSql, TResult>>> : TResult extends string | number | boolean ? Prettify<Builder<"new", T, TSql, z.ZodLiteral<TResult>, TResult, z.ZodUnion<[TSql, z.ZodLiteral<TResult>]>, z.ZodUnion<[TSql, z.ZodLiteral<TResult>]>>> : Prettify<Builder<"new", T, TSql, ZodTypeFromPrimitive<TResult>, TResult, InferSmartClientType<TSql, ZodTypeFromPrimitive<TResult>>, InferSmartClientType<TSql, ZodTypeFromPrimitive<TResult>>>>;
|
|
61
62
|
<TNewNext extends z.ZodTypeAny, const TDefaultNext>(schema: ((tools: {
|
|
62
63
|
sql: TSql;
|
|
63
|
-
}) => TNewNext) | TNewNext, defaultValue: TDefaultNext
|
|
64
|
+
}) => TNewNext) | TNewNext, defaultValue: TDefaultNext): Prettify<Builder<"new", T, TSql, z.ZodUnion<[
|
|
65
|
+
TNewNext,
|
|
66
|
+
z.ZodLiteral<TDefaultNext extends () => infer R ? R : TDefaultNext>
|
|
67
|
+
]>, IsLiteralType<z.infer<TNewNext>> extends true ? TDefaultNext extends () => infer R ? R : TDefaultNext : z.infer<TNewNext>, z.ZodUnion<[
|
|
68
|
+
TSql,
|
|
69
|
+
TNewNext,
|
|
70
|
+
z.ZodLiteral<TDefaultNext extends () => infer R ? R : TDefaultNext>
|
|
71
|
+
]>, z.ZodUnion<[
|
|
72
|
+
TSql,
|
|
73
|
+
TNewNext,
|
|
74
|
+
z.ZodLiteral<TDefaultNext extends () => infer R ? R : TDefaultNext>
|
|
75
|
+
]>>>;
|
|
64
76
|
};
|
|
65
77
|
client: <TClientNext extends z.ZodTypeAny>(schema: ((tools: {
|
|
66
78
|
sql: TSql;
|
package/dist/schema.js
CHANGED
|
@@ -156,8 +156,12 @@ function createBuilder(config) {
|
|
|
156
156
|
: schemaOrDefault
|
|
157
157
|
: config.sqlZod; // If only a primitive is passed, the "new" schema is still the SQL one.
|
|
158
158
|
const finalDefaultValue = hasSchemaArg
|
|
159
|
-
? defaultValue
|
|
160
|
-
|
|
159
|
+
? isFunction(defaultValue)
|
|
160
|
+
? defaultValue() // If it's a function, call it
|
|
161
|
+
: defaultValue // If it's a direct value, use it as-is
|
|
162
|
+
: isFunction(schemaOrDefault)
|
|
163
|
+
? schemaOrDefault()
|
|
164
|
+
: schemaOrDefault;
|
|
161
165
|
const newCompletedStages = new Set(completedStages);
|
|
162
166
|
newCompletedStages.add("new");
|
|
163
167
|
// ---- THIS IS THE RUNTIME FIX THAT MATCHES YOUR INTERFACE ----
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.72",
|
|
4
4
|
"description": "A TypeScript library for creating type-safe database schemas with Zod validation, SQL type definitions, and automatic client/server transformations. Unifies client, server, and database types through a single schema definition, with built-in support for relationships and serialization.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|