cogsbox-shape 0.5.176 → 0.5.178

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,16 +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() → .initialState() → .client() → .server() → .transform()
36
+ s.sql() → .client() → .server() → .transform()
37
+ → .derive() → .sqlOnly()
37
38
  ```
38
39
 
39
40
  | Method | Purpose |
40
41
  | -------------------------------------------- | -------------------------------------------------------------- |
41
42
  | `s.sql({ type })` | Database column type. The starting point for every field. |
42
- | `.initialState({ value, schema, clientPk })` | Default value and type for new records created on the client. |
43
- | `.client(fn)` | Client-side validation. Overrides the client type if needed. |
43
+ | `.client({ value, schema })` | Client-side validation and default value for new records. |
44
44
  | `.server(fn)` | Server-side validation. Stricter rules before database writes. |
45
- | `.transform({ toClient, toDb })` | Converts between database and client representations. |
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). |
46
48
 
47
49
  ### 1. SQL — Define Your Database Schema
48
50
 
@@ -61,66 +63,34 @@ const userSchema = schema({
61
63
 
62
64
  This generates a Zod schema matching your SQL types exactly.
63
65
 
64
- ### 2. Initial State — Defaults for New Records
66
+ ### 2. Client — Defaults and Client-Side Validation
65
67
 
66
- When creating new records on the client, you often need different types than what the database stores. `.initialState()` sets the default value and optionally narrows or widens the client type.
68
+ `.client()` sets the default value and client-side validation type for new records. It combines what was previously done with `.initialState()`.
67
69
 
68
70
  ```typescript
69
71
  const userSchema = schema({
70
72
  _tableName: "users",
71
73
  // DB stores auto-increment integers, but new records need a temp string ID
72
- id: s.sql({ type: "int", pk: true }).initialState({
74
+ id: s.sql({ type: "int", pk: true }).client({
73
75
  value: () => crypto.randomUUID(),
74
76
  schema: z.string(),
75
- clientPk: true, // Explicitly marks this as a client PK, auto-creating a union type
76
77
  }),
77
- // Client type becomes: string | number (union of SQL + initialState)
78
+ // Client type becomes: string | number (union of SQL + client)
78
79
  // Default value: a generated UUID string
79
- });
80
- ```
81
-
82
- If the type you pass to `.initialState()` matches the SQL type, no union is created:
83
-
84
- ```typescript
85
- count: s.sql({ type: "int" }).initialState({ value: 0 }),
86
- // Client type: number (no union, same type)
87
- // Default value: 0
88
- ```
89
-
90
- ### 3. Client — Client-Side Validation
91
80
 
92
- The client schema is automatically derived as a union of the SQL type (data fetched from the database) and the initial state type (data created on the client). `.client()` lets you override this to add client-side validation rules or declare a completely different type.
81
+ // Simple default without type override
82
+ name: s.sql({ type: "varchar" }).client({ value: "Anonymous" }),
83
+ // Client type: string (inherits from SQL)
84
+ // Default value: "Anonymous"
93
85
 
94
- ```typescript
95
- // Without .client() the type is inferred automatically
96
- id: s.sql({ type: "int", pk: true }).initialState({
97
- value: () => crypto.randomUUID(),
98
- schema: z.string(),
99
- }),
100
- // Client type: string | number
101
- // (string from initialState + number from SQL)
102
-
103
- // With .client() — add validation rules
104
- name: s
105
- .sql({ type: "varchar" })
106
- .client(({ sql }) => sql.min(2).max(100)),
107
- // Client type: string (with min/max validation)
108
-
109
- // With .client() — declare a different type entirely
110
- // Pair with .transform() to convert between them
111
- isActive: s
112
- .sql({ type: "int" })
113
- .client(() => z.boolean())
114
- .transform({
115
- toClient: (dbValue) => dbValue === 1,
116
- toDb: (clientValue) => (clientValue ? 1 : 0),
117
- }),
118
- // Client type: boolean (DB stores 0/1, client works with true/false)
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)
89
+ // Default value: inferred from type (0 for number)
90
+ });
119
91
  ```
120
92
 
121
- When `.client()` overrides the type without `.initialState()`, the default value is inferred from the client schema (e.g., `boolean` `false`, `string` → `""`).
122
-
123
- ### 4. Server — Server-Side Validation
93
+ ### 3. Server Server-Side Validation
124
94
 
125
95
  `.server()` adds validation rules that run at the server boundary before database writes. It builds on the client schema, adding stricter constraints.
126
96
 
@@ -146,7 +116,7 @@ name: s
146
116
  .server(({ client }) => client.min(2, "Too short")),
147
117
  ```
148
118
 
149
- ### 5. Transform — Convert Between Layers
119
+ ### 4. Transform — Convert Between Layers
150
120
 
151
121
  `.transform()` defines bidirectional conversion functions. These run on the server when reading from or writing to the database.
152
122
 
@@ -162,6 +132,84 @@ status: s
162
132
 
163
133
  Transforms are optional — only needed when the client type differs from the SQL type.
164
134
 
135
+ ### 6. Derive — Computed Fields
136
+
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
141
+
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
+ });
152
+
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
166
+
167
+ ```typescript
168
+ const userSchema = schema({
169
+ _tableName: "users",
170
+ id: s.sql({ type: "int", pk: true }),
171
+ email: s.sql({ type: "varchar" }),
172
+ internalToken: s.sql({ type: "varchar", sqlOnly: true }),
173
+ trustScore: s.sql({ type: "int", sqlOnly: true }),
174
+ });
175
+
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
179
+ ```
180
+
181
+ ### 8. Client-Only Fields
182
+
183
+ Use `s.client()` without `s.sql()` to define fields that exist only on the client:
184
+
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(""),
192
+ }).derive({
193
+ formattedTotal: (row) => `$${(row.total / 100).toFixed(2)}`,
194
+ });
195
+ ```
196
+
197
+ ### Schema Object Structure
198
+
199
+ The returned schema object has a clear separation of concerns:
200
+
201
+ ```typescript
202
+ const schema = createSchema(mySchema);
203
+
204
+ schema.schemas; // { sqlSchema, clientSchema, serverSchema } — Zod schemas
205
+ schema.transforms; // { toClient, toDb, parseForDb, parseFromDb } — transformations
206
+ schema.defaults; // Default values for forms
207
+ 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
211
+ ```
212
+
165
213
  ## Using Schemas
166
214
 
167
215
  ### Single Schema with `createSchema`
@@ -173,10 +221,9 @@ import { s, schema, createSchema } from "cogsbox-shape";
173
221
 
174
222
  const contactSchema = schema({
175
223
  _tableName: "contacts",
176
- id: s.sql({ type: "int", pk: true }).initialState({
224
+ id: s.sql({ type: "int", pk: true }).client({
177
225
  value: () => `new_${crypto.randomUUID().slice(0, 8)}`,
178
226
  schema: z.string(),
179
- clientPk: true,
180
227
  }),
181
228
  name: s.sql({ type: "varchar" }).server(({ sql }) => sql.min(2)),
182
229
  email: s.sql({ type: "varchar" }).server(({ sql }) => sql.email()),
@@ -189,15 +236,14 @@ const contactSchema = schema({
189
236
  }),
190
237
  });
191
238
 
192
- const {
193
- clientSchema, // Zod schema for client-side validation
194
- serverSchema, // Zod schema with .server() rules
195
- sqlSchema, // Zod schema matching DB column types
196
- defaultValues, // Typed defaults matching clientSchema
197
- generateDefaults, // Generates fresh defaults (executes randomizers/dates)
198
- parseForDb, // Validates client app data & transforms to DB format
199
- parseFromDb, // Transforms DB data & validates to Client format
200
- } = createSchema(contactSchema);
239
+ const schema = createSchema(contactSchema);
240
+
241
+ // Access schemas directly
242
+ const { clientSchema, serverSchema, sqlSchema } = schema;
243
+ const { defaultValues, generateDefaults } = schema;
244
+
245
+ // Transforms for converting between layers
246
+ const { toClient, toDb, parseForDb, parseFromDb } = schema.transforms;
201
247
 
202
248
  // Use in a form
203
249
  const [data, setData] = useState(generateDefaults());
@@ -252,11 +298,19 @@ const box = createSchemaBox({ users, posts }, (s) => ({
252
298
  Base schemas **exclude relations** by default, preventing circular dependencies:
253
299
 
254
300
  ```typescript
255
- const { schemas, defaults } = box.users;
301
+ const { schemas, defaults, transforms, pk, clientPk } = box.users;
256
302
 
257
303
  type UserClient = z.infer<typeof schemas.client>;
258
304
  // { id: number; name: string; }
259
305
  // No 'posts' — relations are excluded from base schemas
306
+
307
+ // Convert data between layers
308
+ const dbRow = transforms.toDb(clientData);
309
+ const clientData = transforms.toClient(dbRow);
310
+
311
+ // Validate and convert in one step
312
+ const dbRow = transforms.parseForDb(appData);
313
+ const clientData = transforms.parseFromDb(dbRow);
260
314
  ```
261
315
 
262
316
  ### 4. Create Views to Include Relations
@@ -275,6 +329,8 @@ type UserWithPosts = z.infer<typeof userWithPosts.schemas.client>;
275
329
  // posts: { id: number; title: string; authorId: number; }[]
276
330
  // }
277
331
 
278
- const defaults = userWithPosts.defaults;
279
- // { id: 0, name: '', posts:
332
+ // Views also have transforms for the selected fields
333
+ const { defaults, transforms } = userWithPosts;
334
+ // transforms.apply() handles nested relations automatically
335
+ ```
280
336
  ```
package/dist/schema.d.ts CHANGED
@@ -33,6 +33,7 @@ type BaseConfig = {
33
33
  nullable?: boolean;
34
34
  pk?: true;
35
35
  field?: string;
36
+ sqlOnly?: true;
36
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
39
  default: "CURRENT_TIMESTAMP";
@@ -41,45 +42,59 @@ type SQLToZodType<T extends SQLType, TDefault extends boolean> = T["pk"] extends
41
42
  } ? TDefault extends true ? never : z.ZodDate : z.ZodDate : never;
42
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;
43
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]>;
44
- export interface IBuilderMethods<T extends DbConfig, TSql extends z.ZodTypeAny, TNew extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> {
45
- initialState<const TValue>(options: {
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
47
  value: TValue | ((tools: {
47
48
  uuid: () => string;
48
49
  }) => TValue);
49
50
  schema?: never;
50
51
  clientPk?: boolean | ((val: any) => boolean);
51
- }): Prettify<Builder<"new", T, TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, TValue extends () => infer R ? R : TValue, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>, CollapsedUnion<TSql, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>>>;
52
- initialState<const TSchema extends z.ZodTypeAny>(options: {
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: {
53
54
  value?: never;
54
55
  schema: TSchema;
55
56
  clientPk?: boolean | ((val: any) => boolean);
56
- }): Prettify<Builder<"new", T, TSql, TSchema, z.infer<TSchema>, CollapsedUnion<TSql, TSchema>, CollapsedUnion<TSql, TSchema>>>;
57
- initialState<const TValue, const TSchema extends z.ZodTypeAny>(options: {
57
+ }): Prettify<Builder<"client", T, TSql, z.infer<TSchema>, CollapsedUnion<TSql, TSchema>, CollapsedUnion<TSql, TSchema>>>;
58
+ client<const TSchema extends z.ZodTypeAny>(options: {
59
+ value?: never;
60
+ schema: TSchema | ((tools: any) => TSchema);
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: {
64
+ value: TValue | ((tools: {
65
+ uuid: () => string;
66
+ }) => TValue);
67
+ schema?: never;
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: {
71
+ value?: never;
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: {
58
75
  value: TValue | ((tools: {
59
76
  uuid: () => string;
60
77
  }) => TValue);
61
78
  schema: TSchema | ((base: ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>) => TSchema);
62
79
  clientPk?: boolean | ((val: any) => boolean);
63
- }): Prettify<Builder<"new", T, TSql, TSchema, z.infer<TSchema>, CollapsedUnion<TSql, TSchema>, CollapsedUnion<TSql, TSchema>>>;
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: {
82
+ sql: TSql;
83
+ }) => TClientNext) | TClientNext): Prettify<Builder<"client", T, TSql, TInitialValue, TClientNext, TClientNext>>;
64
84
  reference: <TRefSchema extends {
65
85
  _tableName: string;
66
86
  }>(fieldGetter: () => any) => Builder<"sql", T & {
67
87
  references: typeof fieldGetter;
68
- }, TSql, TNew, TInitialValue, TClient, TValidation>;
69
- client: <TClientNext extends z.ZodTypeAny>(schema: ((tools: {
70
- sql: TSql;
71
- initialState: TNew;
72
- }) => TClientNext) | TClientNext) => Prettify<Builder<"client", T, TSql, TNew, TInitialValue, TClientNext, TClientNext>>;
88
+ }, TSql, TInitialValue, TClient, TValidation>;
73
89
  server: <TValidationNext extends z.ZodTypeAny>(schema: ((tools: {
74
90
  sql: TSql;
75
- initialState: TNew;
76
91
  client: TClient;
77
- }) => TValidationNext) | TValidationNext) => Prettify<Builder<"server", T, TSql, TNew, TInitialValue, TClient, TValidationNext>>;
92
+ }) => TValidationNext) | TValidationNext) => Prettify<Builder<"server", T, TSql, TInitialValue, TClient, TValidationNext>>;
78
93
  transform: (transforms: {
79
94
  toClient: (dbValue: z.infer<TSql>) => z.infer<TClient>;
80
95
  toDb: (clientValue: z.infer<TClient>) => z.infer<TSql>;
81
96
  }) => {
82
- config: Prettify<BuilderConfig<T, TSql, TNew, TInitialValue, TClient, TValidation>> & {
97
+ config: Prettify<BuilderConfig<T, TSql, TInitialValue, TClient, TValidation>> & {
83
98
  transforms: typeof transforms;
84
99
  };
85
100
  };
@@ -99,35 +114,32 @@ export type RelationConfig<T extends Schema<any>> = (BaseRelationConfig<T> & {
99
114
  }) | (BaseRelationConfig<T> & {
100
115
  type: "manyToMany";
101
116
  });
102
- type Stage = "sql" | "relation" | "new" | "client" | "server" | "done";
117
+ type Stage = "sql" | "relation" | "client" | "server" | "done";
103
118
  type StageMethods = {
104
- sql: "initialState" | "client" | "server" | "transform" | "reference";
105
- relation: "server" | "transform";
106
- new: "client" | "server" | "transform";
119
+ sql: "client" | "server" | "transform" | "reference";
120
+ relation: "client" | "server" | "transform";
107
121
  client: "server" | "transform";
108
122
  server: "transform";
109
123
  done: never;
110
124
  };
111
- type BuilderConfig<T extends DbConfig, TSql extends z.ZodTypeAny, TNew extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> = {
125
+ type BuilderConfig<T extends DbConfig, TSql extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> = {
112
126
  sql: T;
113
127
  zodSqlSchema: TSql;
114
- zodNewSchema: TNew;
115
128
  initialValue: TInitialValue;
116
129
  zodClientSchema: TClient;
117
130
  zodValidationSchema: TValidation;
118
131
  clientTransform?: (schema: z.ZodTypeAny) => z.ZodTypeAny;
119
132
  validationTransform?: (schema: z.ZodTypeAny) => z.ZodTypeAny;
120
133
  };
121
- export type Builder<TStage extends Stage, T extends DbConfig, TSql extends z.ZodTypeAny, TNew extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> = {
134
+ export type Builder<TStage extends Stage, T extends DbConfig, TSql extends z.ZodTypeAny, TInitialValue, TClient extends z.ZodTypeAny, TValidation extends z.ZodTypeAny> = {
122
135
  config: {
123
136
  sql: T;
124
137
  zodSqlSchema: TSql;
125
- zodNewSchema: TNew;
126
138
  initialValue: TInitialValue;
127
139
  zodClientSchema: TClient;
128
140
  zodValidationSchema: TValidation;
129
141
  };
130
- } & Pick<IBuilderMethods<T, TSql, TNew, TInitialValue, TClient, TValidation>, StageMethods[TStage]>;
142
+ } & Pick<IBuilderMethods<T, TSql, TInitialValue, TClient, TValidation>, StageMethods[TStage]>;
131
143
  type HasManyDefault = true | undefined | [] | {
132
144
  count: number;
133
145
  };
@@ -137,11 +149,11 @@ export type Reference<TGetter extends () => any> = {
137
149
  getter: TGetter;
138
150
  };
139
151
  interface ShapeAPI {
140
- initialState: <const TValue>(value: TValue | ((tools: {
152
+ client: <const TValue>(value: TValue | ((tools: {
141
153
  uuid: () => string;
142
- }) => TValue)) => Builder<"new", null, z.ZodUndefined, // No SQL schema
143
- ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, TValue extends () => infer R ? R : TValue, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>, ZodTypeFromPrimitive<TValue extends () => infer R ? R : TValue>>;
144
- sql: <const T extends SQLType>(sqlConfig: T) => Builder<"sql", T, SQLToZodType<T, false>, SQLToZodType<T, false>, z.infer<SQLToZodType<T, false>>, SQLToZodType<T, false>, SQLToZodType<T, false>>;
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>>;
156
+ sql: <const T extends SQLType>(sqlConfig: T) => Builder<"sql", T, SQLToZodType<T, false>, z.infer<SQLToZodType<T, false>>, SQLToZodType<T, false>, SQLToZodType<T, false>>;
145
157
  reference: <TGetter extends () => any>(getter: TGetter) => Reference<TGetter>;
146
158
  hasMany: <T extends HasManyDefault>(config?: T) => PlaceholderRelation<"hasMany">;
147
159
  hasOne: (config?: HasOneDefault) => PlaceholderRelation<"hasOne">;
@@ -173,7 +185,9 @@ type PickPrimaryKeys<T extends ShapeSchema> = {
173
185
  };
174
186
  type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
175
187
  __primaryKeySQL?: string;
188
+ __derives?: Record<string, (row: any) => any>;
176
189
  primaryKeySQL: (definer: (pkFields: PickPrimaryKeys<T>) => string) => SchemaBuilder<T>;
190
+ derive: <D extends Partial<Record<keyof T, (row: Prettify<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<T, "zodClientSchema">>>>>) => any>>>(derivers: D) => SchemaBuilder<T>;
177
191
  };
178
192
  export declare function schema<T extends string, U extends ShapeSchema<T>>(schema: U): SchemaBuilder<U>;
179
193
  export type RelationType = "hasMany" | "hasOne" | "manyToMany";
@@ -267,7 +281,7 @@ type ResolveField<Field, Resolution> = Field extends PlaceholderReference ? Reso
267
281
  type: "belongsTo";
268
282
  } : RelType extends "manyToMany" ? BaseRelationConfig<TargetSchema> & {
269
283
  type: "manyToMany";
270
- } : never, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>, RelType extends "hasMany" | "manyToMany" ? any[] : any, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>> : never : never : Field;
284
+ } : never, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>, RelType extends "hasMany" | "manyToMany" ? any[] : any, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>, RelType extends "hasMany" | "manyToMany" ? z.ZodArray<z.ZodObject<any>> : z.ZodObject<any>> : never : never : Field;
271
285
  type ResolveSchema<Schema extends SchemaWithPlaceholders, Resolutions extends Record<string, any>> = {
272
286
  [K in keyof Schema]: K extends keyof Resolutions ? ResolveField<Schema[K], Resolutions[K]> : Schema[K];
273
287
  };
@@ -280,15 +294,17 @@ type ResolvedRegistryWithSchemas<S extends Record<string, SchemaWithPlaceholders
280
294
  serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodValidationSchema">>>;
281
295
  defaultValues: Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
282
296
  stateType: Prettify<DeriveStateType<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
297
+ };
298
+ transforms: {
283
299
  toClient: (dbObject: any) => any;
284
300
  toDb: (clientObject: any) => any;
285
301
  parseForDb: (appData: any) => any;
286
302
  parseFromDb: (dbData: any) => any;
287
- pk: string[] | null;
288
- clientPk: string[] | null;
289
- isClientRecord: (record: any) => boolean;
290
- generateDefaults: () => Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
291
303
  };
304
+ pk: string[] | null;
305
+ clientPk: string[] | null;
306
+ isClientRecord: (record: any) => boolean;
307
+ generateDefaults: () => Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
292
308
  };
293
309
  };
294
310
  type IsRelationField<Field> = Field extends {
@@ -313,7 +329,8 @@ type GetRelationRegistryKey<Field, TRegistry extends RegistryShape> = Field exte
313
329
  type OmitRelationFields<Shape, RawSchema> = Omit<Shape, {
314
330
  [K in keyof Shape]: K extends keyof RawSchema ? IsRelationField<RawSchema[K]> extends true ? K : never : never;
315
331
  }[keyof Shape]>;
316
- type _DeriveViewShape<TTableName extends keyof TRegistry, TSelection, TRegistry extends RegistryShape, TKey extends "clientSchema" | "serverSchema", Depth extends any[] = []> = Depth["length"] extends 10 ? any : TRegistry[TTableName]["zodSchemas"][TKey] extends z.ZodObject<infer BaseShape> ? TSelection extends Record<string, any> ? Prettify<OmitRelationFields<BaseShape, TRegistry[TTableName]["rawSchema"]> & {
332
+ type _DeriveViewShape<TTableName extends keyof TRegistry, TSelection, TRegistry extends RegistryShape, TKey extends "clientSchema" | "serverSchema" | "sqlSchema", Depth extends any[] = []> = Depth["length"] extends 10 ? any : TKey extends "sqlSchema" ? TRegistry[TTableName]["zodSchemas"]["sqlSchema"] extends z.ZodObject<infer BaseShape> ? _DeriveViewShapeInner<BaseShape, TTableName, TSelection, TRegistry, TKey, Depth> : never : TRegistry[TTableName]["zodSchemas"][TKey] extends z.ZodObject<infer BaseShape> ? _DeriveViewShapeInner<BaseShape, TTableName, TSelection, TRegistry, TKey, Depth> : never;
333
+ type _DeriveViewShapeInner<BaseShape, TTableName extends keyof TRegistry, TSelection, TRegistry extends RegistryShape, TKey extends "clientSchema" | "serverSchema" | "sqlSchema", Depth extends any[] = []> = TSelection extends Record<string, any> ? Prettify<OmitRelationFields<BaseShape, TRegistry[TTableName]["rawSchema"]> & {
317
334
  [K in keyof TSelection & keyof TRegistry[TTableName]["rawSchema"] as IsRelationField<TRegistry[TTableName]["rawSchema"][K]> extends true ? K : never]: GetRelationRegistryKey<TRegistry[TTableName]["rawSchema"][K], TRegistry> extends infer TargetKey ? TargetKey extends keyof TRegistry ? TRegistry[TTableName]["rawSchema"][K] extends {
318
335
  config: {
319
336
  sql: {
@@ -327,7 +344,7 @@ type _DeriveViewShape<TTableName extends keyof TRegistry, TSelection, TRegistry
327
344
  ...Depth,
328
345
  1
329
346
  ]>>> : never : never : never;
330
- }> : OmitRelationFields<BaseShape, TRegistry[TTableName]["rawSchema"]> : never;
347
+ }> : OmitRelationFields<BaseShape, TRegistry[TTableName]["rawSchema"]>;
331
348
  type DeriveViewDefaults<TTableName extends keyof TRegistry, TSelection, TRegistry extends RegistryShape, Depth extends any[] = []> = Prettify<TRegistry[TTableName]["zodSchemas"]["defaultValues"] & (TSelection extends Record<string, any> ? {
332
349
  [K in keyof TSelection & keyof TRegistry[TTableName]["rawSchema"] as IsRelationField<TRegistry[TTableName]["rawSchema"][K]> extends true ? K : never]: TRegistry[TTableName]["rawSchema"][K] extends {
333
350
  config: {
@@ -353,11 +370,11 @@ export type DeriveViewResult<TTableName extends keyof TRegistry, TSelection, TRe
353
370
  server: z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "serverSchema">>;
354
371
  };
355
372
  transforms: {
356
- toClient: TRegistry[TTableName]["zodSchemas"]["toClient"];
357
- toDb: TRegistry[TTableName]["zodSchemas"]["toDb"];
373
+ toClient: TRegistry[TTableName]["transforms"]["toClient"];
374
+ toDb: TRegistry[TTableName]["transforms"]["toDb"];
375
+ parseForDb: (appData: z.input<z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "serverSchema">>>) => z.infer<z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "sqlSchema">>>;
376
+ parseFromDb: (dbData: Partial<z.infer<z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "sqlSchema">>>>) => z.infer<z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "clientSchema">>>;
358
377
  };
359
- parseForDb: (appData: z.input<TRegistry[TTableName]["zodSchemas"]["serverSchema"]>) => z.infer<TRegistry[TTableName]["zodSchemas"]["sqlSchema"]>;
360
- parseFromDb: (dbData: Partial<z.infer<TRegistry[TTableName]["zodSchemas"]["sqlSchema"]>>) => z.infer<TRegistry[TTableName]["zodSchemas"]["clientSchema"]>;
361
378
  defaults: DeriveViewDefaults<TTableName, TSelection, TRegistry>;
362
379
  pk: string[] | null;
363
380
  clientPk: string[] | null;
@@ -397,15 +414,17 @@ type RegistryShape = Record<string, {
397
414
  serverSchema: z.ZodObject<any>;
398
415
  defaultValues: any;
399
416
  stateType: any;
417
+ };
418
+ transforms: {
400
419
  toClient: (dbObject: any) => any;
401
420
  toDb: (clientObject: any) => any;
402
421
  parseForDb: (appData: any) => any;
403
422
  parseFromDb: (dbData: any) => any;
404
- pk: string[] | null;
405
- clientPk: string[] | null;
406
- isClientRecord: (record: any) => boolean;
407
- generateDefaults: () => any;
408
423
  };
424
+ pk: string[] | null;
425
+ clientPk: string[] | null;
426
+ isClientRecord: (record: any) => boolean;
427
+ generateDefaults: () => any;
409
428
  }>;
410
429
  type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R extends ResolutionMap<S>, Resolved extends RegistryShape = ResolvedRegistryWithSchemas<S, R> extends RegistryShape ? ResolvedRegistryWithSchemas<S, R> : RegistryShape> = {
411
430
  [K in keyof Resolved]: {
@@ -419,9 +438,9 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
419
438
  transforms: {
420
439
  toClient: (dbData: z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>;
421
440
  toDb: (clientData: z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>;
441
+ parseForDb: (appData: z.input<Resolved[K]["zodSchemas"]["serverSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>;
442
+ parseFromDb: (dbData: Partial<z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>>) => z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>;
422
443
  };
423
- parseForDb: (appData: z.input<Resolved[K]["zodSchemas"]["serverSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>;
424
- parseFromDb: (dbData: Partial<z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>>) => z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>;
425
444
  defaults: Resolved[K]["zodSchemas"]["defaultValues"];
426
445
  stateType: Resolved[K]["zodSchemas"]["stateType"];
427
446
  generateDefaults: () => Resolved[K]["zodSchemas"]["defaultValues"];
@@ -434,20 +453,7 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
434
453
  __registry: Resolved;
435
454
  };
436
455
  };
437
- export declare function createSchemaBox<S extends Record<string, SchemaWithPlaceholders>, R extends ResolutionMap<S>>(schemas: S, resolver: (proxy: SchemaProxy<S>) => R): CreateSchemaBoxReturn<S, R>;
438
- type SchemaProxy<S extends Record<string, SchemaWithPlaceholders>> = {
439
- [K in keyof S]: {
440
- [F in keyof S[K] as F extends "_tableName" ? never : F]: S[K][F] extends {
441
- config: infer Config;
442
- } ? S[K][F] & {
443
- __meta: {
444
- _key: F;
445
- _fieldType: S[K][F];
446
- };
447
- __parentTableType: S[K];
448
- } : S[K][F];
449
- };
450
- };
456
+ export declare function createSchemaBox<S extends Record<string, SchemaWithPlaceholders>, R extends ResolutionMap<S>>(schemas: S, resolutions: R): CreateSchemaBoxReturn<S, R>;
451
457
  type Prettify<T> = {
452
458
  [K in keyof T]: T[K];
453
459
  } & {};
@@ -465,7 +471,13 @@ type GetDbKey<K, Field> = Field extends Reference<infer TGetter> ? ReturnType<TG
465
471
  };
466
472
  } ? string extends F ? K : F : K;
467
473
  type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientSchema" | "zodValidationSchema", Depth extends any[] = []> = Depth["length"] extends 10 ? any : {
468
- [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" ? never : K extends keyof T ? T[K] extends Reference<any> ? Key extends "zodSqlSchema" ? GetDbKey<K, T[K]> : K : T[K] extends {
474
+ [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" ? never : K extends keyof T ? T[K] extends {
475
+ config: {
476
+ sql: {
477
+ sqlOnly: true;
478
+ };
479
+ };
480
+ } ? Key extends "zodSqlSchema" ? GetDbKey<K, T[K]> : never : T[K] extends Reference<any> ? Key extends "zodSqlSchema" ? GetDbKey<K, T[K]> : K : T[K] extends {
469
481
  config: {
470
482
  sql: {
471
483
  type: "hasMany" | "manyToMany" | "hasOne" | "belongsTo";
@@ -482,7 +494,13 @@ type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientSchema" | "zodV
482
494
  } ? ZodSchema : never;
483
495
  };
484
496
  type DeriveDefaults<T, Depth extends any[] = []> = Prettify<Depth["length"] extends 10 ? any : {
485
- [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" ? never : K extends keyof T ? T[K] extends Reference<any> ? K : T[K] extends {
497
+ [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" ? never : K extends keyof T ? T[K] extends {
498
+ config: {
499
+ sql: {
500
+ sqlOnly: true;
501
+ };
502
+ };
503
+ } ? never : T[K] extends Reference<any> ? K : T[K] extends {
486
504
  config: {
487
505
  sql: {
488
506
  type: "hasMany" | "manyToMany" | "hasOne" | "belongsTo";
@@ -494,15 +512,18 @@ type DeriveDefaults<T, Depth extends any[] = []> = Prettify<Depth["length"] exte
494
512
  };
495
513
  } ? D extends () => infer R ? R : D : never : T[K] extends {
496
514
  config: {
497
- zodNewSchema: infer TNew;
498
- zodSqlSchema: infer TSql;
499
515
  zodClientSchema: infer TClient extends z.ZodTypeAny;
500
- initialValue: infer D;
501
516
  };
502
- } ? TNew extends TSql ? z.infer<TClient> : D extends () => infer R ? R : D : never;
517
+ } ? z.infer<TClient> : never;
503
518
  }>;
504
519
  type DeriveStateType<T, Depth extends any[] = []> = Prettify<Depth["length"] extends 10 ? any : {
505
- [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" ? never : K extends keyof T ? T[K] extends Reference<any> ? K : T[K] extends {
520
+ [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" ? never : K extends keyof T ? T[K] extends {
521
+ config: {
522
+ sql: {
523
+ sqlOnly: true;
524
+ };
525
+ };
526
+ } ? never : T[K] extends Reference<any> ? K : T[K] extends {
506
527
  config: {
507
528
  sql: {
508
529
  type: "hasMany" | "manyToMany" | "hasOne" | "belongsTo";
package/dist/schema.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { v4 as uuid } from "uuid";
3
3
  export const isFunction = (fn) => typeof fn === "function";
4
- // Function to create a properly typed current timestamp config
5
4
  export function currentTimeStamp() {
6
5
  return {
7
6
  default: "CURRENT_TIMESTAMP",
@@ -9,9 +8,8 @@ export function currentTimeStamp() {
9
8
  };
10
9
  }
11
10
  export const s = {
12
- initialState: (value) => {
11
+ client: (value) => {
13
12
  const actualValue = isFunction(value) ? value({ uuid }) : value;
14
- // Infer the Zod type from the primitive value
15
13
  let inferredZodType;
16
14
  if (typeof actualValue === "string") {
17
15
  inferredZodType = z.string();
@@ -32,10 +30,9 @@ export const s = {
32
30
  inferredZodType = z.any();
33
31
  }
34
32
  return createBuilder({
35
- stage: "new",
33
+ stage: "client",
36
34
  sqlConfig: null,
37
35
  sqlZod: z.undefined(),
38
- newZod: inferredZodType,
39
36
  initialValue: actualValue,
40
37
  clientZod: inferredZodType,
41
38
  validationZod: inferredZodType,
@@ -56,7 +53,7 @@ export const s = {
56
53
  hasOne: (config) => ({
57
54
  __type: "placeholder-relation",
58
55
  relationType: "hasOne",
59
- defaultConfig: config, // This line is the crucial fix
56
+ defaultConfig: config,
60
57
  }),
61
58
  manyToMany: (config) => ({
62
59
  __type: "placeholder-relation",
@@ -96,7 +93,6 @@ export const s = {
96
93
  stage: "sql",
97
94
  sqlConfig: sqlConfig,
98
95
  sqlZod: sqlZodType,
99
- newZod: sqlZodType,
100
96
  initialValue: inferDefaultFromZod(sqlZodType, sqlConfig),
101
97
  clientZod: sqlZodType,
102
98
  validationZod: sqlZodType,
@@ -109,7 +105,6 @@ function createBuilder(config) {
109
105
  config: {
110
106
  sql: config.sqlConfig,
111
107
  zodSqlSchema: config.sqlZod,
112
- zodNewSchema: config.newZod,
113
108
  initialValue: config.initialValue ||
114
109
  inferDefaultFromZod(config.clientZod, config.sqlConfig),
115
110
  zodClientSchema: config.clientZod,
@@ -117,138 +112,163 @@ function createBuilder(config) {
117
112
  clientTransform: config.clientTransform,
118
113
  validationTransform: config.validationTransform,
119
114
  },
120
- initialState: (options) => {
121
- if (completedStages.has("new")) {
122
- throw new Error("initialState() can only be called once in the chain");
115
+ reference: (fieldGetter) => {
116
+ return createBuilder({
117
+ ...config,
118
+ sqlConfig: {
119
+ ...config.sqlConfig,
120
+ reference: fieldGetter,
121
+ },
122
+ });
123
+ },
124
+ client: (...args) => {
125
+ if (completedStages.has("client")) {
126
+ throw new Error("client() can only be called once in the chain");
127
+ }
128
+ if (completedStages.has("server")) {
129
+ throw new Error("client() must be called before server()");
130
+ }
131
+ const newCompletedStages = new Set(completedStages);
132
+ newCompletedStages.add("client");
133
+ let optionsOrSchema = args[0];
134
+ if (config.stage === "relation") {
135
+ const assert = typeof optionsOrSchema === "function" ||
136
+ optionsOrSchema instanceof z.ZodType
137
+ ? optionsOrSchema
138
+ : optionsOrSchema?.schema;
139
+ return createBuilder({
140
+ ...config,
141
+ stage: "client",
142
+ completedStages: newCompletedStages,
143
+ clientTransform: (baseSchema) => {
144
+ if (isFunction(assert)) {
145
+ return assert({ sql: baseSchema });
146
+ }
147
+ return assert;
148
+ },
149
+ });
150
+ }
151
+ let isDirectShortcut = false;
152
+ let isValueAndSchemaShortcut = false;
153
+ let options = {};
154
+ if (optionsOrSchema !== undefined &&
155
+ typeof optionsOrSchema === "function") {
156
+ if (args.length === 2 && isFunction(args[1])) {
157
+ isValueAndSchemaShortcut = true;
158
+ options = {
159
+ schema: optionsOrSchema,
160
+ value: args[1],
161
+ };
162
+ }
163
+ else {
164
+ options = { schema: optionsOrSchema };
165
+ isDirectShortcut = true;
166
+ }
167
+ }
168
+ else if (optionsOrSchema !== undefined &&
169
+ typeof optionsOrSchema === "object" &&
170
+ !("_def" in optionsOrSchema) &&
171
+ !("parse" in optionsOrSchema) &&
172
+ (optionsOrSchema.value !== undefined ||
173
+ optionsOrSchema.schema !== undefined ||
174
+ optionsOrSchema.clientPk !== undefined)) {
175
+ options = optionsOrSchema;
176
+ }
177
+ else if (optionsOrSchema !== undefined) {
178
+ options = { schema: optionsOrSchema };
179
+ isDirectShortcut = true;
123
180
  }
124
181
  const { value, schema: schemaOrModifier, clientPk } = options;
125
- let actualValue;
182
+ let actualValue = config.initialValue;
126
183
  let finalSchema;
127
- // 1. Determine the actual value
128
184
  if (value !== undefined) {
129
185
  actualValue = isFunction(value) ? value({ uuid }) : value;
130
186
  }
131
187
  else if (schemaOrModifier &&
132
188
  typeof schemaOrModifier === "object" &&
133
189
  "_def" in schemaOrModifier) {
134
- // If only a schema is provided, infer the default from it
135
- actualValue = inferDefaultFromZod(schemaOrModifier, config.sqlConfig);
190
+ if (config.sqlZod instanceof z.ZodUndefined ||
191
+ actualValue === undefined) {
192
+ actualValue = inferDefaultFromZod(schemaOrModifier, config.sqlConfig);
193
+ }
136
194
  }
137
- // 2. Determine the final schema
138
195
  let baseSchema;
139
196
  if (schemaOrModifier &&
140
197
  typeof schemaOrModifier === "object" &&
141
198
  "_def" in schemaOrModifier) {
142
- // A raw Zod schema was passed
143
199
  finalSchema = schemaOrModifier;
144
200
  }
145
201
  else {
146
- // Base schema must be inferred from the value type
147
- if (typeof actualValue === "string")
148
- baseSchema = z.string();
149
- else if (typeof actualValue === "number")
150
- baseSchema = z.number();
151
- else if (typeof actualValue === "boolean")
152
- baseSchema = z.boolean();
153
- else if (actualValue instanceof Date)
154
- baseSchema = z.date();
155
- else if (actualValue === null)
156
- baseSchema = z.null();
157
- else
158
- baseSchema = z.any();
202
+ if (value !== undefined) {
203
+ if (typeof actualValue === "string")
204
+ baseSchema = z.string();
205
+ else if (typeof actualValue === "number")
206
+ baseSchema = z.number();
207
+ else if (typeof actualValue === "boolean")
208
+ baseSchema = z.boolean();
209
+ else if (actualValue instanceof Date)
210
+ baseSchema = z.date();
211
+ else if (actualValue === null)
212
+ baseSchema = z.null();
213
+ else
214
+ baseSchema = z.any();
215
+ }
216
+ else {
217
+ baseSchema = config.clientZod;
218
+ }
159
219
  if (isFunction(schemaOrModifier)) {
160
- // A modifier function was passed
161
- finalSchema = schemaOrModifier(baseSchema);
220
+ if (isDirectShortcut) {
221
+ finalSchema = schemaOrModifier({ sql: config.sqlZod });
222
+ }
223
+ else {
224
+ finalSchema = schemaOrModifier(baseSchema);
225
+ }
162
226
  }
163
227
  else {
164
- // No schema/modifier, use the inferred base schema
165
228
  finalSchema = baseSchema;
166
229
  }
167
230
  }
168
- const newCompletedStages = new Set(completedStages);
169
- newCompletedStages.add("new");
170
231
  const newConfig = { ...config.sqlConfig };
171
232
  if (clientPk !== undefined) {
172
- // Store the boolean OR the function directly into the config
173
233
  newConfig.isClientPk = clientPk;
174
234
  }
175
235
  let clientAndServerSchema;
176
236
  if (clientPk) {
177
- // Always union for clientPk fields
178
237
  clientAndServerSchema = z.union([config.sqlZod, finalSchema]);
179
238
  }
180
- else if (schemaOrModifier) {
181
- // Schema provided without clientPk — use as-is
182
- clientAndServerSchema = finalSchema;
239
+ else if (schemaOrModifier !== undefined) {
240
+ if (isDirectShortcut) {
241
+ clientAndServerSchema = finalSchema;
242
+ }
243
+ else {
244
+ clientAndServerSchema = finalSchema;
245
+ }
183
246
  }
184
247
  else {
185
- // No schema provided — union with SQL type
186
- clientAndServerSchema = z.union([config.sqlZod, finalSchema]);
248
+ if (config.sqlZod instanceof z.ZodUndefined) {
249
+ clientAndServerSchema = finalSchema;
250
+ }
251
+ else {
252
+ clientAndServerSchema = z.union([config.sqlZod, finalSchema]);
253
+ }
187
254
  }
188
255
  return createBuilder({
189
256
  ...config,
190
- stage: "new",
257
+ stage: "client",
191
258
  sqlConfig: newConfig,
192
- newZod: finalSchema,
193
259
  initialValue: actualValue,
194
260
  clientZod: clientAndServerSchema,
195
261
  validationZod: clientAndServerSchema,
196
262
  completedStages: newCompletedStages,
197
263
  });
198
264
  },
199
- reference: (fieldGetter) => {
200
- return createBuilder({
201
- ...config,
202
- sqlConfig: {
203
- ...config.sqlConfig,
204
- reference: fieldGetter,
205
- },
206
- });
207
- },
208
- client: (assert) => {
209
- if (completedStages.has("client")) {
210
- throw new Error("client() can only be called once in the chain");
211
- }
212
- if (completedStages.has("server")) {
213
- throw new Error("client() must be called before validation()");
214
- }
215
- const newCompletedStages = new Set(completedStages);
216
- newCompletedStages.add("client");
217
- if (config.stage === "relation") {
218
- return createBuilder({
219
- ...config,
220
- stage: "client",
221
- completedStages: newCompletedStages,
222
- clientTransform: (baseSchema) => {
223
- if (isFunction(assert)) {
224
- return assert({
225
- sql: baseSchema,
226
- initialState: config.newZod,
227
- });
228
- }
229
- return assert;
230
- },
231
- });
232
- }
233
- const clientSchema = isFunction(assert)
234
- ? assert({ sql: config.sqlZod, initialState: config.newZod })
235
- : assert;
236
- return createBuilder({
237
- ...config,
238
- stage: "client",
239
- clientZod: clientSchema,
240
- validationZod: clientSchema,
241
- completedStages: newCompletedStages,
242
- });
243
- },
244
265
  server: (assert) => {
245
266
  if (completedStages.has("server")) {
246
- throw new Error("validation() can only be called once in the chain");
267
+ throw new Error("server() can only be called once in the chain");
247
268
  }
248
269
  const serverSchema = isFunction(assert)
249
270
  ? assert({
250
271
  sql: config.sqlZod,
251
- initialState: config.newZod,
252
272
  client: config.clientZod,
253
273
  })
254
274
  : assert;
@@ -263,7 +283,7 @@ function createBuilder(config) {
263
283
  },
264
284
  transform: (transforms) => {
265
285
  if (!completedStages.has("server") && !completedStages.has("client")) {
266
- throw new Error("transform() requires at least client() or validation() to be called first");
286
+ throw new Error("transform() requires at least client() or server() to be called first");
267
287
  }
268
288
  return {
269
289
  config: {
@@ -280,7 +300,6 @@ function createBuilder(config) {
280
300
  }
281
301
  export const SchemaWrapperBrand = Symbol("SchemaWrapper");
282
302
  export function schema(schema) {
283
- // Create the enriched schema with all fields
284
303
  const enrichedSchema = {};
285
304
  for (const key in schema) {
286
305
  if (Object.prototype.hasOwnProperty.call(schema, key)) {
@@ -298,6 +317,7 @@ export function schema(schema) {
298
317
  }
299
318
  enrichedSchema[SchemaWrapperBrand] = true;
300
319
  enrichedSchema.__primaryKeySQL = undefined;
320
+ enrichedSchema.__derives = undefined;
301
321
  enrichedSchema.primaryKeySQL = function (definer) {
302
322
  const pkFieldsOnly = {};
303
323
  for (const key in schema) {
@@ -311,6 +331,10 @@ export function schema(schema) {
311
331
  enrichedSchema.__primaryKeySQL = definer(pkFieldsOnly);
312
332
  return enrichedSchema;
313
333
  };
334
+ enrichedSchema.derive = function (derivers) {
335
+ enrichedSchema.__derives = derivers;
336
+ return enrichedSchema;
337
+ };
314
338
  return enrichedSchema;
315
339
  }
316
340
  function inferDefaultFromZod(zodType, sqlConfig) {
@@ -323,10 +347,6 @@ function inferDefaultFromZod(zodType, sqlConfig) {
323
347
  if ("default" in sqlConfig && sqlConfig.default !== undefined) {
324
348
  return sqlConfig.default;
325
349
  }
326
- if (typeof sqlConfig.type === "string" &&
327
- ["hasMany", "hasOne", "belongsTo", "manyToMany"].includes(sqlConfig.type)) {
328
- // ...
329
- }
330
350
  const sqlTypeConfig = sqlConfig;
331
351
  if (sqlTypeConfig.type && !sqlTypeConfig.nullable) {
332
352
  switch (sqlTypeConfig.type) {
@@ -373,13 +393,16 @@ export function createSchema(schema, relations) {
373
393
  const fieldTransforms = {};
374
394
  const clientToDbKeys = {};
375
395
  const dbToClientKeys = {};
396
+ const sqlOnlyDbKeys = new Set();
376
397
  const fullSchema = { ...schema, ...(relations || {}) };
377
398
  let pkKeys = [];
378
399
  let clientPkKeys = [];
400
+ const derives = schema.__derives;
379
401
  for (const key in fullSchema) {
380
402
  const value = fullSchema[key];
381
403
  if (key === "_tableName" ||
382
404
  key.startsWith("__") ||
405
+ key === "derive" ||
383
406
  key === String(SchemaWrapperBrand) ||
384
407
  key === "primaryKeySQL" ||
385
408
  typeof value === "function")
@@ -390,19 +413,29 @@ export function createSchema(schema, relations) {
390
413
  if (targetField && targetField.config) {
391
414
  const config = targetField.config;
392
415
  const dbFieldName = config.sql?.field || key;
393
- clientToDbKeys[key] = dbFieldName;
394
- dbToClientKeys[dbFieldName] = key;
395
416
  sqlFields[dbFieldName] = config.zodSqlSchema;
396
- clientFields[key] = config.zodClientSchema;
397
- serverFields[key] = config.zodValidationSchema;
398
- const initialValueOrFn = config.initialValue;
399
- defaultGenerators[key] = initialValueOrFn;
400
- let rawDefault = isFunction(initialValueOrFn)
401
- ? initialValueOrFn({ uuid })
402
- : initialValueOrFn;
403
- defaultValues[key] = rawDefault;
404
- if (config.transforms) {
405
- fieldTransforms[key] = config.transforms;
417
+ if (config.sql?.sqlOnly) {
418
+ sqlOnlyDbKeys.add(dbFieldName);
419
+ }
420
+ else {
421
+ clientToDbKeys[key] = dbFieldName;
422
+ dbToClientKeys[dbFieldName] = key;
423
+ clientFields[key] = config.zodClientSchema;
424
+ serverFields[key] = config.zodValidationSchema;
425
+ const initialValueOrFn = config.initialValue;
426
+ defaultGenerators[key] = initialValueOrFn;
427
+ let rawDefault = isFunction(initialValueOrFn)
428
+ ? initialValueOrFn({ uuid })
429
+ : initialValueOrFn;
430
+ if (config.transforms?.toClient && rawDefault !== undefined) {
431
+ defaultValues[key] = config.transforms.toClient(rawDefault);
432
+ }
433
+ else {
434
+ defaultValues[key] = rawDefault;
435
+ }
436
+ if (config.transforms) {
437
+ fieldTransforms[key] = config.transforms;
438
+ }
406
439
  }
407
440
  }
408
441
  continue;
@@ -419,11 +452,34 @@ export function createSchema(schema, relations) {
419
452
  ["hasMany", "hasOne", "belongsTo", "manyToMany"].includes(sqlConfig.type)) {
420
453
  continue;
421
454
  }
422
- else {
423
- const dbFieldName = sqlConfig?.field || key;
424
- clientToDbKeys[key] = dbFieldName;
425
- dbToClientKeys[dbFieldName] = key;
455
+ else if (sqlConfig) {
456
+ const dbFieldName = sqlConfig.field || key;
426
457
  sqlFields[dbFieldName] = config.zodSqlSchema;
458
+ if (sqlConfig.sqlOnly) {
459
+ sqlOnlyDbKeys.add(dbFieldName);
460
+ }
461
+ else {
462
+ clientToDbKeys[key] = dbFieldName;
463
+ dbToClientKeys[dbFieldName] = key;
464
+ clientFields[key] = config.zodClientSchema;
465
+ serverFields[key] = config.zodValidationSchema;
466
+ if (config.transforms) {
467
+ fieldTransforms[key] = config.transforms;
468
+ }
469
+ const initialValueOrFn = config.initialValue;
470
+ defaultGenerators[key] = initialValueOrFn;
471
+ let rawDefault = isFunction(initialValueOrFn)
472
+ ? initialValueOrFn({ uuid })
473
+ : initialValueOrFn;
474
+ if (config.transforms?.toClient && rawDefault !== undefined) {
475
+ defaultValues[key] = config.transforms.toClient(rawDefault);
476
+ }
477
+ else {
478
+ defaultValues[key] = rawDefault;
479
+ }
480
+ }
481
+ }
482
+ else {
427
483
  clientFields[key] = config.zodClientSchema;
428
484
  serverFields[key] = config.zodValidationSchema;
429
485
  if (config.transforms) {
@@ -443,7 +499,6 @@ export function createSchema(schema, relations) {
443
499
  }
444
500
  }
445
501
  }
446
- // --- NEW: SMART CHECKER BUILDER ---
447
502
  let isClientRecord = () => false;
448
503
  if (clientPkKeys.length > 0) {
449
504
  const checkers = [];
@@ -453,21 +508,16 @@ export function createSchema(schema, relations) {
453
508
  const dbKey = sqlConfig?.field || key;
454
509
  const isClientPkVal = sqlConfig?.isClientPk;
455
510
  if (typeof isClientPkVal === "function") {
456
- // Explicit checker provided directly in the field!
457
511
  checkers.push({ clientKey: key, dbKey, check: isClientPkVal });
458
512
  }
459
513
  else {
460
- // Fallback auto-detection: If they just passed `true`
461
514
  const initialValueOrFn = field?.config?.initialValue;
462
515
  let sampleValue = initialValueOrFn;
463
- // Safely execute the function once to figure out its return type!
464
516
  if (isFunction(initialValueOrFn)) {
465
517
  try {
466
518
  sampleValue = initialValueOrFn({ uuid });
467
519
  }
468
- catch (e) {
469
- // Ignore if the factory fails with a dummy payload
470
- }
520
+ catch (e) { }
471
521
  }
472
522
  if (sqlConfig?.type === "int" && typeof sampleValue === "string") {
473
523
  checkers.push({
@@ -483,7 +533,6 @@ export function createSchema(schema, relations) {
483
533
  if (!record || typeof record !== "object")
484
534
  return false;
485
535
  return checkers.some(({ clientKey, dbKey, check }) => {
486
- // Look at both the client shape key AND the db shape key safely
487
536
  const val = record[clientKey] !== undefined ? record[clientKey] : record[dbKey];
488
537
  return check(val);
489
538
  });
@@ -501,6 +550,11 @@ export function createSchema(schema, relations) {
501
550
  ? fieldTransforms[key].toClient(rawValue)
502
551
  : rawValue;
503
552
  }
553
+ if (derives) {
554
+ for (const key in derives) {
555
+ freshDefaults[key] = derives[key](freshDefaults);
556
+ }
557
+ }
504
558
  return freshDefaults;
505
559
  };
506
560
  const toClient = (dbObject) => {
@@ -508,12 +562,19 @@ export function createSchema(schema, relations) {
508
562
  for (const dbKey in dbObject) {
509
563
  if (dbObject[dbKey] === undefined)
510
564
  continue;
565
+ if (sqlOnlyDbKeys.has(dbKey))
566
+ continue;
511
567
  const clientKey = dbToClientKeys[dbKey] || dbKey;
512
568
  const transform = fieldTransforms[clientKey]?.toClient;
513
569
  clientObject[clientKey] = transform
514
570
  ? transform(dbObject[dbKey])
515
571
  : dbObject[dbKey];
516
572
  }
573
+ if (derives) {
574
+ for (const key in derives) {
575
+ clientObject[key] = derives[key](clientObject);
576
+ }
577
+ }
517
578
  return clientObject;
518
579
  };
519
580
  const toDb = (clientObject) => {
@@ -615,31 +676,8 @@ function createViewObject(initialRegistryKey, selection, registry, tableNameToRe
615
676
  supportsReconciliation: allTablesSupportsReconciliation,
616
677
  };
617
678
  }
618
- export function createSchemaBox(schemas, resolver) {
619
- const schemaProxy = new Proxy({}, {
620
- get(target, tableName) {
621
- const schema = schemas[tableName];
622
- if (!schema)
623
- return undefined;
624
- return new Proxy({}, {
625
- get(target, fieldName) {
626
- const field = schema[fieldName];
627
- if (field && typeof field === "object") {
628
- return {
629
- ...field,
630
- __meta: {
631
- _key: fieldName,
632
- _fieldType: field,
633
- },
634
- __parentTableType: schema,
635
- };
636
- }
637
- return field;
638
- },
639
- });
640
- },
641
- });
642
- const resolutionConfig = resolver(schemaProxy);
679
+ export function createSchemaBox(schemas, resolutions) {
680
+ const resolutionConfig = resolutions;
643
681
  const resolvedSchemas = schemas;
644
682
  for (const tableName in schemas) {
645
683
  for (const fieldName in schemas[tableName]) {
@@ -694,7 +732,6 @@ export function createSchemaBox(schemas, resolver) {
694
732
  defaultConfig: field.defaultConfig,
695
733
  },
696
734
  sqlZod: zodSchema,
697
- newZod: zodSchema,
698
735
  initialValue,
699
736
  clientZod: zodSchema,
700
737
  validationZod: zodSchema,
@@ -709,6 +746,16 @@ export function createSchemaBox(schemas, resolver) {
709
746
  finalRegistry[tableName] = {
710
747
  rawSchema: resolvedSchemas[tableName],
711
748
  zodSchemas: zodSchemas,
749
+ transforms: {
750
+ toClient: zodSchemas.toClient,
751
+ toDb: zodSchemas.toDb,
752
+ parseForDb: zodSchemas.parseForDb,
753
+ parseFromDb: zodSchemas.parseFromDb,
754
+ },
755
+ pk: zodSchemas.pk,
756
+ clientPk: zodSchemas.clientPk,
757
+ isClientRecord: zodSchemas.isClientRecord,
758
+ generateDefaults: zodSchemas.generateDefaults,
712
759
  };
713
760
  }
714
761
  const createNavProxy = (currentTable, registry) => {
@@ -746,17 +793,17 @@ export function createSchemaBox(schemas, resolver) {
746
793
  server: entry.zodSchemas.serverSchema,
747
794
  },
748
795
  transforms: {
749
- toClient: entry.zodSchemas.toClient,
750
- toDb: entry.zodSchemas.toDb,
796
+ toClient: entry.transforms.toClient,
797
+ toDb: entry.transforms.toDb,
798
+ parseForDb: entry.transforms.parseForDb,
799
+ parseFromDb: entry.transforms.parseFromDb,
751
800
  },
752
- parseForDb: entry.zodSchemas.parseForDb,
753
- parseFromDb: entry.zodSchemas.parseFromDb,
754
- defaults: entry.zodSchemas.defaultValues,
801
+ defaults: entry.generateDefaults(),
755
802
  stateType: entry.zodSchemas.stateType,
756
- generateDefaults: entry.zodSchemas.generateDefaults,
757
- pk: entry.zodSchemas.pk,
758
- clientPk: entry.zodSchemas.clientPk,
759
- isClientRecord: entry.zodSchemas.isClientRecord,
803
+ generateDefaults: entry.generateDefaults,
804
+ pk: entry.pk,
805
+ clientPk: entry.clientPk,
806
+ isClientRecord: entry.isClientRecord,
760
807
  nav: createNavProxy(tableName, finalRegistry),
761
808
  createView: (selection) => {
762
809
  const view = createViewObject(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
@@ -767,7 +814,7 @@ export function createSchemaBox(schemas, resolver) {
767
814
  if (Array.isArray(dbData))
768
815
  return dbData.map((item) => deepToClient(item, currentSelection, currentKey));
769
816
  const regEntry = finalRegistry[currentKey];
770
- const baseMapped = regEntry.zodSchemas.toClient(dbData);
817
+ const baseMapped = { ...regEntry.transforms.toClient(dbData) };
771
818
  if (typeof currentSelection === "object") {
772
819
  for (const relKey in currentSelection) {
773
820
  if (currentSelection[relKey] &&
@@ -784,16 +831,20 @@ export function createSchemaBox(schemas, resolver) {
784
831
  }
785
832
  }
786
833
  }
834
+ if (regEntry.rawSchema.__derives) {
835
+ for (const key in regEntry.rawSchema.__derives) {
836
+ baseMapped[key] = regEntry.rawSchema.__derives[key](baseMapped);
837
+ }
838
+ }
787
839
  return baseMapped;
788
840
  };
789
- // --- NEW: Implement recursive toDb ---
790
841
  const deepToDb = (clientData, currentSelection, currentKey) => {
791
842
  if (!clientData)
792
843
  return clientData;
793
844
  if (Array.isArray(clientData))
794
845
  return clientData.map((item) => deepToDb(item, currentSelection, currentKey));
795
846
  const regEntry = finalRegistry[currentKey];
796
- const baseMapped = regEntry.zodSchemas.toDb(clientData);
847
+ const baseMapped = regEntry.transforms.toDb(clientData);
797
848
  if (typeof currentSelection === "object") {
798
849
  for (const relKey in currentSelection) {
799
850
  if (currentSelection[relKey] &&
@@ -813,7 +864,6 @@ export function createSchemaBox(schemas, resolver) {
813
864
  return baseMapped;
814
865
  };
815
866
  const viewToClient = (dbData) => deepToClient(dbData, selection, tableName);
816
- // --- NEW: View To Db ---
817
867
  const viewToDb = (clientData) => deepToDb(clientData, selection, tableName);
818
868
  return {
819
869
  definition: entry.rawSchema,
@@ -825,16 +875,13 @@ export function createSchemaBox(schemas, resolver) {
825
875
  },
826
876
  transforms: {
827
877
  toClient: viewToClient,
828
- toDb: viewToDb, // <--- UPDATED: now uses the recursive function
829
- },
830
- // --- UPDATED: uses view.server.parse to retain relation arrays/objects instead of stripping them
831
- parseForDb: (appData) => {
832
- const validData = view.server.parse(appData);
833
- return viewToDb(validData);
834
- },
835
- parseFromDb: (dbData) => {
836
- const mapped = viewToClient(dbData);
837
- return view.client.parse(mapped);
878
+ toDb: viewToDb,
879
+ parseForDb: (appData) => {
880
+ return viewToDb(appData);
881
+ },
882
+ parseFromDb: (dbData) => {
883
+ return viewToClient(dbData);
884
+ },
838
885
  },
839
886
  defaults: defaults,
840
887
  pk: entry.zodSchemas.pk,
@@ -863,7 +910,7 @@ function computeViewDefaults(currentRegistryKey, selection, registry, tableNameT
863
910
  return {};
864
911
  }
865
912
  const rawSchema = entry.rawSchema;
866
- const baseDefaults = { ...entry.zodSchemas.defaultValues };
913
+ const baseDefaults = entry.generateDefaults();
867
914
  if (selection === true || typeof selection !== "object") {
868
915
  return baseDefaults;
869
916
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.176",
3
+ "version": "0.5.178",
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",
@@ -38,11 +38,11 @@
38
38
  "dependencies": {
39
39
  "commander": "^13.1.0",
40
40
  "tsx": "^4.19.3",
41
- "uuid": "^9.0.0"
41
+ "uuid": "^11.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^20.10.5",
45
- "@types/uuid": "^9.0.7",
45
+ "@types/uuid": "^11.0.0",
46
46
  "@typescript-eslint/eslint-plugin": "^6.15.0",
47
47
  "@typescript-eslint/parser": "^6.15.0",
48
48
  "eslint": "^8.56.0",