cogsbox-shape 0.5.69 → 0.5.71
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/README.md +188 -138
- package/dist/cli.js +23 -13
- package/dist/generateSQL.d.ts +5 -4
- package/dist/generateSQL.js +78 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,184 +1,234 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cogsbox-shape
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> [!CAUTION]
|
|
4
|
+
> **This library is under active development and the API is rapidly changing. Do not use in production.**
|
|
5
|
+
>
|
|
6
|
+
> Breaking changes are expected between any release. The library is currently in an experimental phase as we work towards a stable v1.0 release.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
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.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- Single source of truth for database, server, and client types
|
|
12
|
-
- Type-safe schema definitions with TypeScript
|
|
13
|
-
- Built-in Zod validation
|
|
14
|
-
- Automatic type transformations between client and database
|
|
15
|
-
- Relationship handling (hasMany, hasOne, belongsTo)
|
|
16
|
-
- Schema serialization
|
|
17
|
-
- Default value generation
|
|
8
|
+
A TypeScript-first schema declaration and validation library for full-stack applications. Define your database schema once and get type-safe schemas for your database, client, and validation layers with automatic transformations.
|
|
18
9
|
|
|
19
10
|
## Installation
|
|
20
11
|
|
|
21
12
|
```bash
|
|
22
13
|
npm install cogsbox-shape
|
|
14
|
+
# or
|
|
15
|
+
yarn add cogsbox-shape
|
|
16
|
+
# or
|
|
17
|
+
pnpm add cogsbox-shape
|
|
23
18
|
```
|
|
24
19
|
|
|
25
|
-
##
|
|
20
|
+
## The Problem
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
import { shape, hasMany, createSchema } from "cogsbox-shape";
|
|
22
|
+
In full-stack applications, data flows through multiple layers:
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
price: shape
|
|
41
|
-
.sql({ type: "int" })
|
|
42
|
-
.client(({ sql }) => z.number().multipleOf(0.01))
|
|
43
|
-
.transform({
|
|
44
|
-
toClient: (dbValue) => dbValue / 100,
|
|
45
|
-
toDb: (clientValue) => Math.round(clientValue * 100),
|
|
46
|
-
}),
|
|
47
|
-
inStock: shape
|
|
48
|
-
.sql({ type: "boolean" })
|
|
49
|
-
.client(({ sql }) => z.boolean())
|
|
50
|
-
.initialState(z.boolean(), () => true),
|
|
51
|
-
categories: hasMany({
|
|
52
|
-
fromKey: "id",
|
|
53
|
-
toKey: () => categorySchema.productId,
|
|
54
|
-
schema: () => categorySchema,
|
|
55
|
-
}),
|
|
56
|
-
};
|
|
24
|
+
- **Database** stores data in SQL types (integers, varchars, etc.)
|
|
25
|
+
- **Server** needs to transform data for clients (e.g., convert cents to dollars)
|
|
26
|
+
- **Client** expects data in specific formats (e.g., UUIDs as strings, not numbers)
|
|
27
|
+
- **Forms** need validation rules and default values
|
|
28
|
+
|
|
29
|
+
Traditional approaches require defining these transformations in multiple places, leading to type mismatches and runtime errors.
|
|
30
|
+
|
|
31
|
+
## The Solution: The Shape Flow
|
|
32
|
+
|
|
33
|
+
cogsbox-shape introduces a unified flow that mirrors how data moves through your application:
|
|
57
34
|
|
|
58
|
-
const { sqlSchema, clientSchema, validationSchema, defaultValues } =
|
|
59
|
-
createSchema(productSchema);
|
|
60
35
|
```
|
|
36
|
+
SQL → Initial State → Client → Validation
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This flow ensures type safety at every step while giving you control over transformations.
|
|
61
40
|
|
|
62
|
-
##
|
|
41
|
+
## Core Concept: The Shape Flow
|
|
63
42
|
|
|
64
|
-
###
|
|
43
|
+
### 1. SQL - Define Your Database Schema
|
|
65
44
|
|
|
66
|
-
|
|
45
|
+
Start with your database reality:
|
|
67
46
|
|
|
68
47
|
```typescript
|
|
69
|
-
const
|
|
70
|
-
_tableName: "
|
|
71
|
-
id:
|
|
72
|
-
|
|
73
|
-
|
|
48
|
+
const userSchema = schema({
|
|
49
|
+
_tableName: "users",
|
|
50
|
+
id: s.int({ pk: true }), // In DB: integer auto-increment
|
|
51
|
+
email: s.varchar({ length: 255 }),
|
|
52
|
+
createdAt: s.datetime({ default: "CURRENT_TIMESTAMP" }),
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Initial State - Define Creation Defaults
|
|
57
|
+
|
|
58
|
+
When creating new records, you often need different types than what's stored in the database. Initial state serves two purposes: defining default values AND adding additional types to the client schema.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const userSchema = schema({
|
|
62
|
+
_tableName: "users",
|
|
63
|
+
id: s
|
|
64
|
+
.int({ pk: true })
|
|
65
|
+
.initialState(z.string().uuid(), () => crypto.randomUUID()),
|
|
66
|
+
// DB stores integers, but client can work with UUID strings for new records
|
|
67
|
+
// This automatically creates a union type: number | string on the client
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Client - Define Client Representation
|
|
72
|
+
|
|
73
|
+
Transform how data appears to clients:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const productSchema = schema({
|
|
77
|
+
_tableName: "products",
|
|
78
|
+
id: s
|
|
79
|
+
.int({ pk: true })
|
|
80
|
+
.initialState(z.string(), () => `tmp_${Date.now()}`)
|
|
74
81
|
.client(({ sql, initialState }) => z.union([sql, initialState])),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
.validation(({ sql }) =>
|
|
81
|
-
sql.refine((val) =>
|
|
82
|
-
["pending", "processing", "shipped", "delivered"].includes(val)
|
|
83
|
-
)
|
|
84
|
-
),
|
|
85
|
-
metadata: shape
|
|
86
|
-
.sql({ type: "text" })
|
|
87
|
-
.client(({ sql }) => z.record(z.unknown()))
|
|
88
|
-
.transform({
|
|
89
|
-
toClient: (value) => JSON.parse(value),
|
|
90
|
-
toDb: (value) => JSON.stringify(value),
|
|
91
|
-
}),
|
|
92
|
-
createdAt: shape
|
|
93
|
-
.sql({ type: "datetime" })
|
|
94
|
-
.client(({ sql }) => z.string().datetime())
|
|
82
|
+
// Client can receive either the integer (from DB) or string (when creating)
|
|
83
|
+
|
|
84
|
+
price: s
|
|
85
|
+
.int() // Stored as cents in DB
|
|
86
|
+
.client(() => z.number().multipleOf(0.01)) // But dollars on client
|
|
95
87
|
.transform({
|
|
96
|
-
toClient: (
|
|
97
|
-
toDb: (
|
|
88
|
+
toClient: (cents) => cents / 100,
|
|
89
|
+
toDb: (dollars) => Math.round(dollars * 100),
|
|
98
90
|
}),
|
|
99
|
-
};
|
|
91
|
+
});
|
|
100
92
|
```
|
|
101
93
|
|
|
102
|
-
###
|
|
94
|
+
### 4. Validation - Define Business Rules
|
|
103
95
|
|
|
104
|
-
|
|
96
|
+
Add validation that runs before data enters your system:
|
|
105
97
|
|
|
106
98
|
```typescript
|
|
107
|
-
const
|
|
108
|
-
_tableName: "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
primaryAddress: hasOne({
|
|
117
|
-
fromKey: "id",
|
|
118
|
-
toKey: () => addressSchema.customerId,
|
|
119
|
-
schema: () => addressSchema,
|
|
120
|
-
}),
|
|
121
|
-
company: belongsTo({
|
|
122
|
-
fromKey: "companyId",
|
|
123
|
-
toKey: () => companySchema.id,
|
|
124
|
-
schema: () => companySchema,
|
|
125
|
-
}),
|
|
126
|
-
};
|
|
99
|
+
const userSchema = schema({
|
|
100
|
+
_tableName: "users",
|
|
101
|
+
email: s
|
|
102
|
+
.varchar({ length: 255 })
|
|
103
|
+
.client(({ sql }) => sql)
|
|
104
|
+
.validation(({ client }) => client.email().toLowerCase()),
|
|
105
|
+
|
|
106
|
+
age: s.int().validation(({ sql }) => sql.min(18).max(120)),
|
|
107
|
+
});
|
|
127
108
|
```
|
|
128
109
|
|
|
129
|
-
|
|
110
|
+
## Why This Flow?
|
|
130
111
|
|
|
131
|
-
|
|
112
|
+
The flow matches how data moves through your application:
|
|
113
|
+
|
|
114
|
+
1. **SQL**: Database constraints and types
|
|
115
|
+
2. **Initial State**: What shape new records take before persistence
|
|
116
|
+
3. **Client**: How data looks in your UI/API
|
|
117
|
+
4. **Validation**: Business rules applied to user input
|
|
118
|
+
5. **Transform**: Convert between database and client representations
|
|
119
|
+
|
|
120
|
+
Each step can reference previous steps, creating a pipeline:
|
|
132
121
|
|
|
133
122
|
```typescript
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
const orderSchema = schema({
|
|
124
|
+
_tableName: "orders",
|
|
125
|
+
status: s
|
|
126
|
+
.varchar({ length: 20 })
|
|
127
|
+
// 1. SQL: Simple varchar in database
|
|
128
|
+
.initialState(z.literal("draft"), () => "draft")
|
|
129
|
+
// 2. Initial: New orders start as 'draft'
|
|
130
|
+
.client(({ sql }) => z.enum(["draft", "pending", "shipped", "delivered"]))
|
|
131
|
+
// 3. Client: Enforce enum on client
|
|
132
|
+
.validation(({ client }) => client),
|
|
133
|
+
// 4. Validation: Use same rules as client
|
|
134
|
+
|
|
135
|
+
totalPrice: s
|
|
136
|
+
.int()
|
|
137
|
+
// 1. SQL: Store as cents (integer)
|
|
138
|
+
.client(() => z.number().multipleOf(0.01))
|
|
139
|
+
// 2. Client: Work with dollars (decimal)
|
|
140
|
+
.transform({
|
|
141
|
+
toClient: (cents) => cents / 100,
|
|
142
|
+
toDb: (dollars) => Math.round(dollars * 100),
|
|
143
|
+
}),
|
|
144
|
+
// 3. Transform: Automatically convert between cents and dollars
|
|
145
|
+
});
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
|
|
148
|
+
This approach ensures type safety throughout your entire data lifecycle while keeping transformations co-located with your schema definition.
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
## Real-World Example
|
|
151
|
+
|
|
152
|
+
Here's a complete example showing the power of the flow:
|
|
146
153
|
|
|
147
154
|
```typescript
|
|
148
|
-
const userSchema = {
|
|
155
|
+
const userSchema = schema({
|
|
149
156
|
_tableName: "users",
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
|
|
157
|
+
id: s.int({ pk: true }).initialState(z.string().uuid(), () => uuidv4()),
|
|
158
|
+
|
|
159
|
+
email: s.varchar({ length: 255 }).validation(({ sql }) => sql.email()),
|
|
160
|
+
|
|
161
|
+
metadata: s
|
|
162
|
+
.text()
|
|
163
|
+
.initialState(
|
|
164
|
+
z.object({
|
|
165
|
+
preferences: z.object({
|
|
166
|
+
theme: z.enum(["light", "dark"]),
|
|
167
|
+
notifications: z.boolean(),
|
|
168
|
+
}),
|
|
169
|
+
}),
|
|
170
|
+
() => ({ preferences: { theme: "light", notifications: true } })
|
|
171
|
+
)
|
|
172
|
+
.client(({ initialState }) => initialState)
|
|
173
|
+
.transform({
|
|
174
|
+
toClient: (json) => JSON.parse(json),
|
|
175
|
+
toDb: (obj) => JSON.stringify(obj),
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const userRelations = schemaRelations(userSchema, (rel) => ({
|
|
180
|
+
posts: rel
|
|
181
|
+
.hasMany({
|
|
182
|
+
fromKey: "id",
|
|
183
|
+
toKey: () => postRelations.userId,
|
|
184
|
+
defaultCount: 0,
|
|
185
|
+
})
|
|
186
|
+
.validation(({ client }) =>
|
|
187
|
+
client.min(1, "User must have at least one post")
|
|
160
188
|
),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
// Generate schemas
|
|
192
|
+
const { sqlSchema, clientSchema, validationSchema, defaultValues } =
|
|
193
|
+
createSchema(userSchema, userRelations);
|
|
194
|
+
|
|
195
|
+
// Use in your app
|
|
196
|
+
const newUser = defaultValues; // Fully typed with defaults
|
|
197
|
+
const validated = validationSchema.parse(userInput); // Runtime validation
|
|
198
|
+
const dbUser = toDb(validated); // Transform for database
|
|
199
|
+
const apiUser = toClient(dbUser); // Transform for API
|
|
165
200
|
```
|
|
166
201
|
|
|
167
|
-
##
|
|
202
|
+
## Relationships
|
|
168
203
|
|
|
169
|
-
|
|
204
|
+
Define relationships that are type-safe across all layers:
|
|
170
205
|
|
|
171
206
|
```typescript
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
207
|
+
const messageSchema = schema({
|
|
208
|
+
_tableName: "messages",
|
|
209
|
+
id: s.int({ pk: true }).initialState(z.string(), () => uuidv4()),
|
|
210
|
+
content: s.text(),
|
|
211
|
+
timestamp: s.datetime(),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const messageRelations = schemaRelations(messageSchema, (rel) => ({
|
|
215
|
+
recipients: rel
|
|
216
|
+
.hasMany({
|
|
217
|
+
fromKey: "id",
|
|
218
|
+
toKey: () => recipientRelations.messageId,
|
|
219
|
+
})
|
|
220
|
+
.validation(({ sql }) => sql.min(1, "Must have at least one recipient")),
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
// The flow works with relationships too!
|
|
224
|
+
const { clientSchema } = createSchema(messageSchema, messageRelations);
|
|
225
|
+
type Message = z.infer<typeof clientSchema>;
|
|
226
|
+
// {
|
|
227
|
+
// id: string | number;
|
|
228
|
+
// content: string;
|
|
229
|
+
// timestamp: Date;
|
|
230
|
+
// recipients: Array<Recipient>;
|
|
231
|
+
// }
|
|
182
232
|
```
|
|
183
233
|
|
|
184
234
|
## License
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.71",
|
|
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",
|