cogsbox-shape 0.5.155 → 0.5.156

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.
Files changed (2) hide show
  1. package/README.md +54 -66
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -35,7 +35,7 @@ cogsbox-shape introduces a unified flow that mirrors how data moves through your
35
35
  ```
36
36
  Initial State
37
37
  \
38
- SQL ←→ Transform ←→ Client ←→ Validation
38
+ SQL ←→ Transform ←→ Client ←→ Server (Validation)
39
39
  ```
40
40
 
41
41
  This flow ensures type safety at every step while giving you control over transformations.
@@ -59,25 +59,35 @@ const userSchema = schema({
59
59
 
60
60
  ### 2. Initial State - Define Creation Defaults
61
61
 
62
- When creating new records, you often need different types than what's stored in the database:
62
+ When creating new records, you often need different types than what's stored in the database.
63
+
64
+ **Note:** `initialState` takes an object configuration where you provide the runtime `value` and optionally the Zod `schema`.
63
65
 
64
66
  ```typescript
67
+ import { z } from "zod";
68
+
65
69
  const userSchema = schema({
66
70
  _tableName: "users",
67
- id: s.sql({ type: "int", pk: true }).initialState(() => crypto.randomUUID()),
68
71
  // DB stores integers, but new records start with UUID strings
72
+ id: s.sql({ type: "int", pk: true }).initialState({
73
+ value: () => crypto.randomUUID(),
74
+ schema: z.string(),
75
+ }),
69
76
  // This automatically creates a union type: number | string on the client
70
77
  });
71
78
  ```
72
79
 
73
80
  ### 3. Client - Define Client Representation
74
81
 
75
- Transform how data appears to clients:
82
+ Transform how data appears to clients using `.client()`:
76
83
 
77
84
  ```typescript
78
85
  const productSchema = schema({
79
86
  _tableName: "products",
80
- id: s.sql({ type: "int", pk: true }).initialState(() => `tmp_${Date.now()}`),
87
+ id: s.sql({ type: "int", pk: true }).initialState({
88
+ value: () => `tmp_${Date.now()}`,
89
+ schema: z.string(),
90
+ }),
81
91
 
82
92
  price: s
83
93
  .sql({ type: "int" }) // Stored as cents in DB
@@ -89,18 +99,18 @@ const productSchema = schema({
89
99
  });
90
100
  ```
91
101
 
92
- ### 4. Validation - Define Business Rules
102
+ ### 4. Server - Define Validation Rules
93
103
 
94
- Add validation that runs at your client -> server boundary:
104
+ Add validation that runs at your client -> server boundary using `.server()`:
95
105
 
96
106
  ```typescript
97
107
  const userSchema = schema({
98
108
  _tableName: "users",
99
109
  email: s
100
110
  .sql({ type: "varchar", length: 255 })
101
- .validation(({ sql }) => sql.email().toLowerCase()),
111
+ .server(({ sql }) => sql.email().toLowerCase()),
102
112
 
103
- age: s.sql({ type: "int" }).validation(({ sql }) => sql.min(18).max(120)),
113
+ age: s.sql({ type: "int" }).server(({ sql }) => sql.min(18).max(120)),
104
114
  });
105
115
  ```
106
116
 
@@ -159,25 +169,22 @@ const box = createSchemaBox({ users, posts }, (s) => ({
159
169
  }));
160
170
  ```
161
171
 
162
- ### 3. Access Base Schemas and Defaults
172
+ ### 3. Access Base Schemas
163
173
 
164
- Once the box is created, you can access the base schemas (without relations) and their default values.
174
+ By default, the schemas accessed directly on the box **exclude relations**. This prevents circular dependencies and over-fetching.
165
175
 
166
176
  ```typescript
167
177
  // Access the processed schemas for the 'users' table
168
178
  const userSchemas = box.users.schemas;
169
- const userDefaults = box.users.defaults;
170
-
171
- // Type-safe operations
172
- const newUser = userDefaults; // { id: 0, name: '' }
173
179
 
174
180
  // The base schema does NOT include the 'posts' relation
175
- type UserClient = z.infer<typeof userSchemas.client>; // { id: number; name: string; }
181
+ type UserClient = z.infer<typeof userSchemas.client>;
182
+ // { id: number; name: string; }
176
183
  ```
177
184
 
178
185
  ### 4. Create Views to Include Relations
179
186
 
180
- The real power is in creating views to select exactly which relationships to include for a given operation.
187
+ To include relationships, you must explicitly create a view. This ensures you only load the data you need.
181
188
 
182
189
  ```typescript
183
190
  // Create a view that includes the 'posts' for a user
@@ -186,7 +193,7 @@ const userWithPostsView = box.users.createView({
186
193
  });
187
194
 
188
195
  // The type of this view now includes the nested posts
189
- type UserWithPosts = z.infer<typeof userWithPostsView.client>;
196
+ type UserWithPosts = z.infer<typeof userWithPostsView.schemas.client>;
190
197
  // {
191
198
  // id: number;
192
199
  // name: string;
@@ -197,8 +204,8 @@ type UserWithPosts = z.infer<typeof userWithPostsView.client>;
197
204
  // }[];
198
205
  // }
199
206
 
200
- // You can also get default values for the view
201
- const newUserWithPosts = userWithPostsView.defaults;
207
+ // You can also get default values specifically for this view
208
+ const defaults = userWithPostsView.defaults;
202
209
  // { id: 0, name: '', posts: [] }
203
210
  ```
204
211
 
@@ -207,17 +214,19 @@ const newUserWithPosts = userWithPostsView.defaults;
207
214
  Here's a complete example showing the power of the flow:
208
215
 
209
216
  ```typescript
210
- import { s, schema, createSchemaBox, z } from "cogsbox-shape";
217
+ import { s, schema, createSchemaBox } from "cogsbox-shape";
218
+ import { z } from "zod";
211
219
 
212
220
  const users = schema({
213
221
  _tableName: "users",
214
- id: s
215
- .sql({ type: "int", pk: true })
216
- .initialState(() => `user_${crypto.randomUUID()}`),
222
+ id: s.sql({ type: "int", pk: true }).initialState({
223
+ value: () => `user_${crypto.randomUUID()}`,
224
+ schema: z.string(),
225
+ }),
217
226
 
218
227
  email: s
219
228
  .sql({ type: "varchar", length: 255 })
220
- .validation(({ sql }) => sql.email()),
229
+ .server(({ sql }) => sql.email()),
221
230
 
222
231
  metadata: s
223
232
  .sql({ type: "text" })
@@ -227,14 +236,14 @@ const users = schema({
227
236
  theme: z.enum(["light", "dark"]),
228
237
  notifications: z.boolean(),
229
238
  }),
230
- })
239
+ }),
231
240
  )
232
241
  .transform({
233
242
  toClient: (json) => JSON.parse(json),
234
243
  toDb: (obj) => JSON.stringify(obj),
235
244
  }),
236
245
 
237
- posts: s.hasMany({ defaultCount: 0 }), // Default to an empty array
246
+ posts: s.hasMany({ count: 0 }), // Default to an empty array
238
247
  });
239
248
 
240
249
  const posts = schema({
@@ -257,58 +266,38 @@ const box = createSchemaBox({ users, posts }, (s) => ({
257
266
  },
258
267
  }));
259
268
 
260
- // Use a view for our API response
269
+ // 1. Create a View for your API response
261
270
  const userApiView = box.users.createView({ posts: true });
271
+ const { client, server } = userApiView.schemas;
272
+ const { toClient, toDb } = userApiView.transforms;
262
273
 
263
- // Use the schemas from the view
264
- const { clientSchema, validationSchema, defaults, toClient, toDb } =
265
- userApiView;
266
- type UserApiResponse = z.infer<typeof clientSchema>;
267
- // {
268
- // id: string | number;
269
- // email: string;
270
- // metadata: { preferences: { theme: 'light' | 'dark'; notifications: boolean; } };
271
- // posts: { id: number; title: string; published: boolean; authorId: number | string; }[];
272
- // }
273
-
274
- // Create a new user with view-aware defaults
275
- const newUser = defaults;
276
- // newUser.posts is now guaranteed to be an empty array.
277
-
278
- // Validate user input against the view's validation schema
279
- const validated = validationSchema.parse(userInput);
274
+ // 2. Type Inference
275
+ type UserApiResponse = z.infer<typeof client>;
280
276
 
281
- // Transform for database
282
- const dbUser = toDb(validated);
277
+ // 3. Validation
278
+ // Validate user input against the view's server schema
279
+ const validated = server.parse(userInput);
283
280
 
284
- // Transform for API response
285
- const apiUser = toClient(dbUser);
281
+ // 4. Transformation
282
+ const dbUser = toDb(validated); // Ready for SQL
283
+ const apiUser = toClient(dbUser); // Ready for API response
286
284
  ```
287
285
 
288
- ## Why This Approach?
289
-
290
- 1. **Type Safety**: Full TypeScript support with inferred types at every layer.
291
- 2. **Single Source of Truth**: Define your schema once, use it everywhere.
292
- 3. **Explicit Data Loading**: Views encourage explicitly defining the data shape you need, preventing over-fetching.
293
- 4. **Transformation Co-location**: Keep data transformations next to field definitions.
294
- 5. **Progressive Enhancement**: Start simple, add complexity as needed.
295
- 6. **Framework Agnostic**: Works with any TypeScript project.
296
-
297
286
  ## API Reference
298
287
 
299
288
  ### Schema Definition
300
289
 
301
290
  - `s.sql(config)`: Define SQL column type.
302
- - `.initialState(value)`: Set default value for new records.
303
- - `.client(schema)`: Define client-side schema.
304
- - `.validation(schema)`: Add validation rules.
305
- - `.transform(transforms)`: Define bidirectional transformations.
291
+ - `.initialState({ value, schema })`: Set default value for new records and the Zod schema for that state.
292
+ - `.client(schema | fn)`: Define client-side schema.
293
+ - `.server(schema | fn)`: Add validation rules (runs on server before DB insertion).
294
+ - `.transform(transforms)`: Define `toClient` and `toDb` transformations.
306
295
 
307
296
  ### Relationships
308
297
 
309
298
  - `s.reference(getter)`: Create a foreign key reference.
310
299
  - `s.hasMany(config)`: Define one-to-many relationship placeholder.
311
- - `s.hasOne()`: Define one-to-one relationship placeholder.
300
+ - `s.hasOne(config)`: Define one-to-one relationship placeholder.
312
301
  - `s.manyToMany(config)`: Define many-to-many relationship placeholder.
313
302
 
314
303
  ### Schema Processing
@@ -316,10 +305,9 @@ const apiUser = toClient(dbUser);
316
305
  - `schema(definition)`: Create a schema definition.
317
306
  - `createSchemaBox(schemas, resolver)`: The main function to create and resolve a schema registry.
318
307
  - From the box entry (e.g., `box.users`):
319
- - `.schemas`: Access base Zod schemas (sql, client, validation).
308
+ - `.schemas`: Access base Zod schemas (excludes relations).
320
309
  - `.defaults`: Access base default values.
321
- - `.transforms`: Access `toClient` and `toDb` functions for the base schema.
322
- - `.createView(selection)`: Creates a new set of schemas and transforms including the selected relations.
310
+ - `.createView(selection)`: Creates a specific view including selected relations.
323
311
 
324
312
  ## License
325
313
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.155",
3
+ "version": "0.5.156",
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",