cogsbox-shape 0.5.207 → 0.5.209

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
@@ -307,6 +307,28 @@ schema({ ... })
307
307
 
308
308
  **Note**: `parsePatchForDb` uses the base schema (without refinement) since partial data may not satisfy cross-field rules.
309
309
 
310
+ ### Schemas vs Validators
311
+
312
+ Each box entry exposes two sets of Zod schemas:
313
+
314
+ - **`schemas`** — plain `ZodObject` shapes. Always composable with `.pick()`, `.omit()`, `.partial()`, etc. Use these for form field extraction, type inference, and partial validation.
315
+ - **`validators`** — schema + refinements. These are `ZodEffects` when `.refine()` is used, otherwise the same `ZodObject`. Use these for full validation that enforces cross-field rules.
316
+
317
+ ```typescript
318
+ const box = createSchemaBox({ events }, { events: {} });
319
+
320
+ // Base schema — always a ZodObject, always composable
321
+ box.events.schemas.client.pick({ startDate: true, endDate: true }); // works!
322
+
323
+ // Validator — enforces refine rules
324
+ box.events.validators.client.safeParse(data); // runs cross-field checks
325
+
326
+ // Internal transforms use validators automatically
327
+ box.events.transforms.parseForDb(data); // uses validator.server
328
+ ```
329
+
330
+ Why the split? After `.refine()`, Zod wraps the schema in `ZodEffects`, which loses `.shape`, `.pick()`, `.omit()`, and `.partial()`. By keeping the base schema separate from refinements, you can always compose the shape while still enforcing cross-field rules when needed.
331
+
310
332
  ### Schema Object Structure
311
333
 
312
334
  The returned schema object has a clear separation of concerns:
@@ -314,16 +336,17 @@ The returned schema object has a clear separation of concerns:
314
336
  ```typescript
315
337
  const schema = createSchema(mySchema);
316
338
 
317
- schema.schemas; // { sql, client, clientChecked, server } — Zod schemas
339
+ schema.schemas; // { sql, client, clientChecked, server } — ZodObject shapes (composable)
340
+ schema.validators; // { sql, client, clientChecked, server } — with refinements enforced
318
341
  schema.transforms; // { toClient, toDb, parseForDb, parseFromDb } — transformations
319
342
  schema.defaults; // Default values for forms
320
- schema.generateDefaults; // Function to generate fresh client defaults (executes randomizers)
343
+ schema.generateDefaults; // Function to generate fresh client defaults (executes randomizers)
321
344
  schema.pk; // Primary key field names
322
345
  schema.clientPk; // Client-side primary key field names
323
346
  schema.isClientRecord; // Function to check if a record is client-created
324
347
  schema.deriveDependencies; // Derive function dependencies ({ [field]: string[] })
325
348
  schema.refineInfo; // Refinement info ({ groups: RefineEntry[], fieldToGroup: Record<string, number[]> })
326
- ```
349
+ ```
327
350
 
328
351
  ## Using Schemas
329
352
 
@@ -364,8 +387,11 @@ const { toClient, toDb, parseForDb, parseFromDb } = schema.transforms;
364
387
  const [data, setData] = useState(generateDefaults());
365
388
  // { id: "new_a1b2c3d4", name: "", email: "", isActive: true }
366
389
 
367
- // Validate explicitly
368
- const result = server.safeParse(data);
390
+ // Validate explicitly (use validators for refinement enforcement)
391
+ const result = schema.validators.server.safeParse(data);
392
+
393
+ // Or use the base schema for shape operations
394
+ const pickedSchema = schema.schemas.server.pick({ email: true, age: true });
369
395
 
370
396
  // Or handle validation & transformation in a single step!
371
397
  const safeDbRow = parseForDb(data);
@@ -35,8 +35,9 @@ export class TableDB {
35
35
  }
36
36
  }
37
37
  }
38
- const limit = opts?.limit ?? 100;
39
- query = query.limit(limit);
38
+ if (opts?.limit !== undefined) {
39
+ query = query.limit(opts.limit);
40
+ }
40
41
  if (opts?.offset !== undefined) {
41
42
  query = query.offset(opts.offset);
42
43
  }
@@ -27,6 +27,8 @@ function sqlType(dialect, fieldName, tableName, config) {
27
27
  switch (config.type) {
28
28
  case "int":
29
29
  return "INTEGER";
30
+ case "real":
31
+ return "REAL";
30
32
  case "boolean":
31
33
  return "INTEGER";
32
34
  case "varchar":
@@ -45,6 +47,8 @@ function sqlType(dialect, fieldName, tableName, config) {
45
47
  switch (config.type) {
46
48
  case "int":
47
49
  return "INTEGER";
50
+ case "real":
51
+ return "REAL";
48
52
  case "boolean":
49
53
  return "BOOLEAN";
50
54
  case "varchar":
@@ -70,6 +74,8 @@ function sqlType(dialect, fieldName, tableName, config) {
70
74
  switch (config.type) {
71
75
  case "int":
72
76
  return "INTEGER";
77
+ case "real":
78
+ return "DOUBLE";
73
79
  case "boolean":
74
80
  return "TINYINT(1)";
75
81
  case "varchar":
package/dist/schema.d.ts CHANGED
@@ -14,6 +14,10 @@ type SQLTypeConfig = ({
14
14
  type: "int";
15
15
  nullable?: boolean;
16
16
  default?: number;
17
+ } | {
18
+ type: "real";
19
+ nullable?: boolean;
20
+ default?: number;
17
21
  } | {
18
22
  type: "boolean";
19
23
  nullable?: boolean;
@@ -54,11 +58,11 @@ type BaseConfig = {
54
58
  };
55
59
  type SQLToZodType<T extends SQLTypeInput, 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 "enum" ? T extends {
56
60
  values: infer TValues extends readonly [string, ...string[]];
57
- } ? z.ZodNullable<z.ZodType<TValues[number]>> : never : T["type"] extends "int" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "boolean" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
61
+ } ? z.ZodNullable<z.ZodType<TValues[number]>> : never : T["type"] extends "int" | "real" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "boolean" ? z.ZodNullable<z.ZodNumber> : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
58
62
  default: "CURRENT_TIMESTAMP";
59
63
  } ? 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 "enum" ? T extends {
60
64
  values: infer TValues extends readonly [string, ...string[]];
61
- } ? z.ZodType<TValues[number]> : never : T["type"] extends "int" ? z.ZodNumber : T["type"] extends "boolean" ? z.ZodNumber : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
65
+ } ? z.ZodType<TValues[number]> : never : T["type"] extends "int" | "real" ? z.ZodNumber : T["type"] extends "boolean" ? z.ZodNumber : T["type"] extends "date" | "datetime" | "timestamp" ? T extends {
62
66
  default: "CURRENT_TIMESTAMP";
63
67
  } ? TDefault extends true ? never : z.ZodDate : z.ZodDate : never;
64
68
  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;
@@ -336,6 +340,12 @@ export declare function createSchema<T extends {
336
340
  clientSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodClientSchema">>>;
337
341
  clientCheckedSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodClientCheckedSchema">>>;
338
342
  serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodValidationSchema">>>;
343
+ validators: {
344
+ sql: z.ZodTypeAny;
345
+ client: z.ZodTypeAny;
346
+ clientChecked: z.ZodTypeAny;
347
+ server: z.ZodTypeAny;
348
+ };
339
349
  defaultValues: Prettify<DeriveDefaults<TActualSchema>>;
340
350
  stateType: Prettify<DeriveStateType<TActualSchema>>;
341
351
  generateDefaults: () => Prettify<DeriveDefaults<TActualSchema>>;
@@ -400,6 +410,12 @@ type ResolvedRegistryWithSchemas<S extends Record<string, SchemaWithPlaceholders
400
410
  clientSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodClientSchema">>>;
401
411
  clientCheckedSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodClientCheckedSchema">>>;
402
412
  serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodValidationSchema">>>;
413
+ validators: {
414
+ sql: z.ZodTypeAny;
415
+ client: z.ZodTypeAny;
416
+ clientChecked: z.ZodTypeAny;
417
+ server: z.ZodTypeAny;
418
+ };
403
419
  defaultValues: Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
404
420
  stateType: Prettify<DeriveStateType<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
405
421
  };
@@ -511,6 +527,12 @@ export type DeriveViewResult<TTableName extends keyof TRegistry, TSelection, TRe
511
527
  clientChecked: z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "clientCheckedSchema">>;
512
528
  server: z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "serverSchema">>;
513
529
  };
530
+ validators: {
531
+ sql: TRegistry[TTableName]["zodSchemas"]["validators"]["sql"];
532
+ client: TRegistry[TTableName]["zodSchemas"]["validators"]["client"];
533
+ clientChecked: TRegistry[TTableName]["zodSchemas"]["validators"]["clientChecked"];
534
+ server: TRegistry[TTableName]["zodSchemas"]["validators"]["server"];
535
+ };
514
536
  transforms: {
515
537
  toClient: TRegistry[TTableName]["transforms"]["toClient"];
516
538
  toDb: TRegistry[TTableName]["transforms"]["toDb"];
@@ -560,6 +582,12 @@ type RegistryShape = Record<string, {
560
582
  clientSchema: z.ZodObject<any>;
561
583
  clientCheckedSchema: z.ZodObject<any>;
562
584
  serverSchema: z.ZodObject<any>;
585
+ validators: {
586
+ sql: z.ZodTypeAny;
587
+ client: z.ZodTypeAny;
588
+ clientChecked: z.ZodTypeAny;
589
+ server: z.ZodTypeAny;
590
+ };
563
591
  defaultValues: any;
564
592
  stateType: any;
565
593
  deriveDependencies: Record<string, string[]>;
@@ -595,6 +623,12 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
595
623
  clientChecked: Resolved[K]["zodSchemas"]["clientCheckedSchema"];
596
624
  server: Resolved[K]["zodSchemas"]["serverSchema"];
597
625
  };
626
+ validators: {
627
+ sql: Resolved[K]["zodSchemas"]["validators"]["sql"];
628
+ client: Resolved[K]["zodSchemas"]["validators"]["client"];
629
+ clientChecked: Resolved[K]["zodSchemas"]["validators"]["clientChecked"];
630
+ server: Resolved[K]["zodSchemas"]["validators"]["server"];
631
+ };
598
632
  transforms: {
599
633
  toClient: (dbData: z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>;
600
634
  toDb: (clientData: z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>;
package/dist/schema.js CHANGED
@@ -18,6 +18,9 @@ function createSqlBuilder(dialect, sqlConfig) {
18
18
  case "int":
19
19
  baseType = z.number();
20
20
  break;
21
+ case "real":
22
+ baseType = z.number();
23
+ break;
21
24
  case "boolean":
22
25
  baseType = z.number();
23
26
  break;
@@ -467,6 +470,8 @@ function inferDefaultFromZod(zodType, sqlConfig) {
467
470
  return sqlTypeConfig.default ?? sqlTypeConfig.values[0];
468
471
  case "int":
469
472
  return 0;
473
+ case "real":
474
+ return 0;
470
475
  case "boolean":
471
476
  return false;
472
477
  case "date":
@@ -787,10 +792,16 @@ export function createSchema(schema, relations) {
787
792
  deriveDependencies,
788
793
  refineInfo: { groups: refineGroups ?? [], fieldToGroup },
789
794
  isClientRecord,
790
- sqlSchema: refinedSqlSchema,
791
- clientSchema: refinedClientSchema,
792
- clientCheckedSchema: refinedClientCheckedSchema,
793
- serverSchema: refinedValidationSchema,
795
+ sqlSchema: finalSqlSchema,
796
+ clientSchema: finalClientSchema,
797
+ clientCheckedSchema: finalClientCheckedSchema,
798
+ serverSchema: finalValidationSchema,
799
+ validators: {
800
+ sql: refinedSqlSchema,
801
+ client: refinedClientSchema,
802
+ clientChecked: refinedClientCheckedSchema,
803
+ server: refinedValidationSchema,
804
+ },
794
805
  defaultValues: defaultValues,
795
806
  stateType: {},
796
807
  generateDefaults,
@@ -1028,6 +1039,12 @@ export function createSchemaBox(schemas, resolutions) {
1028
1039
  clientChecked: entry.zodSchemas.clientCheckedSchema,
1029
1040
  server: entry.zodSchemas.serverSchema,
1030
1041
  },
1042
+ validators: {
1043
+ sql: entry.zodSchemas.validators.sql,
1044
+ client: entry.zodSchemas.validators.client,
1045
+ clientChecked: entry.zodSchemas.validators.clientChecked,
1046
+ server: entry.zodSchemas.validators.server,
1047
+ },
1031
1048
  transforms: {
1032
1049
  toClient: entry.transforms.toClient,
1033
1050
  toDb: entry.transforms.toDb,
@@ -1172,6 +1189,12 @@ export function createSchemaBox(schemas, resolutions) {
1172
1189
  clientChecked: view.clientChecked,
1173
1190
  server: view.server,
1174
1191
  },
1192
+ validators: {
1193
+ sql: view.sql,
1194
+ client: view.client,
1195
+ clientChecked: view.clientChecked,
1196
+ server: view.server,
1197
+ },
1175
1198
  transforms: {
1176
1199
  toClient: viewToClient,
1177
1200
  toDb: viewToDb,
@@ -31,6 +31,17 @@ describe("Schema Builder Type Tests (with expect-type)", () => {
31
31
  expect(statusField.config.zodSqlSchema.parse("draft")).toBe("draft");
32
32
  expect(() => statusField.config.zodSqlSchema.parse("deleted")).toThrow();
33
33
  });
34
+ it("should correctly type a real (float) field", () => {
35
+ const tempField = s.sqlite({ type: "real" });
36
+ expectTypeOf(tempField.config.zodSqlSchema).toEqualTypeOf();
37
+ expect(tempField.config.zodSqlSchema.parse(3.14)).toBe(3.14);
38
+ });
39
+ it("should correctly type a nullable real field", () => {
40
+ const nullableReal = s.sqlite({ type: "real", nullable: true });
41
+ expectTypeOf(nullableReal.config.zodClientCheckedSchema).toEqualTypeOf();
42
+ expect(nullableReal.config.zodSqlSchema.parse(null)).toBeNull();
43
+ expect(nullableReal.config.zodSqlSchema.parse(2.5)).toBe(2.5);
44
+ });
34
45
  });
35
46
  describe("Chainable Methods", () => {
36
47
  it("should create a union type when .client provides a different type", () => {
@@ -368,7 +379,7 @@ describe("client vs clientChecked schema divergence after .clientCheck()", () =>
368
379
  id: 1, password: "abc", confirmPassword: "def",
369
380
  });
370
381
  expect(clientResult.success).toBe(true);
371
- const clientCheckedResult = box.forms.schemas.clientChecked.safeParse({
382
+ const clientCheckedResult = box.forms.validators.clientChecked.safeParse({
372
383
  id: 1, password: "abc", confirmPassword: "def",
373
384
  });
374
385
  expect(clientCheckedResult.success).toBe(false);
@@ -1204,7 +1215,7 @@ describe("refine", () => {
1204
1215
  }),
1205
1216
  ]);
1206
1217
  const box = createSchemaBox({ forms }, { forms: {} });
1207
- const result = box.forms.schemas.client.safeParse({
1218
+ const result = box.forms.validators.client.safeParse({
1208
1219
  id: 1,
1209
1220
  password: "secret",
1210
1221
  confirmPassword: "different",
@@ -1213,7 +1224,7 @@ describe("refine", () => {
1213
1224
  if (!result.success) {
1214
1225
  expect(result.error.issues[0].message).toBe("Passwords must match");
1215
1226
  }
1216
- const validResult = box.forms.schemas.client.safeParse({
1227
+ const validResult = box.forms.validators.client.safeParse({
1217
1228
  id: 1,
1218
1229
  password: "secret",
1219
1230
  confirmPassword: "secret",
@@ -67,4 +67,32 @@ describe("generateSQL dialect columns", () => {
67
67
  });
68
68
  await expect(withOutputFile((path) => generateSQL({ posts }, path))).rejects.toThrow(/Mixed SQL dialects/);
69
69
  });
70
+ it("generates REAL column for SQLite real type", async () => {
71
+ const measurements = schema({
72
+ _tableName: "measurements",
73
+ id: s.sqlite({ type: "int", pk: true }),
74
+ temperature: s.sqlite({ type: "real" }),
75
+ humidity: s.sqlite({ type: "real", nullable: true }),
76
+ });
77
+ const sql = await withOutputFile((path) => generateSQL({ measurements }, path));
78
+ expect(sql).toContain("id INTEGER PRIMARY KEY");
79
+ expect(sql).toContain("temperature REAL NOT NULL");
80
+ expect(sql).toContain("humidity REAL");
81
+ });
82
+ it("generates REAL for Postgres and DOUBLE for MySQL real type", async () => {
83
+ const pgData = schema({
84
+ _tableName: "readings",
85
+ id: s.postgres({ type: "int", pk: true }),
86
+ value: s.postgres({ type: "real" }),
87
+ });
88
+ const pgSql = await withOutputFile((path) => generateSQL({ pgData }, path));
89
+ expect(pgSql).toContain("value REAL NOT NULL");
90
+ const myData = schema({
91
+ _tableName: "readings",
92
+ id: s.mysql({ type: "int", pk: true }),
93
+ value: s.mysql({ type: "real" }),
94
+ });
95
+ const mySql = await withOutputFile((path) => generateSQL({ myData }, path));
96
+ expect(mySql).toContain("value DOUBLE NOT NULL");
97
+ });
70
98
  });
@@ -32,16 +32,16 @@ describe("refine runtime behavior", () => {
32
32
  ]);
33
33
  return createSchemaBox({ rules }, { rules: {} });
34
34
  }
35
- it("schemas.client catches client refine", () => {
35
+ it("validators.client catches client refine", () => {
36
36
  const box = makeRefinedBox();
37
- const good = box.rules.schemas.client.safeParse({
37
+ const good = box.rules.validators.client.safeParse({
38
38
  id: 1,
39
39
  min: 1,
40
40
  max: 10,
41
41
  label: "x",
42
42
  });
43
43
  expect(good.success).toBe(true);
44
- const bad = box.rules.schemas.client.safeParse({
44
+ const bad = box.rules.validators.client.safeParse({
45
45
  id: 1,
46
46
  min: 10,
47
47
  max: 1,
@@ -52,9 +52,9 @@ describe("refine runtime behavior", () => {
52
52
  expect(bad.error.issues[0].message).toBe("Max must be > min");
53
53
  }
54
54
  });
55
- it("schemas.clientChecked catches client refine", () => {
55
+ it("validators.clientChecked catches client refine", () => {
56
56
  const box = makeRefinedBox();
57
- const bad = box.rules.schemas.clientChecked.safeParse({
57
+ const bad = box.rules.validators.clientChecked.safeParse({
58
58
  id: 1,
59
59
  min: 10,
60
60
  max: 1,
@@ -65,9 +65,9 @@ describe("refine runtime behavior", () => {
65
65
  expect(bad.error.issues[0].message).toBe("Max must be > min");
66
66
  }
67
67
  });
68
- it("schemas.server catches server refine", () => {
68
+ it("validators.server catches server refine", () => {
69
69
  const box = makeRefinedBox();
70
- const bad = box.rules.schemas.server.safeParse({
70
+ const bad = box.rules.validators.server.safeParse({
71
71
  id: 1,
72
72
  min: 1,
73
73
  max: 10,
@@ -78,9 +78,9 @@ describe("refine runtime behavior", () => {
78
78
  expect(bad.error.issues[0].message).toBe("Label required");
79
79
  }
80
80
  });
81
- it("schemas.sql catches server refine", () => {
81
+ it("validators.sql catches server refine", () => {
82
82
  const box = makeRefinedBox();
83
- const bad = box.rules.schemas.sql.safeParse({
83
+ const bad = box.rules.validators.sql.safeParse({
84
84
  id: 1,
85
85
  min: 1,
86
86
  max: 10,
@@ -122,36 +122,18 @@ describe("refine runtime behavior", () => {
122
122
  });
123
123
  expect(good.success).toBe(true);
124
124
  });
125
- it("view keeps leaf refines and prefixes issue paths", () => {
126
- const rules = schema({
127
- _tableName: "rules",
128
- id: s.sqlite({ type: "int", pk: true }),
129
- min: s
130
- .sqlite({ type: "int", field: "minField" })
131
- .client({ value: 0 }),
132
- max: s.sqlite({ type: "int" }).client({ value: 0 }),
133
- }).refine((r) => [
134
- r("clientCheck", (row) => row.min >= row.max
135
- ? { path: ["max"], message: "Max must be > min" }
136
- : undefined, ["min", "max"]),
137
- ]);
138
- const journal = schema({
139
- _tableName: "journal",
140
- id: s.sqlite({ type: "int", pk: true }),
141
- rules: s.hasOne(true),
142
- });
143
- const box = createSchemaBox({ rules, journal }, {
144
- journal: { rules: { fromKey: "id", toKey: rules.id } },
145
- });
146
- const view = box.journal.createView({ rules: true });
147
- const bad = view.schemas.clientChecked.safeParse({
125
+ it("validators enforce refine on direct table schemas", () => {
126
+ const box = makeRefinedBox();
127
+ const bad = box.rules.validators.clientChecked.safeParse({
148
128
  id: 1,
149
- rules: { id: 1, min: 10, max: 1 },
129
+ min: 10,
130
+ max: 1,
131
+ label: "x",
150
132
  });
151
133
  expect(bad.success).toBe(false);
152
134
  if (!bad.success) {
153
135
  expect(bad.error.issues[0]).toMatchObject({
154
- path: ["rules", "max"],
136
+ path: ["max"],
155
137
  message: "Max must be > min",
156
138
  });
157
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.207",
3
+ "version": "0.5.209",
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",