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

@masaori/zod-to-entity-definitions

v1.2.0

Published

A library to define data models using Zod with extended metadata and convert them into framework-agnostic Entity Definitions

Downloads

1,202

Readme

zod-to-entity-definitions

A TypeScript library that allows you to define data models using Zod with extended metadata (Primary Key, Foreign Key, Unique) and convert them into framework-agnostic Entity Definitions (ER models).

Features

  • 🔧 Zod Extensions: Add .pk(), .unique(), and .ref() methods to Zod schemas
  • 🏗️ Entity & Struct Factories: Define entities and reusable struct types
  • 🔄 Automatic Generation: Convert Zod schemas to entity definitions and relations
  • 🔒 Type-Safe: Full TypeScript support with strict typing
  • 📦 Framework Agnostic: Generate generic entity definitions usable by any framework
  • Validation: Built-in validation for entity nesting and reference integrity

Installation

Installation

Install the package from npm:

npm install @masaori/zod-to-entity-definitions zod

Quick Start

import { z } from 'zod';
import {
  entity,
  struct,
  generateEntities,
  generateRelations,
} from '@masaori/zod-to-entity-definitions';

// 1. Define a Struct (reusable component)
const Address = struct({
  name: 'AddressStruct',
  description: 'Common address',
  columns: {
    city: z.string(),
    street: z.string(),
  },
});

// 2. Define Entities
const Company = entity({
  name: 'Company',
  columns: {
    id: z.string().pk(),
    name: z.string(),
    address: Address, // Using struct is OK
  },
});

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    email: z.string().email().unique(),
    companyId: z.string().ref(Company), // Reference to Company entity
  },
});

// 3. Generate Entity Definitions
const definitions = generateEntities([Company, User]);

// 4. Generate Relations
const relations = generateRelations(definitions);

console.log(JSON.stringify(definitions, null, 2));
console.log(JSON.stringify(relations, null, 2));

📝 See the examples directory for complete working examples with generated JSON output.

API Reference

Zod Extensions

The library extends Zod schemas with the following methods:

.pk()

Marks a field as the Primary Key.

id: z.string().pk();

.unique()

Marks a field as Unique.

email: z.string().email().unique();

.ref(targetEntity, targetColumn?)

Marks a field as a Foreign Key reference to another entity.

  • targetEntity: Must be a schema created with entity()
  • targetColumn: Optional, defaults to "id"
companyId: z.string().ref(Company);
managerId: z.string().ref(User, 'userId');

Factory Functions

entity(config)

Creates an entity schema with metadata.

type EntityConfig<T extends z.ZodRawShape> = {
  name: string;
  description?: string;
  columns: T;
  // Composite (multi-column) unique constraints. Column names are type-checked against `columns`.
  uniques?: (keyof T & string)[][];
};

struct(config)

Creates a struct schema for reusable components.

type StructConfig<T extends z.ZodRawShape> = {
  name: string;
  description?: string;
  columns: T;
};

Generator Functions

generateEntities(schemas)

Parses Zod schemas and returns an array of EntityDefinition.

Validation Rules:

  1. Entities cannot directly embed other entities (must use .ref())
  2. .ref() must point to valid entity schemas
const definitions = generateEntities([Company, User]);

generateRelations(definitions)

Analyzes entity definitions to construct relation maps.

const relations = generateRelations(definitions);

Type Definitions

EntityDefinition

type EntityDefinition = {
  name: string;
  description?: string;
  properties: EntityPropertyDefinition[];
};

EntityPropertyDefinition

Union type representing different property types:

  • EntityPropertyDefinitionPrimaryKey: Primary key field
  • EntityPropertyDefinitionPrimitive: boolean, number, string, Date
  • EntityPropertyDefinitionTypedStruct: Reference to a struct type
  • EntityPropertyDefinitionReferencedObject: Foreign key reference

EntityRelation

type EntityRelation = {
  entityName: string;
  referTos: EntityRelationReferTo[];
  referredBys: EntityRelationReferredBy[];
};

Advanced Usage

Nullable and Optional Fields

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    nickname: z.string().optional(), // Optional field
    bio: z.string().nullable(), // Nullable field
  },
});

Array Fields

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    tags: z.array(z.string()),
    scores: z.array(z.number()).optional(),
  },
});

Enum Fields

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    role: z.enum(['admin', 'user', 'guest']),
  },
});

Composite Unique Constraints

Single-column uniqueness uses .unique() on the column. For multi-column (composite) uniqueness, declare it at the entity level with uniques. Column names are type-checked against columns.

const CustomerSegmentProductVariantOffering = entity({
  name: 'CustomerSegmentProductVariantOffering',
  columns: {
    id: z.string().pk(),
    productVariantId: z.string().ref(ProductVariant),
    customerSegmentId: z.string().ref(CustomerSegment),
    price: z.number(),
  },
  // (productVariantId, customerSegmentId) must be unique together
  uniques: [['productVariantId', 'customerSegmentId']],
});

The constraint is emitted on the generated EntityDefinition as compositeUniqueConstraints:

{
  "name": "CustomerSegmentProductVariantOffering",
  "properties": [ /* ... */ ],
  "compositeUniqueConstraints": [["productVariantId", "customerSegmentId"]]
}
  • Multiple constraints are supported: uniques: [['a', 'b'], ['c', 'd']].
  • Each group must list at least 2 distinct, existing columns (otherwise generateEntities throws). Use .unique() for single-column uniqueness.
  • When no uniques are declared, compositeUniqueConstraints is omitted from the output (existing consumers are unaffected).

Complex Relations

const Department = entity({
  name: 'Department',
  columns: {
    id: z.string().pk(),
    name: z.string(),
  },
});

const Employee = entity({
  name: 'Employee',
  columns: {
    id: z.string().pk(),
    name: z.string(),
    departmentId: z.string().ref(Department),
  },
});

const Project = entity({
  name: 'Project',
  columns: {
    id: z.string().pk(),
    name: z.string(),
    leadId: z.string().ref(Employee),
    departmentId: z.string().ref(Department),
  },
});

Validation Rules

❌ Entity Nesting (Not Allowed)

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    company: Company, // ❌ Error: Direct entity embedding not allowed
  },
});

✅ Use References Instead

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    companyId: z.string().ref(Company), // ✅ Correct: Use .ref()
  },
});

❌ Invalid References

const Address = struct({
  name: 'Address',
  columns: { city: z.string() },
});

const User = entity({
  name: 'User',
  columns: {
    id: z.string().pk(),
    addressId: z.string().ref(Address), // ❌ Error: Can't reference struct
  },
});

Development

# Install dependencies
pnpm install

# Run tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Type checking
pnpm check-types

# Linting
pnpm lint

# Auto-fix lint issues
pnpm lint:fix

# Format code
pnpm format

# Build
pnpm build

CI/CD

This project uses GitHub Actions for continuous integration and deployment.

Workflows

  1. Commit Message Validation (.github/workflows/commit-check.yml)

    • Runs on all PRs
    • Validates commit messages using Conventional Commits
    • Ensures all commits are linear (no merge commits)
  2. Lint and Test (.github/workflows/lint-test.yml)

    • Runs on all PRs and pushes to main
    • Executes linting, type checking, and tests
    • Must pass before merging
  3. Publish to GitHub Packages (.github/workflows/publish.yml)

    • Manual workflow trigger only (workflow_dispatch)
    • Automatically determines version bump using semantic versioning
    • Publishes to GitHub Packages with automatic changelog generation
    • Version bumps follow Conventional Commits:
      • feat: → minor version bump
      • fix:, docs:, style:, refactor:, test:, build:, ci:, chore: → patch version bump
      • BREAKING CHANGE: → major version bump

Publishing to GitHub Packages

Publishing is automated via GitHub Actions. To publish a new version:

  1. Required tokens: No additional tokens needed!

    • The workflow uses the built-in GITHUB_TOKEN which is automatically provided by GitHub Actions
    • The GITHUB_TOKEN has the necessary permissions to:
      • Write to the repository (create tags, update files)
      • Publish to GitHub Packages
      • Create GitHub releases
    • No manual token configuration required - it works out of the box
  2. Trigger the publish workflow:

    • Go to the Actions tab in GitHub
    • Select "Publish to GitHub Packages" workflow
    • Click "Run workflow" on the main branch
  3. Automated process:

    • The workflow will analyze commit messages since the last release
    • Automatically determine the version bump (major/minor/patch)
    • Update package.json and CHANGELOG.md
    • Create a git tag and GitHub release
    • Publish to GitHub Packages

Commit Message Format

All commits must follow the Conventional Commits specification:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Examples:

  • feat: add new entity validation
  • fix: correct reference resolution
  • docs: update README with examples
  • feat!: remove deprecated API (breaking change)

Pre-publish Checklist

The package includes a prepublishOnly script that automatically runs before publishing:

  • Type checking (pnpm check-types)
  • Linting (pnpm lint)
  • Tests (pnpm test)
  • Build (pnpm build)

All these checks must pass before the package can be published.

License

MIT