cogsbox-shape 0.5.2

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/dist/schema.js ADDED
@@ -0,0 +1,452 @@
1
+ import { z } from "zod";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import zodToJsonSchema, {} from "zod-to-json-schema";
4
+ export const isFunction = (fn) => typeof fn === "function";
5
+ // Function to create a properly typed current timestamp config
6
+ export function currentTimeStamp() {
7
+ return {
8
+ default: "CURRENT_TIMESTAMP",
9
+ defaultValue: new Date(),
10
+ };
11
+ }
12
+ // Internal type creation helper
13
+ const createClient = ({ sqlConfig, inferredDbType, inferredClientType, baseJsonSchema, serverType, }) => {
14
+ return (assert, defaultValue) => {
15
+ const clientType = isFunction(assert)
16
+ ? assert({
17
+ zod: inferredClientType,
18
+ ...(serverType && { serverType }),
19
+ })
20
+ : assert || inferredClientType;
21
+ // Handle timestamp default
22
+ let finalSqlConfig = sqlConfig;
23
+ let finalDefaultValue = defaultValue;
24
+ if (defaultValue &&
25
+ typeof defaultValue === "object" &&
26
+ "default" in defaultValue &&
27
+ defaultValue.default === "CURRENT_TIMESTAMP") {
28
+ finalSqlConfig = {
29
+ ...sqlConfig,
30
+ default: "CURRENT_TIMESTAMP",
31
+ };
32
+ finalDefaultValue = defaultValue.defaultValue;
33
+ }
34
+ const effectiveDbType = serverType || inferredDbType;
35
+ const clientJsonSchema = zodToJsonSchema(clientType);
36
+ return {
37
+ sql: finalSqlConfig,
38
+ zodDbSchema: effectiveDbType,
39
+ zodClientSchema: clientType,
40
+ jsonSchema: serverType ? clientJsonSchema : baseJsonSchema,
41
+ defaultValue: finalDefaultValue ??
42
+ (serverType
43
+ ? inferDefaultFromZod(serverType)
44
+ : finalDefaultValue),
45
+ transform: (transforms) => ({
46
+ sql: finalSqlConfig,
47
+ zodDbSchema: effectiveDbType,
48
+ zodClientSchema: clientType,
49
+ jsonSchema: serverType ? clientJsonSchema : baseJsonSchema,
50
+ defaultValue: finalDefaultValue,
51
+ toClient: transforms.toClient,
52
+ toDb: transforms.toDb,
53
+ transforms: {
54
+ toClient: transforms.toClient.toString(),
55
+ toDb: transforms.toDb.toString(),
56
+ },
57
+ }),
58
+ };
59
+ };
60
+ };
61
+ export function createTransforms(transforms) {
62
+ return {
63
+ sql: (config) => {
64
+ const base = shape.sql(config);
65
+ return {
66
+ sql: base.sql,
67
+ dbType: base.dbType,
68
+ zodDbSchema: base.zodDbSchema,
69
+ zodClientSchema: base.zodClientSchema,
70
+ client: base.client,
71
+ db: (dbType) => {
72
+ const baseDb = base.db(dbType);
73
+ const transformMethods = Object.entries(transforms).reduce((acc, [key, transform]) => ({
74
+ ...acc,
75
+ [key]: () => ({
76
+ sql: config,
77
+ zodDbSchema: baseDb.zodDbSchema,
78
+ zodClientSchema: z.unknown(),
79
+ toClient: transform.toClient,
80
+ toDb: transform.toDb,
81
+ }),
82
+ }), {});
83
+ return {
84
+ ...baseDb,
85
+ client: Object.assign(baseDb.client, transformMethods),
86
+ };
87
+ },
88
+ };
89
+ },
90
+ };
91
+ }
92
+ export const shape = {
93
+ // Integer fields
94
+ int: (config = {}) => shape.sql({
95
+ type: "int",
96
+ ...config,
97
+ }),
98
+ // String fields with variants
99
+ varchar: (config = {}) => shape.sql({
100
+ type: "varchar",
101
+ ...config,
102
+ }),
103
+ char: (config = {}) => shape.sql({
104
+ type: "char",
105
+ ...config,
106
+ }),
107
+ text: (config = {}) => shape.sql({
108
+ type: "text",
109
+ ...config,
110
+ }),
111
+ longtext: (config = {}) => shape.sql({
112
+ type: "longtext",
113
+ ...config,
114
+ }),
115
+ // Boolean fields
116
+ boolean: (config = {}) => shape.sql({
117
+ type: "boolean",
118
+ ...config,
119
+ }),
120
+ // Date fields
121
+ date: (config = {}) => shape.sql({
122
+ type: "date",
123
+ ...config,
124
+ }),
125
+ datetime: (config = {}) => shape.sql({
126
+ type: "datetime",
127
+ ...config,
128
+ }),
129
+ sql: (sqlConfig) => {
130
+ const inferredDbType = (() => {
131
+ let baseType;
132
+ if (sqlConfig.pk) {
133
+ baseType = z.number(); // DB PKs are always numbers
134
+ }
135
+ else {
136
+ switch (sqlConfig.type) {
137
+ case "int":
138
+ baseType = z.number();
139
+ break;
140
+ case "varchar":
141
+ case "char":
142
+ case "text":
143
+ case "longtext":
144
+ baseType = z.string();
145
+ break;
146
+ case "boolean":
147
+ baseType = z.boolean();
148
+ break;
149
+ case "date":
150
+ case "datetime":
151
+ baseType = z.date();
152
+ break;
153
+ default:
154
+ throw new Error(`Unsupported type: ${sqlConfig}`);
155
+ }
156
+ }
157
+ if (sqlConfig.nullable) {
158
+ baseType = baseType.nullable();
159
+ }
160
+ return baseType;
161
+ })();
162
+ const inferredClientType = (() => {
163
+ let baseType;
164
+ if (sqlConfig.pk) {
165
+ baseType = z.string(); // Client PKs are always strings
166
+ }
167
+ else {
168
+ switch (sqlConfig.type) {
169
+ case "int":
170
+ baseType = z.number();
171
+ break;
172
+ case "varchar":
173
+ case "char":
174
+ case "text":
175
+ case "longtext":
176
+ baseType = z.string();
177
+ break;
178
+ case "boolean":
179
+ baseType = z.boolean();
180
+ break;
181
+ case "date":
182
+ case "datetime":
183
+ if (sqlConfig.default === "CURRENT_TIMESTAMP") {
184
+ baseType = z.date().optional();
185
+ }
186
+ baseType = z.date();
187
+ break;
188
+ default:
189
+ throw new Error(`Unsupported type: ${sqlConfig}`);
190
+ }
191
+ }
192
+ if (sqlConfig.nullable) {
193
+ baseType = baseType.nullable();
194
+ }
195
+ return baseType;
196
+ })();
197
+ // Create JSON Schema version immediately
198
+ const jsonSchema = zodToJsonSchema(inferredDbType);
199
+ return {
200
+ sql: sqlConfig,
201
+ dbType: inferredDbType,
202
+ zodDbSchema: inferredDbType,
203
+ zodClientSchema: inferredClientType,
204
+ jsonSchema,
205
+ defaultValue: inferDefaultFromZod(inferredDbType, sqlConfig),
206
+ client: createClient({
207
+ sqlConfig,
208
+ inferredDbType,
209
+ inferredClientType,
210
+ baseJsonSchema: jsonSchema,
211
+ }),
212
+ db: (assert) => {
213
+ const serverType = assert({ zod: inferredDbType });
214
+ return {
215
+ sql: sqlConfig,
216
+ dbType: serverType,
217
+ zodDbSchema: serverType,
218
+ zodClientSchema: serverType,
219
+ jsonSchema: zodToJsonSchema(serverType),
220
+ defaultValue: inferDefaultFromZod(serverType),
221
+ client: createClient({
222
+ sqlConfig,
223
+ inferredDbType,
224
+ inferredClientType,
225
+ baseJsonSchema: jsonSchema,
226
+ serverType,
227
+ }),
228
+ };
229
+ },
230
+ };
231
+ },
232
+ };
233
+ export function hasMany(config) {
234
+ return () => ({
235
+ type: "hasMany",
236
+ fromKey: config.fromKey,
237
+ toKey: config.toKey(),
238
+ schema: config.schema(),
239
+ defaultCount: config.defaultCount,
240
+ });
241
+ }
242
+ export function hasOne(config) {
243
+ return () => ({
244
+ type: "hasOne",
245
+ fromKey: config.fromKey,
246
+ toKey: config.toKey(),
247
+ schema: config.schema(),
248
+ });
249
+ }
250
+ export function belongsTo(config) {
251
+ return () => ({
252
+ type: "belongsTo",
253
+ fromKey: config.fromKey,
254
+ toKey: config.toKey(),
255
+ schema: config.schema(),
256
+ });
257
+ }
258
+ export function manyToMany(config) {
259
+ return () => ({
260
+ type: "manyToMany",
261
+ fromKey: config.fromKey,
262
+ toKey: config.toKey(),
263
+ schema: config.schema(),
264
+ defaultCount: config.defaultCount,
265
+ });
266
+ }
267
+ function isRelation(value) {
268
+ return (value &&
269
+ typeof value === "object" &&
270
+ "type" in value &&
271
+ "fromKey" in value &&
272
+ "toKey" in value &&
273
+ "schema" in value);
274
+ }
275
+ function inferDefaultFromZod(zodType, sqlConfig) {
276
+ if (sqlConfig?.pk) {
277
+ return uuidv4();
278
+ }
279
+ if (zodType instanceof z.ZodOptional) {
280
+ return undefined;
281
+ }
282
+ if (zodType instanceof z.ZodNullable) {
283
+ return null;
284
+ }
285
+ if (zodType instanceof z.ZodArray) {
286
+ return [];
287
+ }
288
+ if (zodType instanceof z.ZodObject) {
289
+ return {};
290
+ }
291
+ if (zodType instanceof z.ZodString) {
292
+ return "";
293
+ }
294
+ if (zodType instanceof z.ZodNumber) {
295
+ return 0;
296
+ }
297
+ if (zodType instanceof z.ZodBoolean) {
298
+ return false;
299
+ }
300
+ // Check for explicit default last
301
+ if (zodType instanceof z.ZodDefault && zodType._def?.defaultValue) {
302
+ return typeof zodType._def.defaultValue === "function"
303
+ ? zodType._def.defaultValue()
304
+ : zodType._def.defaultValue;
305
+ }
306
+ return undefined;
307
+ }
308
+ // Update reference function
309
+ export function reference(config) {
310
+ return {
311
+ type: "reference",
312
+ to: config.to,
313
+ };
314
+ }
315
+ function createSerializableSchema(schema) {
316
+ const serializableSchema = {
317
+ _tableName: schema._tableName,
318
+ __schemaId: crypto.randomUUID(),
319
+ _syncKey: schema._syncKey
320
+ ? {
321
+ toString: schema._syncKey.toString(),
322
+ }
323
+ : undefined,
324
+ };
325
+ for (const [key, value] of Object.entries(schema)) {
326
+ if (key === "_tableName" || key === "__schemaId")
327
+ continue;
328
+ if (typeof value === "function") {
329
+ const relation = value();
330
+ if (!isRelation(relation)) {
331
+ throw new Error(`Invalid relation for key ${key}`);
332
+ }
333
+ // Call the schema function to get the actual schema
334
+ const childSchema = createSerializableSchema(relation.schema);
335
+ // Get toKey value by calling the function
336
+ const toKeyField = relation.toKey.type === "reference"
337
+ ? relation.toKey.to()
338
+ : relation.toKey;
339
+ const serializedToKey = {
340
+ sql: toKeyField.sql,
341
+ jsonSchema: zodToJsonSchema(toKeyField.zodClientSchema),
342
+ defaultValue: toKeyField.defaultValue,
343
+ };
344
+ serializableSchema[key] = {
345
+ type: "relation",
346
+ relationType: relation.type,
347
+ fromKey: relation.fromKey,
348
+ toKey: serializedToKey,
349
+ schema: childSchema,
350
+ ...(relation.type === "hasMany" && {
351
+ defaultCount: relation.defaultCount,
352
+ }),
353
+ };
354
+ }
355
+ else {
356
+ // Handle regular fields or references (unchanged)
357
+ if (value.type === "reference") {
358
+ const referencedField = value.to();
359
+ const serializedField = {
360
+ sql: referencedField.sql,
361
+ jsonSchema: zodToJsonSchema(referencedField.zodClientSchema),
362
+ defaultValue: referencedField.defaultValue,
363
+ ...(referencedField.toClient &&
364
+ referencedField.toDb && {
365
+ transforms: {
366
+ toClient: referencedField.toClient.toString(),
367
+ toDb: referencedField.toDb.toString(),
368
+ },
369
+ }),
370
+ };
371
+ serializableSchema[key] = serializedField;
372
+ }
373
+ else {
374
+ const serializedField = {
375
+ sql: value.sql,
376
+ jsonSchema: zodToJsonSchema(value.zodClientSchema),
377
+ defaultValue: value.defaultValue,
378
+ ...(value.toClient &&
379
+ value.toDb && {
380
+ transforms: {
381
+ toClient: value.toClient.toString(),
382
+ toDb: value.toDb.toString(),
383
+ },
384
+ }),
385
+ };
386
+ serializableSchema[key] = serializedField;
387
+ }
388
+ }
389
+ }
390
+ return serializableSchema;
391
+ }
392
+ export function createSchema(schema) {
393
+ const serialized = createSerializableSchema(schema);
394
+ const dbFields = {};
395
+ const clientFields = {};
396
+ const defaultValues = {};
397
+ for (const [key, value] of Object.entries(schema)) {
398
+ if (key === "_tableName")
399
+ continue;
400
+ if (typeof value === "function") {
401
+ const relation = value();
402
+ if (!isRelation(relation)) {
403
+ throw new Error(`Invalid relation for key ${key}`);
404
+ }
405
+ const childSchema = createSchema(relation.schema);
406
+ const serializedChildren = createSerializableSchema(relation.schema);
407
+ // Get toKey value by calling the function
408
+ const toKeyField = relation.toKey.type === "reference"
409
+ ? relation.toKey.to()
410
+ : relation.toKey;
411
+ serialized[key] = {
412
+ type: "relation",
413
+ relationType: relation.type,
414
+ fromKey: relation.fromKey,
415
+ toKey: {
416
+ sql: toKeyField.sql,
417
+ jsonSchema: zodToJsonSchema(toKeyField.zodClientSchema),
418
+ defaultValue: toKeyField.defaultValue,
419
+ },
420
+ schema: serializedChildren,
421
+ ...(relation.type === "hasMany" && {
422
+ defaultCount: relation.defaultCount,
423
+ }),
424
+ };
425
+ if (relation.type === "hasMany") {
426
+ dbFields[key] = z.array(z.object(childSchema.dbSchema.shape));
427
+ clientFields[key] = z.array(z.object(childSchema.clientSchema.shape));
428
+ const count = relation.defaultCount || 0;
429
+ defaultValues[key] = Array.from({ length: count }, () => ({
430
+ ...childSchema.defaultValues,
431
+ }));
432
+ }
433
+ else {
434
+ dbFields[key] = z.object(childSchema.dbSchema.shape);
435
+ clientFields[key] = z.object(childSchema.clientSchema.shape);
436
+ defaultValues[key] = childSchema.defaultValues;
437
+ }
438
+ continue;
439
+ }
440
+ dbFields[key] = value.zodDbSchema;
441
+ clientFields[key] = value.zodClientSchema;
442
+ defaultValues[key] =
443
+ value.defaultValue ?? inferDefaultFromZod(value.zodClientSchema);
444
+ }
445
+ return {
446
+ dbSchema: z.object(dbFields),
447
+ clientSchema: z.object(clientFields),
448
+ defaultValues: defaultValues,
449
+ initialValues: () => defaultValues,
450
+ serialized: serialized,
451
+ };
452
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "cogsbox-shape",
3
+ "version": "0.5.2",
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
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "repository": "github:cogsbox/cogsbox-shape",
11
+ "scripts": {
12
+ "build": " tsc",
13
+ "prepublishOnly": "npm run build",
14
+ "lint": "eslint src --ext .ts",
15
+ "format": "prettier --write \"src/**/*.ts\""
16
+ },
17
+ "bin": {
18
+ "cogsbox-shape": "tsx ./dist/cli.js"
19
+ },
20
+ "keywords": [
21
+ "typescript",
22
+ "orm",
23
+ "database",
24
+ "full-stack",
25
+ "type-safe",
26
+ "client-server",
27
+ "zod",
28
+ "validation",
29
+ "cogs",
30
+ "schema",
31
+ "types"
32
+ ],
33
+ "author": "",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "commander": "^13.1.0",
37
+ "ts-node": "^10.9.2",
38
+ "tsx": "^4.19.3",
39
+ "uuid": "^9.0.0",
40
+ "zod": "^3.22.4",
41
+ "zod-to-json-schema": "^3.22.3"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.10.5",
45
+ "@types/uuid": "^9.0.7",
46
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
47
+ "@typescript-eslint/parser": "^6.15.0",
48
+ "eslint": "^8.56.0",
49
+ "prettier": "^3.1.1",
50
+ "typescript": "^5.3.3"
51
+ },
52
+ "peerDependencies": {
53
+ "zod": "^3.22.4"
54
+ },
55
+ "type": "module"
56
+ }