cogsbox-shape 0.5.193 → 0.5.195

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { mkdtemp, rm } from "fs/promises";
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { generateSQL } from "../generateSQL.js";
6
+ import { s, schema } from "../schema.js";
7
+ async function withOutputFile(fn) {
8
+ const dir = await mkdtemp(join(tmpdir(), "cogsbox-shape-sql-"));
9
+ try {
10
+ return await fn(join(dir, "schema.sql"));
11
+ }
12
+ finally {
13
+ await rm(dir, { recursive: true, force: true });
14
+ }
15
+ }
16
+ describe("generateSQL dialect columns", () => {
17
+ it("generates SQLite enum columns as text with a check constraint", async () => {
18
+ const posts = schema({
19
+ _tableName: "posts",
20
+ id: s.sqlite({ type: "int", pk: true }),
21
+ status: s.sqlite({
22
+ type: "enum",
23
+ values: ["draft", "published", "archived"],
24
+ default: "draft",
25
+ }),
26
+ });
27
+ const sql = await withOutputFile((path) => generateSQL({ posts }, path));
28
+ expect(sql).toContain("id INTEGER PRIMARY KEY");
29
+ expect(sql).toContain("status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived'))");
30
+ });
31
+ it("generates MySQL enum columns using native ENUM", async () => {
32
+ const posts = schema({
33
+ _tableName: "posts",
34
+ id: s.mysql({ type: "int", pk: true }),
35
+ status: s.mysql({
36
+ type: "enum",
37
+ values: ["draft", "published", "archived"],
38
+ }),
39
+ });
40
+ const sql = await withOutputFile((path) => generateSQL({ posts }, path));
41
+ expect(sql).toContain("id INTEGER PRIMARY KEY AUTO_INCREMENT");
42
+ expect(sql).toContain("status ENUM('draft', 'published', 'archived') NOT NULL");
43
+ });
44
+ it("generates Postgres enum type DDL before table DDL", async () => {
45
+ const posts = schema({
46
+ _tableName: "posts",
47
+ id: s.postgres({ type: "int", pk: true }),
48
+ status: s.postgres({
49
+ type: "enum",
50
+ name: "post_status",
51
+ values: ["draft", "published", "archived"],
52
+ }),
53
+ });
54
+ const sql = await withOutputFile((path) => generateSQL({ posts }, path));
55
+ expect(sql).toContain("CREATE TYPE post_status AS ENUM ('draft', 'published', 'archived');");
56
+ expect(sql).toContain("status post_status NOT NULL");
57
+ expect(sql.indexOf("CREATE TYPE post_status")).toBeLessThan(sql.indexOf("CREATE TABLE posts"));
58
+ });
59
+ it("rejects mixed SQL dialects in the same table", async () => {
60
+ const posts = schema({
61
+ _tableName: "posts",
62
+ id: s.sqlite({ type: "int", pk: true }),
63
+ status: s.mysql({
64
+ type: "enum",
65
+ values: ["draft", "published"],
66
+ }),
67
+ });
68
+ await expect(withOutputFile((path) => generateSQL({ posts }, path))).rejects.toThrow(/Mixed SQL dialects/);
69
+ });
70
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ describe("package exports", () => {
3
+ it("exposes the root cogsbox-shape entry at runtime", async () => {
4
+ const pkg = await import("cogsbox-shape");
5
+ expect(pkg.s).toBeDefined();
6
+ expect(pkg.schema).toBeDefined();
7
+ expect(pkg.createSchemaBox).toBeDefined();
8
+ });
9
+ it("exposes db subpath entries at runtime", async () => {
10
+ const dbPkg = await import("cogsbox-shape/db");
11
+ const sqlitePkg = await import("cogsbox-shape/db/sqlite");
12
+ expect(dbPkg.connect).toBeDefined();
13
+ expect(sqlitePkg.createSqliteDb).toBeDefined();
14
+ });
15
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import z from "zod";
3
+ import { s, schema, createSchemaBox } from "../schema.js";
4
+ describe("refine runtime behavior", () => {
5
+ function makeRefinedBox() {
6
+ const rules = schema({
7
+ _tableName: "rules",
8
+ id: s.sqlite({ type: "int", pk: true }),
9
+ min: s.sqlite({ type: "int", nullable: true }).clientInput({ value: null, schema: z.number().nullable() }),
10
+ max: s.sqlite({ type: "int", nullable: true }).clientInput({ value: null, schema: z.number().nullable() }),
11
+ label: s.sqlite({ type: "varchar" }).clientInput({ value: "" }),
12
+ }).refine({
13
+ client: (row) => {
14
+ if (row.min !== null && row.max !== null && row.min >= row.max) {
15
+ return { path: ["max"], message: "Max must be > min" };
16
+ }
17
+ return undefined;
18
+ },
19
+ server: (row) => {
20
+ if (row.min !== null && row.max !== null && row.min >= row.max) {
21
+ return { path: ["max"], message: "Max must be > min" };
22
+ }
23
+ if (!row.label) {
24
+ return { path: ["label"], message: "Label required" };
25
+ }
26
+ return undefined;
27
+ },
28
+ });
29
+ return createSchemaBox({ rules }, { rules: {} });
30
+ }
31
+ it("schemas.clientInput catches client refine", () => {
32
+ const box = makeRefinedBox();
33
+ const good = box.rules.schemas.clientInput.safeParse({ id: 1, min: 1, max: 10, label: "x" });
34
+ expect(good.success).toBe(true);
35
+ const bad = box.rules.schemas.clientInput.safeParse({ id: 1, min: 10, max: 1, label: "x" });
36
+ expect(bad.success).toBe(false);
37
+ if (!bad.success) {
38
+ expect(bad.error.issues[0].message).toBe("Max must be > min");
39
+ }
40
+ });
41
+ it("schemas.client catches client refine", () => {
42
+ const box = makeRefinedBox();
43
+ const bad = box.rules.schemas.client.safeParse({ id: 1, min: 10, max: 1, label: "x" });
44
+ expect(bad.success).toBe(false);
45
+ if (!bad.success) {
46
+ expect(bad.error.issues[0].message).toBe("Max must be > min");
47
+ }
48
+ });
49
+ it("schemas.server catches server refine", () => {
50
+ const box = makeRefinedBox();
51
+ const bad = box.rules.schemas.server.safeParse({ id: 1, min: 1, max: 10, label: "" });
52
+ expect(bad.success).toBe(false);
53
+ if (!bad.success) {
54
+ expect(bad.error.issues[0].message).toBe("Label required");
55
+ }
56
+ });
57
+ it("schemas.sql catches server refine", () => {
58
+ const box = makeRefinedBox();
59
+ const bad = box.rules.schemas.sql.safeParse({ id: 1, min: 1, max: 10, label: "" });
60
+ expect(bad.success).toBe(false);
61
+ if (!bad.success) {
62
+ expect(bad.error.issues[0].message).toBe("Label required");
63
+ }
64
+ });
65
+ it("parseForDb rejects invalid data via server refine", () => {
66
+ const box = makeRefinedBox();
67
+ expect(() => box.rules.transforms.parseForDb({ id: 1, min: 10, max: 1, label: "" })).toThrow();
68
+ });
69
+ it("parseFromDb rejects invalid DB data via server refine", () => {
70
+ const box = makeRefinedBox();
71
+ expect(() => box.rules.transforms.parseFromDb({ id: 1, min: 1, max: 10, label: "" })).toThrow("Label required");
72
+ });
73
+ it("parsePatchForDb does NOT run refine (uses partial base)", () => {
74
+ const box = makeRefinedBox();
75
+ expect(() => box.rules.transforms.parsePatchForDb({ min: 10, max: 1 })).not.toThrow();
76
+ });
77
+ it("unrefined box has no refine on any schema", () => {
78
+ const rules = schema({
79
+ _tableName: "rules",
80
+ id: s.sqlite({ type: "int", pk: true }),
81
+ min: s.sqlite({ type: "int", nullable: true }).clientInput({ value: null, schema: z.number().nullable() }),
82
+ max: s.sqlite({ type: "int", nullable: true }).clientInput({ value: null, schema: z.number().nullable() }),
83
+ });
84
+ const box = createSchemaBox({ rules }, { rules: {} });
85
+ const good = box.rules.schemas.clientInput.safeParse({ id: 1, min: 10, max: 1 });
86
+ expect(good.success).toBe(true);
87
+ });
88
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.193",
3
+ "version": "0.5.195",
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",
@@ -14,6 +14,7 @@
14
14
  "lint": "eslint src --ext .ts",
15
15
  "format": "prettier --write \"src/**/*.ts\"",
16
16
  "test": "vitest ",
17
+ "typecheck": "tsc --noEmit && tsc --noEmit -p src/vitest/tsconfig.json",
17
18
  "playground": "pnpm --filter cogsbox-shape-playground dev"
18
19
  },
19
20
  "bin": {