npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cogsbox-shape

v0.5.170

Published

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 r

Readme

cogsbox-shape

[!CAUTION] This library is under active development and the API is rapidly changing. Do not use in production.

Breaking changes are expected between any release. The library is currently in an experimental phase as we work towards a stable v1.0 release.

A TypeScript-first schema declaration and validation library for full-stack applications. Define your database schema once and get type-safe schemas for your database, client, and validation layers with automatic transformations.

Installation

npm install cogsbox-shape
# or
yarn add cogsbox-shape
# or
pnpm add cogsbox-shape

The Problem

In full-stack applications, data flows through multiple layers:

  • Database stores data in SQL types (integers, varchars, etc.)
  • Client needs different types for UI work (booleans instead of 0/1, temp string IDs instead of auto-increment integers)
  • Validation rules differ between client and server boundaries
  • Forms need typed default values that match the client representation

Traditional approaches require defining these layers separately, leading to type mismatches and duplicated logic.

The Shape Flow

Define a field by chaining methods. Each step is optional — use only what you need.

s.sql()  →  .initialState()  →  .client()  →  .server()  →  .transform()

| Method | Purpose | | ---------------------------------- | -------------------------------------------------------------- | | s.sql({ type }) | Database column type. The starting point for every field. | | .initialState({ value, schema }) | Default value and type for new records created on the client. | | .client(fn) | Client-side validation. Overrides the client type if needed. | | .server(fn) | Server-side validation. Stricter rules before database writes. | | .transform({ toClient, toDb }) | Converts between database and client representations. |

1. SQL — Define Your Database Schema

Start with your database reality:

import { s, schema } from "cogsbox-shape";

const userSchema = schema({
  _tableName: "users",
  id: s.sql({ type: "int", pk: true }),
  email: s.sql({ type: "varchar", length: 255 }),
  createdAt: s.sql({ type: "datetime", default: "CURRENT_TIMESTAMP" }),
});

This generates a Zod schema matching your SQL types exactly.

2. Initial State — Defaults for New Records

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.

const userSchema = schema({
  _tableName: "users",
  // DB stores auto-increment integers, but new records need a temp string ID
  id: s.sql({ type: "int", pk: true }).initialState({
    value: () => crypto.randomUUID(),
    schema: z.string(),
  }),
  // Client type becomes: string | number (union of SQL + initialState)
  // Default value: a generated UUID string
});

If the type you pass to .initialState() matches the SQL type, no union is created:

count: s.sql({ type: "int" }).initialState({ value: 0 }),
// Client type: number (no union, same type)
// Default value: 0

3. Client — Client-Side Validation

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.

// Without .client() — the type is inferred automatically
id: s.sql({ type: "int", pk: true }).initialState({
  value: () => crypto.randomUUID(),
  schema: z.string(),
}),
// Client type: string | number
// (string from initialState + number from SQL)

// With .client() — add validation rules
name: s
  .sql({ type: "varchar" })
  .client(({ sql }) => sql.min(2).max(100)),
// Client type: string (with min/max validation)

// With .client() — declare a different type entirely
// Pair with .transform() to convert between them
isActive: s
  .sql({ type: "int" })
  .client(() => z.boolean())
  .transform({
    toClient: (dbValue) => dbValue === 1,
    toDb: (clientValue) => (clientValue ? 1 : 0),
  }),
// Client type: boolean (DB stores 0/1, client works with true/false)

When .client() overrides the type without .initialState(), the default value is inferred from the client schema (e.g., booleanfalse, string"").

4. Server — Server-Side Validation

.server() adds validation rules that run at the server boundary before database writes. It builds on the client schema, adding stricter constraints.

const userSchema = schema({
  _tableName: "users",
  email: s
    .sql({ type: "varchar", length: 255 })
    .server(({ sql }) => sql.email("Invalid email")),

  age: s
    .sql({ type: "int" })
    .server(({ sql }) => sql.min(18, "Must be 18+").max(120)),
});

The callback receives the previous schema in the chain so you can refine it:

name: s
  .sql({ type: "varchar" })
  .client(() => z.string().trim())
  .server(({ client }) => client.min(2, "Too short")),

5. Transform — Convert Between Layers

.transform() defines bidirectional conversion functions. These run on the server when reading from or writing to the database.

status: s
  .sql({ type: "int" })                              // DB: 0 or 1
  .client(() => z.enum(["active", "inactive"]))       // Client: string enum
  .transform({
    toClient: (dbValue) => dbValue === 1 ? "active" : "inactive",
    toDb: (clientValue) => clientValue === "active" ? 1 : 0,
  }),

Transforms are optional — only needed when the client type differs from the SQL type.

Using Schemas

Single Schema with createSchema

For standalone schemas without relationships:

import { s, schema, createSchema } from "cogsbox-shape";

const contactSchema = schema({
  _tableName: "contacts",
  id: s.sql({ type: "int", pk: true }).initialState({
    value: () => `new_${crypto.randomUUID().slice(0, 8)}`,
    schema: z.string(),
  }),
  name: s.sql({ type: "varchar" }).server(({ sql }) => sql.min(2)),
  email: s.sql({ type: "varchar" }).server(({ sql }) => sql.email()),
  isArchived: s
    .sql({ type: "int" })
    .client(() => z.boolean())
    .transform({
      toClient: (val) => val === 1,
      toDb: (val) => (val ? 1 : 0),
    }),
});

const {
  clientSchema, // Zod schema for client-side validation
  validationSchema, // Zod schema with .server() rules
  sqlSchema, // Zod schema matching DB column types
  defaultValues, // Typed defaults matching clientSchema
  toClient, // DB row → client object
  toDb, // Client object → DB row
} = createSchema(contactSchema);

// Use in a form
const [data, setData] = useState(defaultValues);
// { id: "new_a1b2c3d4", name: "", email: "", isArchived: false }

// Validate
const result = validationSchema.safeParse(data);

// Transform (on the server)
const dbRow = toDb(data); // { isArchived: 0, ... }
const clientObj = toClient(row); // { isArchived: true, ... }

Relationships and Views

For schemas with relationships, use createSchemaBox.

1. Define Schemas with Placeholders

import { s, schema, createSchemaBox } from "cogsbox-shape";

const users = schema({
  _tableName: "users",
  id: s.sql({ type: "int", pk: true }),
  name: s.sql({ type: "varchar" }),
  posts: s.hasMany(), // Placeholder — resolved later
});

const posts = schema({
  _tableName: "posts",
  id: s.sql({ type: "int", pk: true }),
  title: s.sql({ type: "varchar" }),
  authorId: s.reference(() => users.id), // Foreign key
});

2. Create the Registry

The createSchemaBox function resolves relationships and gives you a type-safe API:

const box = createSchemaBox({ users, posts }, (s) => ({
  users: {
    posts: { fromKey: "id", toKey: s.posts.authorId },
  },
}));

3. Access Base Schemas

Base schemas exclude relations by default, preventing circular dependencies:

const { schemas, defaults } = box.users;

type UserClient = z.infer<typeof schemas.client>;
// { id: number; name: string; }
// No 'posts' — relations are excluded from base schemas

4. Create Views to Include Relations

Explicitly select which relations to include:

const userWithPosts = box.users.createView({
  posts: true,
});

type UserWithPosts = z.infer<typeof userWithPosts.schemas.client>;
// {
//   id: number;
//   name: string;
//   posts: { id: number; title: string; authorId: number; }[]
// }

const defaults = userWithPosts.defaults;
// { id: 0, name: '', posts: [] }

Complete Example

import { s, schema, createSchemaBox } from "cogsbox-shape";
import { z } from "zod";

const users = schema({
  _tableName: "users",
  id: s.sql({ type: "int", pk: true }).initialState({
    value: () => `user_${crypto.randomUUID()}`,
    schema: z.string(),
  }),
  email: s
    .sql({ type: "varchar", length: 255 })
    .server(({ sql }) => sql.email()),
  isActive: s
    .sql({ type: "int" })
    .client(() => z.boolean())
    .transform({
      toClient: (val) => val === 1,
      toDb: (val) => (val ? 1 : 0),
    }),
  posts: s.hasMany({ count: 0 }),
});

const posts = schema({
  _tableName: "posts",
  id: s.sql({ type: "int", pk: true }),
  title: s.sql({ type: "varchar" }),
  authorId: s.reference(() => users.id),
});

const box = createSchemaBox({ users, posts }, (s) => ({
  users: {
    posts: { fromKey: "id", toKey: s.posts.authorId },
  },
}));

// Base schema (no relations)
const { schemas, defaults, transforms } = box.users;

// View with relations
const userView = box.users.createView({ posts: true });
const { client, server } = userView.schemas;

// Type inference
type User = z.infer<typeof client>;

// Validation
const result = server.safeParse(formData);

// Transformation (server-side)
const dbRow = transforms.toDb(validated);
const apiResponse = transforms.toClient(dbRow);

API Reference

Schema Definition

  • s.sql(config) — Define SQL column type. The starting point for every field.
  • .initialState({ value, schema }) — Set default value for new records. Optionally provide a Zod schema to widen or narrow the client type.
  • .client(schema | fn) — Define the client-side Zod schema for validation. Use when the client type differs from SQL.
  • .server(schema | fn) — Add server-side validation rules. Receives the previous schema in the chain for refinement.
  • .transform({ toClient, toDb }) — Define bidirectional conversion between SQL and client types. Runs on the server.

Relationships

  • s.reference(getter) — Create a foreign key reference to another schema's field.
  • s.hasMany(config) — Declare a one-to-many relationship placeholder.
  • s.hasOne(config) — Declare a one-to-one relationship placeholder.
  • s.manyToMany(config) — Declare a many-to-many relationship placeholder.

Schema Processing

  • schema(definition) — Create a schema definition from field declarations.
  • createSchema(schema) — Process a single schema. Returns clientSchema, validationSchema, sqlSchema, defaultValues, toClient, toDb.
  • createSchemaBox(schemas, resolver) — Process multiple schemas with relationships. Returns a registry with:
    • .schemas — Base Zod schemas (excludes relations).
    • .defaults — Typed default values.
    • .transformstoClient and toDb functions.
    • .createView(selection) — Create a view including selected relations.

License

MIT