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 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 | Purpose |
41
- | -------------------------------------------- | -------------------------------------------------------------- |
42
- | `s.sql({ type })` | Database column type. The starting point for every field. |
43
- | `.client({ value, schema })` | Client-side validation and default value for new records. |
44
- | `.server(fn)` | Server-side validation. Stricter rules before database writes. |
45
- | `.transform({ toClient, toDb })` | Converts between database and client representations. |
46
- | `.derive({ field: (row) => computedValue })` | Adds computed fields based on other field values. |
47
- | `.sqlOnly` | Marks a field as server-only (not sent to client). |
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
- `.client()` sets the default value and client-side validation type for new records. It combines what was previously done with `.initialState()`.
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 }).client({
74
+ id: s.sql({ type: "int", pk: true }).clientInput({
75
75
  value: () => crypto.randomUUID(),
76
76
  schema: z.string(),
77
77
  }),
78
- // Client type becomes: string | number (union of SQL + client)
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" }).client({ value: "Anonymous" }),
83
- // Client type: string (inherits from SQL)
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" }).client(() => z.number().min(0)),
88
- // Client type: number (with min validation)
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
- ### 3. Server Server-Side Validation
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
- .client(() => z.string().trim())
116
- .server(({ client }) => client.min(2, "Too short")),
130
+ .clientInput(() => z.string().trim())
131
+ .server(({ clientInput }) => clientInput.min(2, "Too short")),
117
132
  ```
118
133
 
119
- ### 4. Transform — Convert Between Layers
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
- .client(() => z.enum(["active", "inactive"])) // Client: string enum
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. Derive Computed Fields
150
+ ### 6. Layer Separation: DB-Only, Client-Only, and Derived Fields
136
151
 
137
- `.derive()` adds computed fields that are calculated from other values in the row. These are:
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
- ```typescript
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
- // Now defaults and toClient include the computed value
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
- // Client schema: { id, email } — internalToken and trustScore excluded
177
- // SQL schema: { id, email, internalToken, trustScore } — all fields
178
- // Defaults: { id: 0, email: "" } sqlOnly fields excluded
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
- ### 8. Client-Only Fields
181
+ #### Derived Fields (`.derive()`)
182
182
 
183
- Use `s.client()` without `s.sql()` to define fields that exist only on the client:
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 orderSchema = schema({
187
- _tableName: "orders",
188
- id: s.sql({ type: "int", pk: true }),
189
- total: s.sql({ type: "int" }).client({ value: 0 }),
190
- // Client-only: computed from other fields, not stored
191
- formattedTotal: s.client(""),
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
- formattedTotal: (row) => `$${(row.total / 100).toFixed(2)}`,
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; // { sqlSchema, clientSchema, serverSchema } — Zod schemas
205
- schema.transforms; // { toClient, toDb, parseForDb, parseFromDb } — transformations
206
- schema.defaults; // Default values for forms
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; // Primary key field names
209
- schema.clientPk; // Client-side primary key field names
210
- schema.isClientRecord; // Function to check if a record is client-created
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 }).client({
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
- isArchived: s
231
- .sql({ type: "int" })
232
- .client(() => z.boolean())
235
+ isActive: s
236
+ .sql({ type: "boolean", default: true })
237
+ .clientInput(() => z.boolean())
233
238
  .transform({
234
- toClient: (val) => val === 1,
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 { clientSchema, serverSchema, sqlSchema } = schema;
243
- const { defaultValues, generateDefaults } = schema;
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: "", isArchived: false }
255
+ // { id: "new_a1b2c3d4", name: "", email: "", isActive: true }
251
256
 
252
257
  // Validate explicitly
253
- const result = serverSchema.safeParse(data);
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 serverSchema, outputs { isArchived: 0, ... }
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({ users, posts }, (s) => ({
290
- users: {
291
- posts: { fromKey: "id", toKey: s.posts.authorId },
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.apply() handles nested relations automatically
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.
@@ -5,7 +5,7 @@ const sqlTypeMap = {
5
5
  char: (length = 1) => `CHAR(${length})`,
6
6
  text: "TEXT",
7
7
  longtext: "LONGTEXT",
8
- boolean: "BOOLEAN",
8
+ boolean: "TINYINT(1)",
9
9
  date: "DATE",
10
10
  datetime: "DATETIME",
11
11
  };
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.ZodBoolean> : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
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.ZodBoolean : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
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
- client<const TValue>(options: {
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<"client", T, TSql, TValue extends () => infer R ? R : TValue, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>>>;
53
- client<const TSchema extends z.ZodTypeAny>(options: {
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<"client", T, TSql, z.infer<TSchema>, CollapsedUnion<TSql, TSchema>, CollapsedUnion<TSql, TSchema>>>;
58
- client<const TSchema extends z.ZodTypeAny>(options: {
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<"client", T, TSql, z.infer<TSchema>, CollapsedUnion<TSql, TSchema>, CollapsedUnion<TSql, TSchema>>>;
63
- client<const TValue>(options: {
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<"client", T, TSql, TValue extends () => infer R ? R : TValue, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>>>;
70
- client(options: {
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<"client", T, TSql, unknown, z.ZodTypeAny, z.ZodTypeAny>>;
74
- client<const TValue, const TSchema extends z.ZodTypeAny>(options: {
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<"client", T, TSql, TValue extends () => infer R ? R : TValue, CollapsedUnion<TSql, TSchema>, CollapsedUnion<TSql, TSchema>>>;
81
- client<TClientNext extends z.ZodTypeAny>(schema: ((tools: {
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<"client", T, TSql, TInitialValue, TClientNext, TClientNext>>;
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
- client: TClient;
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
- zodClientSchema: TClient;
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
- client: <const TValue>(value: TValue | ((tools: {
161
+ clientInput: <const TValue>(value: TValue | ((tools: {
153
162
  uuid: () => string;
154
- }) => TValue)) => Builder<"client", null, z.ZodUndefined, // No SQL schema
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
- client: (value) => {
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: "client",
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
- client: (...args) => {
125
- if (completedStages.has("client")) {
126
- throw new Error("client() can only be called once in the chain");
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("client() must be called before server()");
130
+ throw new Error("clientInput() must be called before server()");
130
131
  }
131
132
  const newCompletedStages = new Set(completedStages);
132
- newCompletedStages.add("client");
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: "client",
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: "client",
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") && !completedStages.has("client")) {
286
- throw new Error("transform() requires at least client() or server() to be called first");
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 clientObject) {
583
- if (clientObject[clientKey] === undefined)
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(clientObject[clientKey])
589
- : clientObject[clientKey];
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
- const mappedData = toClient(dbData);
614
- return finalClientSchema.parse(mappedData);
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
- : registryEntry.zodSchemas.clientSchema;
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: registry[initialRegistryKey].zodSchemas.sqlSchema,
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
- return viewToDb(appData);
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
- return viewToClient(dbData);
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.178",
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",