cogsbox-shape 0.5.178 → 0.5.180
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 +114 -85
- package/dist/generateSQL.js +1 -1
- package/dist/schema.d.ts +67 -25
- package/dist/schema.js +179 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,18 +33,18 @@ Traditional approaches require defining these layers separately, leading to type
|
|
|
33
33
|
Define a field by chaining methods. Each step is optional — use only what you need.
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
s.sql() → .client() → .server() → .transform()
|
|
37
|
-
→ .derive() → .sqlOnly()
|
|
36
|
+
s.sql() → .clientInput() → .client() → .server() → .transform()
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
| Method
|
|
41
|
-
|
|
|
42
|
-
| `s.sql({ type })`
|
|
43
|
-
| `.
|
|
44
|
-
| `.
|
|
45
|
-
| `.
|
|
46
|
-
| `.
|
|
47
|
-
|
|
39
|
+
| Method | Purpose |
|
|
40
|
+
| --------------------------------- | -------------------------------------------------------------- |
|
|
41
|
+
| `s.sql({ type, sqlOnly })` | Database column type. `sqlOnly` excludes from client layer. |
|
|
42
|
+
| `.clientInput({ value, schema })` | Client-side input schema and default value for new records. |
|
|
43
|
+
| `.client(fn)` | Client-side validation on the final client union type. |
|
|
44
|
+
| `.server(fn)` | Server-side validation. Stricter rules before database writes. |
|
|
45
|
+
| `.transform({ toClient, toDb })` | Converts between database and client representations. |
|
|
46
|
+
|
|
47
|
+
Note: `.derive()` is a schema-level method, not chainable on individual fields.
|
|
48
48
|
|
|
49
49
|
### 1. SQL — Define Your Database Schema
|
|
50
50
|
|
|
@@ -63,34 +63,49 @@ const userSchema = schema({
|
|
|
63
63
|
|
|
64
64
|
This generates a Zod schema matching your SQL types exactly.
|
|
65
65
|
|
|
66
|
-
### 2. Client — Defaults and Client-Side Validation
|
|
66
|
+
### 2. Client Input — Defaults and Client-Side Validation
|
|
67
67
|
|
|
68
|
-
`.
|
|
68
|
+
`.clientInput()` sets the default value and client-side validation type for new records.
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
71
|
const userSchema = schema({
|
|
72
72
|
_tableName: "users",
|
|
73
73
|
// DB stores auto-increment integers, but new records need a temp string ID
|
|
74
|
-
id: s.sql({ type: "int", pk: true }).
|
|
74
|
+
id: s.sql({ type: "int", pk: true }).clientInput({
|
|
75
75
|
value: () => crypto.randomUUID(),
|
|
76
76
|
schema: z.string(),
|
|
77
77
|
}),
|
|
78
|
-
//
|
|
78
|
+
// clientInput type: string (just the user's schema)
|
|
79
79
|
// Default value: a generated UUID string
|
|
80
80
|
|
|
81
81
|
// Simple default without type override
|
|
82
|
-
name: s.sql({ type: "varchar" }).
|
|
83
|
-
//
|
|
82
|
+
name: s.sql({ type: "varchar" }).clientInput({ value: "Anonymous" }),
|
|
83
|
+
// clientInput type: string (inherits from SQL)
|
|
84
84
|
// Default value: "Anonymous"
|
|
85
85
|
|
|
86
86
|
// Type-only override (no default value change)
|
|
87
|
-
count: s.sql({ type: "int" }).
|
|
88
|
-
//
|
|
87
|
+
count: s.sql({ type: "int" }).clientInput(() => z.number().min(0)),
|
|
88
|
+
// clientInput type: number (with min validation)
|
|
89
89
|
// Default value: inferred from type (0 for number)
|
|
90
90
|
});
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
**Note:** The final `client` schema is a union of `sql | clientInput` types, representing the complete app state after transforms.
|
|
94
|
+
|
|
95
|
+
### 3. Client — Client-Side Validation
|
|
96
|
+
|
|
97
|
+
`.client()` adds validation rules to the final `client` schema (the union of sql | clientInput). Use it for client-side validation that operates on the complete client type.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
name: s.sql({ type: "varchar" })
|
|
101
|
+
.clientInput({ value: "" })
|
|
102
|
+
.client((tools) => tools.clientInput.min(3, "Too short"))
|
|
103
|
+
.server((tools) => tools.clientInput.min(5, "Must be at least 5 chars")),
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The `.client()` callback receives `tools` with `sql`, `clientInput`, and `client` schemas.
|
|
107
|
+
|
|
108
|
+
### 4. Server — Server-Side Validation
|
|
94
109
|
|
|
95
110
|
`.server()` adds validation rules that run at the server boundary before database writes. It builds on the client schema, adding stricter constraints.
|
|
96
111
|
|
|
@@ -112,18 +127,18 @@ The callback receives the previous schema in the chain so you can refine it:
|
|
|
112
127
|
```typescript
|
|
113
128
|
name: s
|
|
114
129
|
.sql({ type: "varchar" })
|
|
115
|
-
.
|
|
116
|
-
.server(({
|
|
130
|
+
.clientInput(() => z.string().trim())
|
|
131
|
+
.server(({ clientInput }) => clientInput.min(2, "Too short")),
|
|
117
132
|
```
|
|
118
133
|
|
|
119
|
-
###
|
|
134
|
+
### 5. Transform — Convert Between Layers
|
|
120
135
|
|
|
121
136
|
`.transform()` defines bidirectional conversion functions. These run on the server when reading from or writing to the database.
|
|
122
137
|
|
|
123
138
|
```typescript
|
|
124
139
|
status: s
|
|
125
140
|
.sql({ type: "int" }) // DB: 0 or 1
|
|
126
|
-
.
|
|
141
|
+
.clientInput(() => z.enum(["active", "inactive"])) // Client input: string enum
|
|
127
142
|
.transform({
|
|
128
143
|
toClient: (dbValue) => dbValue === 1 ? "active" : "inactive",
|
|
129
144
|
toDb: (clientValue) => clientValue === "active" ? 1 : 0,
|
|
@@ -132,37 +147,13 @@ status: s
|
|
|
132
147
|
|
|
133
148
|
Transforms are optional — only needed when the client type differs from the SQL type.
|
|
134
149
|
|
|
135
|
-
### 6.
|
|
150
|
+
### 6. Layer Separation: DB-Only, Client-Only, and Derived Fields
|
|
136
151
|
|
|
137
|
-
|
|
138
|
-
- Available in client schema and defaults
|
|
139
|
-
- NOT stored in the database (computed at runtime)
|
|
140
|
-
- Useful for display-only fields, combinations, or formatted values
|
|
152
|
+
`cogsbox-shape` lets you explicitly define fields that only exist in specific layers, or dynamically compute them.
|
|
141
153
|
|
|
142
|
-
|
|
143
|
-
const userSchema = schema({
|
|
144
|
-
_tableName: "users",
|
|
145
|
-
firstName: s.sql({ type: "varchar" }).client({ value: "John" }),
|
|
146
|
-
lastName: s.sql({ type: "varchar" }).client({ value: "Doe" }),
|
|
147
|
-
// Define placeholder for derived field
|
|
148
|
-
fullName: s.client(""), // Required: tells schema this field exists on client
|
|
149
|
-
}).derive({
|
|
150
|
-
fullName: (row) => `${row.firstName} ${row.lastName}`,
|
|
151
|
-
});
|
|
154
|
+
#### DB-Only Fields (`sqlOnly: true`)
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
const defaults = box.users.defaults;
|
|
155
|
-
// { firstName: "John", lastName: "Doe", fullName: "John Doe" }
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
The derived field's default value is computed from other defaults at initialization time.
|
|
159
|
-
|
|
160
|
-
### 7. sqlOnly — Server-Only Fields
|
|
161
|
-
|
|
162
|
-
`.sql({ sqlOnly: true })` marks a field as server-only:
|
|
163
|
-
- Included in SQL schema (stored in DB)
|
|
164
|
-
- Excluded from client schema (not sent to client)
|
|
165
|
-
- Useful for internal tokens, computed scores, or sensitive data
|
|
156
|
+
Use `sqlOnly: true` to define fields that belong to the database exclusively (like internal tokens). They are saved in the DB, but dropped before data reaches the client.
|
|
166
157
|
|
|
167
158
|
```typescript
|
|
168
159
|
const userSchema = schema({
|
|
@@ -170,27 +161,41 @@ const userSchema = schema({
|
|
|
170
161
|
id: s.sql({ type: "int", pk: true }),
|
|
171
162
|
email: s.sql({ type: "varchar" }),
|
|
172
163
|
internalToken: s.sql({ type: "varchar", sqlOnly: true }),
|
|
173
|
-
trustScore: s.sql({ type: "int", sqlOnly: true }),
|
|
174
164
|
});
|
|
165
|
+
// DB reads/writes: { id, email, internalToken }
|
|
166
|
+
// Client sees: { id, email }
|
|
167
|
+
```
|
|
175
168
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
#### Client-Only Fields
|
|
170
|
+
|
|
171
|
+
By skipping `s.sql()` entirely and just using `s.clientInput()`, you can define fields that exist purely on the client (like a temporary UI state or computed field) and will not be sent to the database.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const products = schema({
|
|
175
|
+
_tableName: "products",
|
|
176
|
+
price: s.sql({ type: "int" }),
|
|
177
|
+
formattedPrice: s.clientInput(""), // Client-only field!
|
|
178
|
+
});
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
#### Derived Fields (`.derive()`)
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
`.derive()` populates _existing fields_ dynamically on read and default generation. Because you define the fields first, derived fields can be either standard DB fields or Client-only fields.
|
|
184
184
|
|
|
185
185
|
```typescript
|
|
186
|
-
const
|
|
187
|
-
_tableName: "
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
const users = schema({
|
|
187
|
+
_tableName: "users",
|
|
188
|
+
firstName: s.sql({ type: "varchar" }).clientInput({ value: "John" }),
|
|
189
|
+
lastName: s.sql({ type: "varchar" }).clientInput({ value: "Doe" }),
|
|
190
|
+
|
|
191
|
+
// 1. Defined purely as a client field
|
|
192
|
+
fullName: s.clientInput(""),
|
|
193
|
+
// 2. Defined as a DB field
|
|
194
|
+
searchIndex: s.sql({ type: "varchar" }),
|
|
192
195
|
}).derive({
|
|
193
|
-
|
|
196
|
+
// Computes on toClient() and generateDefaults()
|
|
197
|
+
fullName: (row) => `${row.firstName} ${row.lastName}`,
|
|
198
|
+
searchIndex: (row) => `${row.firstName} ${row.lastName}`.toLowerCase(),
|
|
194
199
|
});
|
|
195
200
|
```
|
|
196
201
|
|
|
@@ -201,13 +206,13 @@ The returned schema object has a clear separation of concerns:
|
|
|
201
206
|
```typescript
|
|
202
207
|
const schema = createSchema(mySchema);
|
|
203
208
|
|
|
204
|
-
schema.schemas;
|
|
205
|
-
schema.transforms;
|
|
206
|
-
schema.defaults;
|
|
209
|
+
schema.schemas; // { sql, clientInput, client, server } — Zod schemas
|
|
210
|
+
schema.transforms; // { toClient, toDb, parseForDb, parseFromDb } — transformations
|
|
211
|
+
schema.defaults; // Default values for forms
|
|
207
212
|
schema.generateDefaults; // Function to generate fresh defaults (executes randomizers)
|
|
208
|
-
schema.pk;
|
|
209
|
-
schema.clientPk;
|
|
210
|
-
schema.isClientRecord;
|
|
213
|
+
schema.pk; // Primary key field names
|
|
214
|
+
schema.clientPk; // Client-side primary key field names
|
|
215
|
+
schema.isClientRecord; // Function to check if a record is client-created
|
|
211
216
|
```
|
|
212
217
|
|
|
213
218
|
## Using Schemas
|
|
@@ -221,17 +226,17 @@ import { s, schema, createSchema } from "cogsbox-shape";
|
|
|
221
226
|
|
|
222
227
|
const contactSchema = schema({
|
|
223
228
|
_tableName: "contacts",
|
|
224
|
-
id: s.sql({ type: "int", pk: true }).
|
|
229
|
+
id: s.sql({ type: "int", pk: true }).clientInput({
|
|
225
230
|
value: () => `new_${crypto.randomUUID().slice(0, 8)}`,
|
|
226
231
|
schema: z.string(),
|
|
227
232
|
}),
|
|
228
233
|
name: s.sql({ type: "varchar" }).server(({ sql }) => sql.min(2)),
|
|
229
234
|
email: s.sql({ type: "varchar" }).server(({ sql }) => sql.email()),
|
|
230
|
-
|
|
231
|
-
.sql({ type: "
|
|
232
|
-
.
|
|
235
|
+
isActive: s
|
|
236
|
+
.sql({ type: "boolean", default: true })
|
|
237
|
+
.clientInput(() => z.boolean())
|
|
233
238
|
.transform({
|
|
234
|
-
toClient: (val) => val
|
|
239
|
+
toClient: (val) => Boolean(val),
|
|
235
240
|
toDb: (val) => (val ? 1 : 0),
|
|
236
241
|
}),
|
|
237
242
|
});
|
|
@@ -239,22 +244,22 @@ const contactSchema = schema({
|
|
|
239
244
|
const schema = createSchema(contactSchema);
|
|
240
245
|
|
|
241
246
|
// Access schemas directly
|
|
242
|
-
const {
|
|
243
|
-
const {
|
|
247
|
+
const { sql, clientInput, client, server } = schema.schemas;
|
|
248
|
+
const { defaults, generateDefaults } = schema;
|
|
244
249
|
|
|
245
250
|
// Transforms for converting between layers
|
|
246
251
|
const { toClient, toDb, parseForDb, parseFromDb } = schema.transforms;
|
|
247
252
|
|
|
248
253
|
// Use in a form
|
|
249
254
|
const [data, setData] = useState(generateDefaults());
|
|
250
|
-
// { id: "new_a1b2c3d4", name: "", email: "",
|
|
255
|
+
// { id: "new_a1b2c3d4", name: "", email: "", isActive: true }
|
|
251
256
|
|
|
252
257
|
// Validate explicitly
|
|
253
|
-
const result =
|
|
258
|
+
const result = server.safeParse(data);
|
|
254
259
|
|
|
255
260
|
// Or handle validation & transformation in a single step!
|
|
256
261
|
const safeDbRow = parseForDb(data);
|
|
257
|
-
// Validates using
|
|
262
|
+
// Validates using server schema, outputs { isActive: 1, ... }
|
|
258
263
|
```
|
|
259
264
|
|
|
260
265
|
## Relationships and Views
|
|
@@ -286,11 +291,14 @@ const posts = schema({
|
|
|
286
291
|
The `createSchemaBox` function resolves relationships and gives you a type-safe API:
|
|
287
292
|
|
|
288
293
|
```typescript
|
|
289
|
-
const box = createSchemaBox(
|
|
290
|
-
users
|
|
291
|
-
|
|
294
|
+
const box = createSchemaBox(
|
|
295
|
+
{ users, posts },
|
|
296
|
+
{
|
|
297
|
+
users: {
|
|
298
|
+
posts: { fromKey: "id", toKey: posts.authorId },
|
|
299
|
+
},
|
|
292
300
|
},
|
|
293
|
-
|
|
301
|
+
);
|
|
294
302
|
```
|
|
295
303
|
|
|
296
304
|
### 3. Access Base Schemas
|
|
@@ -331,6 +339,27 @@ type UserWithPosts = z.infer<typeof userWithPosts.schemas.client>;
|
|
|
331
339
|
|
|
332
340
|
// Views also have transforms for the selected fields
|
|
333
341
|
const { defaults, transforms } = userWithPosts;
|
|
334
|
-
// transforms.
|
|
342
|
+
// transforms.toClient() handles nested relation transforms automatically
|
|
335
343
|
```
|
|
344
|
+
|
|
345
|
+
### 5. Nested Defaults and Form Definitions (`defaultsDefinition`)
|
|
346
|
+
|
|
347
|
+
When working with forms and nested array relations (like `hasMany`), you often need the default state for a _single new item_ to add to a form array.
|
|
348
|
+
|
|
349
|
+
While `view.defaults` gives you the actual runtime defaults (e.g., an array of 2 default posts if you defined `count: 2`), `view.defaultsDefinition` provides an easy way to grab the structure of a _single element_ using the `__def__relationName` key:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const userView = box.users.createView({
|
|
353
|
+
posts: { user: true },
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Actual runtime defaults (an array)
|
|
357
|
+
console.log(userView.defaults.posts);
|
|
358
|
+
// => [{ title: "Default Post", user: { ... } }, { title: "Default Post", user: { ... } }]
|
|
359
|
+
|
|
360
|
+
// Structural definition of a single item for adding to forms
|
|
361
|
+
console.log(userView.defaultsDefinition.__def__posts);
|
|
362
|
+
// => { title: "Default Post", user: { ... } }
|
|
336
363
|
```
|
|
364
|
+
|
|
365
|
+
This makes it incredibly simple to implement "Add Item" buttons in complex nested forms without having to manually construct or guess the default object shape.
|
package/dist/generateSQL.js
CHANGED
package/dist/schema.d.ts
CHANGED
|
@@ -35,52 +35,57 @@ type BaseConfig = {
|
|
|
35
35
|
field?: string;
|
|
36
36
|
sqlOnly?: true;
|
|
37
37
|
};
|
|
38
|
-
type SQLToZodType<T extends SQLType, TDefault extends boolean> = T["pk"] extends true ? TDefault extends true ? z.ZodString : z.ZodNumber : T["nullable"] extends true ? T["type"] extends "varchar" | "char" | "text" | "longtext" ? z.ZodNullable<z.ZodString> : T["type"] extends "int" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "boolean" ? z.ZodNullable<z.
|
|
38
|
+
type SQLToZodType<T extends SQLType, TDefault extends boolean> = T["pk"] extends true ? TDefault extends true ? z.ZodString : z.ZodNumber : T["nullable"] extends true ? T["type"] extends "varchar" | "char" | "text" | "longtext" ? z.ZodNullable<z.ZodString> : T["type"] extends "int" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "boolean" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
|
|
39
39
|
default: "CURRENT_TIMESTAMP";
|
|
40
|
-
} ? TDefault extends true ? never : z.ZodNullable<z.ZodDate> : z.ZodNullable<z.ZodDate> : never : T["type"] extends "varchar" | "char" | "text" | "longtext" ? z.ZodString : T["type"] extends "int" ? z.ZodNumber : T["type"] extends "boolean" ? z.
|
|
40
|
+
} ? TDefault extends true ? never : z.ZodNullable<z.ZodDate> : z.ZodNullable<z.ZodDate> : never : T["type"] extends "varchar" | "char" | "text" | "longtext" ? z.ZodString : T["type"] extends "int" ? z.ZodNumber : T["type"] extends "boolean" ? z.ZodNumber : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
|
|
41
41
|
default: "CURRENT_TIMESTAMP";
|
|
42
42
|
} ? TDefault extends true ? never : z.ZodDate : z.ZodDate : never;
|
|
43
43
|
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;
|
|
44
44
|
type CollapsedUnion<A extends z.ZodTypeAny, B extends z.ZodTypeAny> = A extends B ? (B extends A ? A : z.ZodUnion<[A, B]>) : z.ZodUnion<[A, B]>;
|
|
45
45
|
export interface IBuilderMethods<T extends DbConfig, TSql extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> {
|
|
46
|
-
|
|
46
|
+
clientInput<const TValue>(options: {
|
|
47
47
|
value: TValue | ((tools: {
|
|
48
48
|
uuid: () => string;
|
|
49
49
|
}) => TValue);
|
|
50
50
|
schema?: never;
|
|
51
51
|
clientPk?: boolean | ((val: any) => boolean);
|
|
52
|
-
}): Prettify<Builder<"
|
|
53
|
-
|
|
52
|
+
}): Prettify<Builder<"clientInput", T, TSql, TValue extends () => infer R ? R : TValue, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>>>;
|
|
53
|
+
clientInput<const TSchema extends z.ZodTypeAny>(options: {
|
|
54
54
|
value?: never;
|
|
55
55
|
schema: TSchema;
|
|
56
56
|
clientPk?: boolean | ((val: any) => boolean);
|
|
57
|
-
}): Prettify<Builder<"
|
|
58
|
-
|
|
57
|
+
}): Prettify<Builder<"clientInput", T, TSql, z.infer<TSchema>, TSchema, CollapsedUnion<TSql, TSchema>>>;
|
|
58
|
+
clientInput<const TSchema extends z.ZodTypeAny>(options: {
|
|
59
59
|
value?: never;
|
|
60
60
|
schema: TSchema | ((tools: any) => TSchema);
|
|
61
61
|
clientPk?: boolean | ((val: any) => boolean);
|
|
62
|
-
}): Prettify<Builder<"
|
|
63
|
-
|
|
62
|
+
}): Prettify<Builder<"clientInput", T, TSql, z.infer<TSchema>, TSchema, CollapsedUnion<TSql, TSchema>>>;
|
|
63
|
+
clientInput<const TValue>(options: {
|
|
64
64
|
value: TValue | ((tools: {
|
|
65
65
|
uuid: () => string;
|
|
66
66
|
}) => TValue);
|
|
67
67
|
schema?: never;
|
|
68
68
|
clientPk?: boolean | ((val: any) => boolean);
|
|
69
|
-
}): Prettify<Builder<"
|
|
70
|
-
|
|
69
|
+
}): Prettify<Builder<"clientInput", T, TSql, TValue extends () => infer R ? R : TValue, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>>>;
|
|
70
|
+
clientInput(options: {
|
|
71
71
|
value?: never;
|
|
72
72
|
schema: (tools: any) => z.ZodTypeAny;
|
|
73
|
-
}): Prettify<Builder<"
|
|
74
|
-
|
|
73
|
+
}): Prettify<Builder<"clientInput", T, TSql, unknown, z.ZodTypeAny, z.ZodTypeAny>>;
|
|
74
|
+
clientInput<const TValue, const TSchema extends z.ZodTypeAny>(options: {
|
|
75
75
|
value: TValue | ((tools: {
|
|
76
76
|
uuid: () => string;
|
|
77
77
|
}) => TValue);
|
|
78
78
|
schema: TSchema | ((base: ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>) => TSchema);
|
|
79
79
|
clientPk?: boolean | ((val: any) => boolean);
|
|
80
|
-
}): Prettify<Builder<"
|
|
81
|
-
|
|
80
|
+
}): Prettify<Builder<"clientInput", T, TSql, TValue extends () => infer R ? R : TValue, TSchema, CollapsedUnion<TSql, TSchema>>>;
|
|
81
|
+
clientInput<TClientNext extends z.ZodTypeAny>(schema: ((tools: {
|
|
82
82
|
sql: TSql;
|
|
83
|
-
}) => TClientNext) | TClientNext): Prettify<Builder<"
|
|
83
|
+
}) => TClientNext) | TClientNext): Prettify<Builder<"clientInput", T, TSql, z.infer<TClientNext>, TClientNext, CollapsedUnion<TSql, TClientNext>>>;
|
|
84
|
+
client: <TClientNext extends z.ZodTypeAny>(schema: ((tools: {
|
|
85
|
+
sql: TSql;
|
|
86
|
+
clientInput: TClient;
|
|
87
|
+
client: z.ZodUnion<[TSql, TClient]>;
|
|
88
|
+
}) => TClientNext) | TClientNext) => Prettify<Builder<"client", T, TSql, TInitialValue, TClient, z.ZodUnion<[TSql, TClientNext]>>>;
|
|
84
89
|
reference: <TRefSchema extends {
|
|
85
90
|
_tableName: string;
|
|
86
91
|
}>(fieldGetter: () => any) => Builder<"sql", T & {
|
|
@@ -88,7 +93,8 @@ export interface IBuilderMethods<T extends DbConfig, TSql extends z.ZodTypeAny,
|
|
|
88
93
|
}, TSql, TInitialValue, TClient, TValidation>;
|
|
89
94
|
server: <TValidationNext extends z.ZodTypeAny>(schema: ((tools: {
|
|
90
95
|
sql: TSql;
|
|
91
|
-
|
|
96
|
+
clientInput: TClient;
|
|
97
|
+
client: z.ZodUnion<[TSql, TClient]>;
|
|
92
98
|
}) => TValidationNext) | TValidationNext) => Prettify<Builder<"server", T, TSql, TInitialValue, TClient, TValidationNext>>;
|
|
93
99
|
transform: (transforms: {
|
|
94
100
|
toClient: (dbValue: z.infer<TSql>) => z.infer<TClient>;
|
|
@@ -114,10 +120,11 @@ export type RelationConfig<T extends Schema<any>> = (BaseRelationConfig<T> & {
|
|
|
114
120
|
}) | (BaseRelationConfig<T> & {
|
|
115
121
|
type: "manyToMany";
|
|
116
122
|
});
|
|
117
|
-
type Stage = "sql" | "relation" | "client" | "server" | "done";
|
|
123
|
+
type Stage = "sql" | "relation" | "clientInput" | "client" | "server" | "done";
|
|
118
124
|
type StageMethods = {
|
|
119
|
-
sql: "client" | "server" | "transform" | "reference";
|
|
120
|
-
relation: "client" | "server" | "transform";
|
|
125
|
+
sql: "clientInput" | "client" | "server" | "transform" | "reference";
|
|
126
|
+
relation: "clientInput" | "client" | "server" | "transform";
|
|
127
|
+
clientInput: "client" | "server" | "transform";
|
|
121
128
|
client: "server" | "transform";
|
|
122
129
|
server: "transform";
|
|
123
130
|
done: never;
|
|
@@ -126,6 +133,7 @@ type BuilderConfig<T extends DbConfig, TSql extends z.ZodTypeAny, TInitialValue,
|
|
|
126
133
|
sql: T;
|
|
127
134
|
zodSqlSchema: TSql;
|
|
128
135
|
initialValue: TInitialValue;
|
|
136
|
+
zodClientInputSchema: TClient;
|
|
129
137
|
zodClientSchema: TClient;
|
|
130
138
|
zodValidationSchema: TValidation;
|
|
131
139
|
clientTransform?: (schema: z.ZodTypeAny) => z.ZodTypeAny;
|
|
@@ -136,7 +144,8 @@ export type Builder<TStage extends Stage, T extends DbConfig, TSql extends z.Zod
|
|
|
136
144
|
sql: T;
|
|
137
145
|
zodSqlSchema: TSql;
|
|
138
146
|
initialValue: TInitialValue;
|
|
139
|
-
|
|
147
|
+
zodClientInputSchema: TClient;
|
|
148
|
+
zodClientSchema: TValidation;
|
|
140
149
|
zodValidationSchema: TValidation;
|
|
141
150
|
};
|
|
142
151
|
} & Pick<IBuilderMethods<T, TSql, TInitialValue, TClient, TValidation>, StageMethods[TStage]>;
|
|
@@ -149,10 +158,9 @@ export type Reference<TGetter extends () => any> = {
|
|
|
149
158
|
getter: TGetter;
|
|
150
159
|
};
|
|
151
160
|
interface ShapeAPI {
|
|
152
|
-
|
|
161
|
+
clientInput: <const TValue>(value: TValue | ((tools: {
|
|
153
162
|
uuid: () => string;
|
|
154
|
-
}) => TValue)) => Builder<"
|
|
155
|
-
TValue extends () => infer R ? R : TValue, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>;
|
|
163
|
+
}) => TValue)) => Builder<"clientInput", null, z.ZodUndefined, TValue extends () => infer R ? R : TValue, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>;
|
|
156
164
|
sql: <const T extends SQLType>(sqlConfig: T) => Builder<"sql", T, SQLToZodType<T, false>, z.infer<SQLToZodType<T, false>>, SQLToZodType<T, false>, SQLToZodType<T, false>>;
|
|
157
165
|
reference: <TGetter extends () => any>(getter: TGetter) => Reference<TGetter>;
|
|
158
166
|
hasMany: <T extends HasManyDefault>(config?: T) => PlaceholderRelation<"hasMany">;
|
|
@@ -228,6 +236,7 @@ export declare function createSchema<T extends {
|
|
|
228
236
|
clientPk: string[] | null;
|
|
229
237
|
isClientRecord: (record: any) => boolean;
|
|
230
238
|
sqlSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodSqlSchema">>>;
|
|
239
|
+
clientInputSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodClientInputSchema">>>;
|
|
231
240
|
clientSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodClientSchema">>>;
|
|
232
241
|
serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodValidationSchema">>>;
|
|
233
242
|
defaultValues: Prettify<DeriveDefaults<TActualSchema>>;
|
|
@@ -290,6 +299,7 @@ type ResolvedRegistryWithSchemas<S extends Record<string, SchemaWithPlaceholders
|
|
|
290
299
|
rawSchema: ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>;
|
|
291
300
|
zodSchemas: {
|
|
292
301
|
sqlSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodSqlSchema">>>;
|
|
302
|
+
clientInputSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodClientInputSchema">>>;
|
|
293
303
|
clientSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodClientSchema">>>;
|
|
294
304
|
serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodValidationSchema">>>;
|
|
295
305
|
defaultValues: Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
|
|
@@ -361,6 +371,34 @@ type DeriveViewDefaults<TTableName extends keyof TRegistry, TSelection, TRegistr
|
|
|
361
371
|
1
|
|
362
372
|
]> | null : never : never : never;
|
|
363
373
|
} : {})>;
|
|
374
|
+
export type DeriveViewDefaultsDefinition<TTableName extends keyof TRegistry, TSelection, TRegistry extends RegistryShape, Depth extends any[] = []> = Depth["length"] extends 10 ? any : Prettify<TRegistry[TTableName]["zodSchemas"]["defaultValues"] & {
|
|
375
|
+
[K in keyof TRegistry[TTableName]["rawSchema"] as IsRelationField<TRegistry[TTableName]["rawSchema"][K]> extends true ? K : never]: TRegistry[TTableName]["rawSchema"][K] extends {
|
|
376
|
+
config: {
|
|
377
|
+
sql: {
|
|
378
|
+
type: infer RelType;
|
|
379
|
+
schema: any;
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
} ? GetRelationRegistryKey<TRegistry[TTableName]["rawSchema"][K], TRegistry> extends infer TargetKey ? TargetKey extends keyof TRegistry ? K extends keyof TSelection ? TSelection[K] extends true ? RelType extends "hasMany" | "manyToMany" ? TRegistry[TargetKey]["zodSchemas"]["defaultValues"][] : TRegistry[TargetKey]["zodSchemas"]["defaultValues"] | null : TSelection[K] extends false | undefined ? RelType extends "hasMany" | "manyToMany" ? TRegistry[TargetKey]["zodSchemas"]["defaultValues"][] : TRegistry[TargetKey]["zodSchemas"]["defaultValues"] | null : RelType extends "hasMany" | "manyToMany" ? DeriveViewDefaultsDefinition<TargetKey, TSelection[K], TRegistry, [
|
|
383
|
+
...Depth,
|
|
384
|
+
1
|
|
385
|
+
]>[] : DeriveViewDefaultsDefinition<TargetKey, TSelection[K], TRegistry, [
|
|
386
|
+
...Depth,
|
|
387
|
+
1
|
|
388
|
+
]> | null : RelType extends "hasMany" | "manyToMany" ? TRegistry[TargetKey]["zodSchemas"]["defaultValues"][] : TRegistry[TargetKey]["zodSchemas"]["defaultValues"] | null : never : never : never;
|
|
389
|
+
} & {
|
|
390
|
+
[K in keyof TRegistry[TTableName]["rawSchema"] as IsRelationField<TRegistry[TTableName]["rawSchema"][K]> extends true ? `__def__${K & string}` : never]: TRegistry[TTableName]["rawSchema"][K] extends {
|
|
391
|
+
config: {
|
|
392
|
+
sql: {
|
|
393
|
+
type: any;
|
|
394
|
+
schema: any;
|
|
395
|
+
};
|
|
396
|
+
};
|
|
397
|
+
} ? GetRelationRegistryKey<TRegistry[TTableName]["rawSchema"][K], TRegistry> extends infer TargetKey ? TargetKey extends keyof TRegistry ? K extends keyof TSelection ? TSelection[K] extends true ? TRegistry[TargetKey]["zodSchemas"]["defaultValues"] : TSelection[K] extends false | undefined ? TRegistry[TargetKey]["zodSchemas"]["defaultValues"] : DeriveViewDefaultsDefinition<TargetKey, TSelection[K], TRegistry, [
|
|
398
|
+
...Depth,
|
|
399
|
+
1
|
|
400
|
+
]> : TRegistry[TargetKey]["zodSchemas"]["defaultValues"] : never : never : never;
|
|
401
|
+
}>;
|
|
364
402
|
export type DeriveViewResult<TTableName extends keyof TRegistry, TSelection, TRegistry extends RegistryShape> = {
|
|
365
403
|
definition: TRegistry[TTableName]["rawSchema"];
|
|
366
404
|
schemaKey: TTableName;
|
|
@@ -376,6 +414,7 @@ export type DeriveViewResult<TTableName extends keyof TRegistry, TSelection, TRe
|
|
|
376
414
|
parseFromDb: (dbData: Partial<z.infer<z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "sqlSchema">>>>) => z.infer<z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "clientSchema">>>;
|
|
377
415
|
};
|
|
378
416
|
defaults: DeriveViewDefaults<TTableName, TSelection, TRegistry>;
|
|
417
|
+
defaultsDefinition: DeriveViewDefaultsDefinition<TTableName, TSelection, TRegistry>;
|
|
379
418
|
pk: string[] | null;
|
|
380
419
|
clientPk: string[] | null;
|
|
381
420
|
supportsReconciliation: boolean;
|
|
@@ -410,6 +449,7 @@ type RegistryShape = Record<string, {
|
|
|
410
449
|
rawSchema: any;
|
|
411
450
|
zodSchemas: {
|
|
412
451
|
sqlSchema: z.ZodObject<any>;
|
|
452
|
+
clientInputSchema: z.ZodObject<any>;
|
|
413
453
|
clientSchema: z.ZodObject<any>;
|
|
414
454
|
serverSchema: z.ZodObject<any>;
|
|
415
455
|
defaultValues: any;
|
|
@@ -432,6 +472,7 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
|
|
|
432
472
|
schemaKey: K;
|
|
433
473
|
schemas: {
|
|
434
474
|
sql: Resolved[K]["zodSchemas"]["sqlSchema"];
|
|
475
|
+
clientInput: Resolved[K]["zodSchemas"]["clientInputSchema"];
|
|
435
476
|
client: Resolved[K]["zodSchemas"]["clientSchema"];
|
|
436
477
|
server: Resolved[K]["zodSchemas"]["serverSchema"];
|
|
437
478
|
};
|
|
@@ -442,6 +483,7 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
|
|
|
442
483
|
parseFromDb: (dbData: Partial<z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>>) => z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>;
|
|
443
484
|
};
|
|
444
485
|
defaults: Resolved[K]["zodSchemas"]["defaultValues"];
|
|
486
|
+
defaultsDefinition: DeriveViewDefaultsDefinition<K & string, {}, Resolved>;
|
|
445
487
|
stateType: Resolved[K]["zodSchemas"]["stateType"];
|
|
446
488
|
generateDefaults: () => Resolved[K]["zodSchemas"]["defaultValues"];
|
|
447
489
|
pk: string[] | null;
|
|
@@ -470,7 +512,7 @@ type GetDbKey<K, Field> = Field extends Reference<infer TGetter> ? ReturnType<TG
|
|
|
470
512
|
};
|
|
471
513
|
};
|
|
472
514
|
} ? string extends F ? K : F : K;
|
|
473
|
-
type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientSchema" | "zodValidationSchema", Depth extends any[] = []> = Depth["length"] extends 10 ? any : {
|
|
515
|
+
type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientInputSchema" | "zodClientSchema" | "zodValidationSchema", Depth extends any[] = []> = Depth["length"] extends 10 ? any : {
|
|
474
516
|
[K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" ? never : K extends keyof T ? T[K] extends {
|
|
475
517
|
config: {
|
|
476
518
|
sql: {
|
package/dist/schema.js
CHANGED
|
@@ -8,7 +8,7 @@ export function currentTimeStamp() {
|
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
10
|
export const s = {
|
|
11
|
-
|
|
11
|
+
clientInput: (value) => {
|
|
12
12
|
const actualValue = isFunction(value) ? value({ uuid }) : value;
|
|
13
13
|
let inferredZodType;
|
|
14
14
|
if (typeof actualValue === "string") {
|
|
@@ -30,7 +30,7 @@ export const s = {
|
|
|
30
30
|
inferredZodType = z.any();
|
|
31
31
|
}
|
|
32
32
|
return createBuilder({
|
|
33
|
-
stage: "
|
|
33
|
+
stage: "clientInput",
|
|
34
34
|
sqlConfig: null,
|
|
35
35
|
sqlZod: z.undefined(),
|
|
36
36
|
initialValue: actualValue,
|
|
@@ -107,6 +107,7 @@ function createBuilder(config) {
|
|
|
107
107
|
zodSqlSchema: config.sqlZod,
|
|
108
108
|
initialValue: config.initialValue ||
|
|
109
109
|
inferDefaultFromZod(config.clientZod, config.sqlConfig),
|
|
110
|
+
zodClientInputSchema: config.clientInputZod || config.clientZod,
|
|
110
111
|
zodClientSchema: config.clientZod,
|
|
111
112
|
zodValidationSchema: config.validationZod,
|
|
112
113
|
clientTransform: config.clientTransform,
|
|
@@ -121,15 +122,15 @@ function createBuilder(config) {
|
|
|
121
122
|
},
|
|
122
123
|
});
|
|
123
124
|
},
|
|
124
|
-
|
|
125
|
-
if (completedStages.has("
|
|
126
|
-
throw new Error("
|
|
125
|
+
clientInput: (...args) => {
|
|
126
|
+
if (completedStages.has("clientInput")) {
|
|
127
|
+
throw new Error("clientInput() can only be called once in the chain");
|
|
127
128
|
}
|
|
128
129
|
if (completedStages.has("server")) {
|
|
129
|
-
throw new Error("
|
|
130
|
+
throw new Error("clientInput() must be called before server()");
|
|
130
131
|
}
|
|
131
132
|
const newCompletedStages = new Set(completedStages);
|
|
132
|
-
newCompletedStages.add("
|
|
133
|
+
newCompletedStages.add("clientInput");
|
|
133
134
|
let optionsOrSchema = args[0];
|
|
134
135
|
if (config.stage === "relation") {
|
|
135
136
|
const assert = typeof optionsOrSchema === "function" ||
|
|
@@ -138,8 +139,10 @@ function createBuilder(config) {
|
|
|
138
139
|
: optionsOrSchema?.schema;
|
|
139
140
|
return createBuilder({
|
|
140
141
|
...config,
|
|
141
|
-
stage: "
|
|
142
|
+
stage: "clientInput",
|
|
142
143
|
completedStages: newCompletedStages,
|
|
144
|
+
clientZod: assert,
|
|
145
|
+
clientInputZod: assert,
|
|
143
146
|
clientTransform: (baseSchema) => {
|
|
144
147
|
if (isFunction(assert)) {
|
|
145
148
|
return assert({ sql: baseSchema });
|
|
@@ -254,14 +257,40 @@ function createBuilder(config) {
|
|
|
254
257
|
}
|
|
255
258
|
return createBuilder({
|
|
256
259
|
...config,
|
|
257
|
-
stage: "
|
|
260
|
+
stage: "clientInput",
|
|
258
261
|
sqlConfig: newConfig,
|
|
259
262
|
initialValue: actualValue,
|
|
260
263
|
clientZod: clientAndServerSchema,
|
|
264
|
+
clientInputZod: finalSchema,
|
|
261
265
|
validationZod: clientAndServerSchema,
|
|
262
266
|
completedStages: newCompletedStages,
|
|
263
267
|
});
|
|
264
268
|
},
|
|
269
|
+
client: (assert) => {
|
|
270
|
+
if (completedStages.has("server")) {
|
|
271
|
+
throw new Error("client() must be called before server()");
|
|
272
|
+
}
|
|
273
|
+
const clientSchema = isFunction(assert)
|
|
274
|
+
? assert({
|
|
275
|
+
sql: config.sqlZod,
|
|
276
|
+
clientInput: config.clientInputZod || config.clientZod,
|
|
277
|
+
client: config.clientZod,
|
|
278
|
+
})
|
|
279
|
+
: assert;
|
|
280
|
+
const newCompletedStages = new Set(completedStages);
|
|
281
|
+
newCompletedStages.add("client");
|
|
282
|
+
const newConfig = {
|
|
283
|
+
...config,
|
|
284
|
+
stage: "client",
|
|
285
|
+
clientZod: clientSchema,
|
|
286
|
+
validationZod: clientSchema,
|
|
287
|
+
completedStages: newCompletedStages,
|
|
288
|
+
};
|
|
289
|
+
if (config.clientInputZod !== undefined) {
|
|
290
|
+
newConfig.clientInputZod = config.clientInputZod;
|
|
291
|
+
}
|
|
292
|
+
return createBuilder(newConfig);
|
|
293
|
+
},
|
|
265
294
|
server: (assert) => {
|
|
266
295
|
if (completedStages.has("server")) {
|
|
267
296
|
throw new Error("server() can only be called once in the chain");
|
|
@@ -269,6 +298,7 @@ function createBuilder(config) {
|
|
|
269
298
|
const serverSchema = isFunction(assert)
|
|
270
299
|
? assert({
|
|
271
300
|
sql: config.sqlZod,
|
|
301
|
+
clientInput: config.clientZod,
|
|
272
302
|
client: config.clientZod,
|
|
273
303
|
})
|
|
274
304
|
: assert;
|
|
@@ -282,8 +312,9 @@ function createBuilder(config) {
|
|
|
282
312
|
});
|
|
283
313
|
},
|
|
284
314
|
transform: (transforms) => {
|
|
285
|
-
if (!completedStages.has("server") &&
|
|
286
|
-
|
|
315
|
+
if (!completedStages.has("server") &&
|
|
316
|
+
!completedStages.has("clientInput")) {
|
|
317
|
+
throw new Error("transform() requires at least clientInput() or server() to be called first");
|
|
287
318
|
}
|
|
288
319
|
return {
|
|
289
320
|
config: {
|
|
@@ -386,6 +417,7 @@ function isReference(value) {
|
|
|
386
417
|
}
|
|
387
418
|
export function createSchema(schema, relations) {
|
|
388
419
|
const sqlFields = {};
|
|
420
|
+
const clientInputFields = {};
|
|
389
421
|
const clientFields = {};
|
|
390
422
|
const serverFields = {};
|
|
391
423
|
const defaultValues = {};
|
|
@@ -420,6 +452,7 @@ export function createSchema(schema, relations) {
|
|
|
420
452
|
else {
|
|
421
453
|
clientToDbKeys[key] = dbFieldName;
|
|
422
454
|
dbToClientKeys[dbFieldName] = key;
|
|
455
|
+
clientInputFields[key] = config.zodClientInputSchema;
|
|
423
456
|
clientFields[key] = config.zodClientSchema;
|
|
424
457
|
serverFields[key] = config.zodValidationSchema;
|
|
425
458
|
const initialValueOrFn = config.initialValue;
|
|
@@ -461,6 +494,7 @@ export function createSchema(schema, relations) {
|
|
|
461
494
|
else {
|
|
462
495
|
clientToDbKeys[key] = dbFieldName;
|
|
463
496
|
dbToClientKeys[dbFieldName] = key;
|
|
497
|
+
clientInputFields[key] = config.zodClientInputSchema;
|
|
464
498
|
clientFields[key] = config.zodClientSchema;
|
|
465
499
|
serverFields[key] = config.zodValidationSchema;
|
|
466
500
|
if (config.transforms) {
|
|
@@ -480,6 +514,7 @@ export function createSchema(schema, relations) {
|
|
|
480
514
|
}
|
|
481
515
|
}
|
|
482
516
|
else {
|
|
517
|
+
clientInputFields[key] = config.zodClientInputSchema;
|
|
483
518
|
clientFields[key] = config.zodClientSchema;
|
|
484
519
|
serverFields[key] = config.zodValidationSchema;
|
|
485
520
|
if (config.transforms) {
|
|
@@ -578,19 +613,28 @@ export function createSchema(schema, relations) {
|
|
|
578
613
|
return clientObject;
|
|
579
614
|
};
|
|
580
615
|
const toDb = (clientObject) => {
|
|
616
|
+
// 1. Calculate derives FIRST based on the client data
|
|
617
|
+
const clientWithDerives = { ...clientObject };
|
|
618
|
+
if (derives) {
|
|
619
|
+
for (const key in derives) {
|
|
620
|
+
clientWithDerives[key] = derives[key](clientWithDerives);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// 2. Map the data (including the newly derived fields) to the DB object
|
|
581
624
|
const dbObject = {};
|
|
582
|
-
for (const clientKey in
|
|
583
|
-
if (
|
|
625
|
+
for (const clientKey in clientWithDerives) {
|
|
626
|
+
if (clientWithDerives[clientKey] === undefined)
|
|
584
627
|
continue;
|
|
585
628
|
const dbKey = clientToDbKeys[clientKey] || clientKey;
|
|
586
629
|
const transform = fieldTransforms[clientKey]?.toDb;
|
|
587
630
|
dbObject[dbKey] = transform
|
|
588
|
-
? transform(
|
|
589
|
-
:
|
|
631
|
+
? transform(clientWithDerives[clientKey])
|
|
632
|
+
: clientWithDerives[clientKey];
|
|
590
633
|
}
|
|
591
634
|
return dbObject;
|
|
592
635
|
};
|
|
593
636
|
const finalSqlSchema = z.object(sqlFields);
|
|
637
|
+
const finalClientInputSchema = z.object(clientInputFields);
|
|
594
638
|
const finalClientSchema = z.object(clientFields);
|
|
595
639
|
const finalValidationSchema = z.object(serverFields);
|
|
596
640
|
return {
|
|
@@ -598,6 +642,7 @@ export function createSchema(schema, relations) {
|
|
|
598
642
|
clientPk: clientPkKeys.length ? clientPkKeys : null,
|
|
599
643
|
isClientRecord,
|
|
600
644
|
sqlSchema: finalSqlSchema,
|
|
645
|
+
clientInputSchema: finalClientInputSchema,
|
|
601
646
|
clientSchema: finalClientSchema,
|
|
602
647
|
serverSchema: finalValidationSchema,
|
|
603
648
|
defaultValues: defaultValues,
|
|
@@ -610,8 +655,8 @@ export function createSchema(schema, relations) {
|
|
|
610
655
|
return toDb(validData);
|
|
611
656
|
},
|
|
612
657
|
parseFromDb: (dbData) => {
|
|
613
|
-
|
|
614
|
-
return
|
|
658
|
+
finalSqlSchema.parse(dbData);
|
|
659
|
+
return toClient(dbData);
|
|
615
660
|
},
|
|
616
661
|
};
|
|
617
662
|
}
|
|
@@ -639,7 +684,9 @@ function createViewObject(initialRegistryKey, selection, registry, tableNameToRe
|
|
|
639
684
|
}
|
|
640
685
|
const baseSchema = schemaType === "server"
|
|
641
686
|
? registryEntry.zodSchemas.serverSchema
|
|
642
|
-
:
|
|
687
|
+
: schemaType === "sql"
|
|
688
|
+
? registryEntry.zodSchemas.sqlSchema
|
|
689
|
+
: registryEntry.zodSchemas.clientSchema;
|
|
643
690
|
const primitiveShape = baseSchema.shape;
|
|
644
691
|
if (subSelection === true) {
|
|
645
692
|
return z.object(primitiveShape);
|
|
@@ -670,7 +717,7 @@ function createViewObject(initialRegistryKey, selection, registry, tableNameToRe
|
|
|
670
717
|
return z.object(finalShape);
|
|
671
718
|
}
|
|
672
719
|
return {
|
|
673
|
-
sql:
|
|
720
|
+
sql: buildView(initialRegistryKey, selection, "sql"),
|
|
674
721
|
client: buildView(initialRegistryKey, selection, "client"),
|
|
675
722
|
server: buildView(initialRegistryKey, selection, "server"),
|
|
676
723
|
supportsReconciliation: allTablesSupportsReconciliation,
|
|
@@ -758,6 +805,44 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
758
805
|
generateDefaults: zodSchemas.generateDefaults,
|
|
759
806
|
};
|
|
760
807
|
}
|
|
808
|
+
const cleanerRegistry = {};
|
|
809
|
+
const tableNameToRegistryKeyMap = {};
|
|
810
|
+
for (const key in finalRegistry) {
|
|
811
|
+
const tableName = finalRegistry[key].rawSchema._tableName;
|
|
812
|
+
tableNameToRegistryKeyMap[tableName] = key;
|
|
813
|
+
}
|
|
814
|
+
for (const tableName in finalRegistry) {
|
|
815
|
+
const entry = finalRegistry[tableName];
|
|
816
|
+
const rawSchema = entry.rawSchema;
|
|
817
|
+
const tableDef = { ...entry.generateDefaults() };
|
|
818
|
+
for (const key in rawSchema) {
|
|
819
|
+
if (key === "_tableName" || key.startsWith("__"))
|
|
820
|
+
continue;
|
|
821
|
+
const field = rawSchema[key];
|
|
822
|
+
if (!field?.config?.sql)
|
|
823
|
+
continue;
|
|
824
|
+
const sqlConfig = field.config.sql;
|
|
825
|
+
if (sqlConfig.schema) {
|
|
826
|
+
const targetTableName = sqlConfig.schema()._tableName;
|
|
827
|
+
const targetRegKey = tableNameToRegistryKeyMap[targetTableName];
|
|
828
|
+
if (targetRegKey && finalRegistry[targetRegKey]) {
|
|
829
|
+
const targetEntry = finalRegistry[targetRegKey];
|
|
830
|
+
const targetDefaults = targetEntry.generateDefaults();
|
|
831
|
+
if (sqlConfig.type === "hasMany" || sqlConfig.type === "manyToMany") {
|
|
832
|
+
const count = sqlConfig?.defaultCount || 1;
|
|
833
|
+
tableDef[key] = Array.from({ length: count }, () => targetDefaults);
|
|
834
|
+
tableDef[`__def__${key}`] = targetDefaults;
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
tableDef[key] =
|
|
838
|
+
sqlConfig.defaultConfig === null ? null : targetDefaults;
|
|
839
|
+
tableDef[`__def__${key}`] = targetDefaults;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
entry.zodSchemas.defaultsDefinition = tableDef;
|
|
845
|
+
}
|
|
761
846
|
const createNavProxy = (currentTable, registry) => {
|
|
762
847
|
return new Proxy({}, {
|
|
763
848
|
get(target, relationName) {
|
|
@@ -776,12 +861,6 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
776
861
|
},
|
|
777
862
|
});
|
|
778
863
|
};
|
|
779
|
-
const cleanerRegistry = {};
|
|
780
|
-
const tableNameToRegistryKeyMap = {};
|
|
781
|
-
for (const key in finalRegistry) {
|
|
782
|
-
const tableName = finalRegistry[key].rawSchema._tableName;
|
|
783
|
-
tableNameToRegistryKeyMap[tableName] = key;
|
|
784
|
-
}
|
|
785
864
|
for (const tableName in finalRegistry) {
|
|
786
865
|
const entry = finalRegistry[tableName];
|
|
787
866
|
cleanerRegistry[tableName] = {
|
|
@@ -789,6 +868,7 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
789
868
|
schemaKey: tableName,
|
|
790
869
|
schemas: {
|
|
791
870
|
sql: entry.zodSchemas.sqlSchema,
|
|
871
|
+
clientInput: entry.zodSchemas.clientInputSchema,
|
|
792
872
|
client: entry.zodSchemas.clientSchema,
|
|
793
873
|
server: entry.zodSchemas.serverSchema,
|
|
794
874
|
},
|
|
@@ -799,6 +879,7 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
799
879
|
parseFromDb: entry.transforms.parseFromDb,
|
|
800
880
|
},
|
|
801
881
|
defaults: entry.generateDefaults(),
|
|
882
|
+
defaultsDefinition: entry.zodSchemas.defaultsDefinition,
|
|
802
883
|
stateType: entry.zodSchemas.stateType,
|
|
803
884
|
generateDefaults: entry.generateDefaults,
|
|
804
885
|
pk: entry.pk,
|
|
@@ -808,6 +889,7 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
808
889
|
createView: (selection) => {
|
|
809
890
|
const view = createViewObject(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
|
|
810
891
|
const defaults = computeViewDefaults(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
|
|
892
|
+
const defaultsDefinition = computeViewDefaultsDefinition(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
|
|
811
893
|
const deepToClient = (dbData, currentSelection, currentKey) => {
|
|
812
894
|
if (!dbData)
|
|
813
895
|
return dbData;
|
|
@@ -877,13 +959,18 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
877
959
|
toClient: viewToClient,
|
|
878
960
|
toDb: viewToDb,
|
|
879
961
|
parseForDb: (appData) => {
|
|
880
|
-
|
|
962
|
+
// FIX: Now correctly validates against the view's server schema first
|
|
963
|
+
const validData = view.server.parse(appData);
|
|
964
|
+
return viewToDb(validData);
|
|
881
965
|
},
|
|
882
966
|
parseFromDb: (dbData) => {
|
|
883
|
-
|
|
967
|
+
// FIX: Now correctly validates against the view's client schema after mapping
|
|
968
|
+
const mappedData = view.sql.parse(dbData);
|
|
969
|
+
return viewToClient(mappedData);
|
|
884
970
|
},
|
|
885
971
|
},
|
|
886
972
|
defaults: defaults,
|
|
973
|
+
defaultsDefinition: defaultsDefinition,
|
|
887
974
|
pk: entry.zodSchemas.pk,
|
|
888
975
|
clientPk: entry.zodSchemas.clientPk,
|
|
889
976
|
supportsReconciliation: view.supportsReconciliation,
|
|
@@ -946,3 +1033,68 @@ function computeViewDefaults(currentRegistryKey, selection, registry, tableNameT
|
|
|
946
1033
|
}
|
|
947
1034
|
return baseDefaults;
|
|
948
1035
|
}
|
|
1036
|
+
function computeViewDefaultsDefinition(currentRegistryKey, selection, registry, tableNameToRegistryKeyMap, visited = new Set()) {
|
|
1037
|
+
if (visited.has(currentRegistryKey)) {
|
|
1038
|
+
return undefined;
|
|
1039
|
+
}
|
|
1040
|
+
visited.add(currentRegistryKey);
|
|
1041
|
+
const entry = registry[currentRegistryKey];
|
|
1042
|
+
if (!entry) {
|
|
1043
|
+
return {};
|
|
1044
|
+
}
|
|
1045
|
+
const baseDef = { ...entry.generateDefaults() };
|
|
1046
|
+
for (const key in entry.rawSchema) {
|
|
1047
|
+
if (key === "_tableName" || key.startsWith("__"))
|
|
1048
|
+
continue;
|
|
1049
|
+
const field = entry.rawSchema[key];
|
|
1050
|
+
if (!field?.config?.sql)
|
|
1051
|
+
continue;
|
|
1052
|
+
const sqlConfig = field.config.sql;
|
|
1053
|
+
if (sqlConfig.schema) {
|
|
1054
|
+
const targetTableName = sqlConfig.schema()._tableName;
|
|
1055
|
+
const nextRegKey = tableNameToRegistryKeyMap[targetTableName];
|
|
1056
|
+
if (!nextRegKey)
|
|
1057
|
+
continue;
|
|
1058
|
+
const targetEntry = registry[nextRegKey];
|
|
1059
|
+
const targetDefaults = targetEntry.generateDefaults();
|
|
1060
|
+
if (sqlConfig.type === "hasMany" || sqlConfig.type === "manyToMany") {
|
|
1061
|
+
const count = sqlConfig?.defaultCount || 1;
|
|
1062
|
+
baseDef[key] = Array.from({ length: count }, () => targetDefaults);
|
|
1063
|
+
baseDef[`__def__${key}`] = targetDefaults;
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
baseDef[key] = sqlConfig.defaultConfig === null ? null : targetDefaults;
|
|
1067
|
+
baseDef[`__def__${key}`] = targetDefaults;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (selection === true || typeof selection !== "object") {
|
|
1072
|
+
return baseDef;
|
|
1073
|
+
}
|
|
1074
|
+
const result = { ...baseDef };
|
|
1075
|
+
for (const key in selection) {
|
|
1076
|
+
if (!selection[key])
|
|
1077
|
+
continue;
|
|
1078
|
+
const field = entry.rawSchema[key];
|
|
1079
|
+
if (!field?.config?.sql?.schema)
|
|
1080
|
+
continue;
|
|
1081
|
+
const relationConfig = field.config.sql;
|
|
1082
|
+
const targetTableName = relationConfig.schema()._tableName;
|
|
1083
|
+
const nextRegistryKey = tableNameToRegistryKeyMap[targetTableName];
|
|
1084
|
+
if (!nextRegistryKey)
|
|
1085
|
+
continue;
|
|
1086
|
+
const nestedDef = computeViewDefaultsDefinition(nextRegistryKey, selection[key], registry, tableNameToRegistryKeyMap, new Set(visited));
|
|
1087
|
+
if (nestedDef) {
|
|
1088
|
+
result[`__def__${key}`] = nestedDef;
|
|
1089
|
+
if (relationConfig.type === "hasMany" ||
|
|
1090
|
+
relationConfig.type === "manyToMany") {
|
|
1091
|
+
const count = relationConfig?.defaultCount || 1;
|
|
1092
|
+
result[key] = Array.from({ length: count }, () => nestedDef);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
result[key] = relationConfig.defaultConfig === null ? null : nestedDef;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return result;
|
|
1100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.180",
|
|
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",
|