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

@everystack/model

v0.4.1

Published

Declare a table's schema, validation, and authorization once — compiled to migrations, RLS, and handler config

Downloads

1,221

Readme

@everystack/model

Declare a table once — its columns, validation, authorization, and relations — and compile it to Postgres migrations, RLS policies, and handler config. One declaration, three targets, no drift between them.

Install

pnpm add @everystack/model

zod is an optional peer dependency, needed only when a field uses .validate(...).

Declare a model

import { defineModel, field, can, unique, index, check, foreignKey, sql } from '@everystack/model';
import { z } from 'zod';

export const Account = defineModel('accounts', {
  fields: {
    id: field.uuid().primaryKey().defaultRandom(),
    email: field.text().notNull().unique(),
    displayName: field.text().notNull(),
    bio: field.text().nullable().validate(z.string().max(160)),
    status: field.enum('account_status', ['active', 'suspended', 'closed']).notNull().default('active'),
    createdAt: field.timestamptz().notNull().defaultNow(),
  },
  abilities: [
    can('read'),                         // anyone can read
    can('update', { owner: 'id' }),      // you can update your own row
  ],
});

export const Note = defineModel('notes', {
  fields: {
    id: field.uuid().primaryKey().defaultRandom(),
    accountId: field.uuid().notNull().references(() => Account, { onDelete: 'cascade' }),
    title: field.text().notNull(),
    createdAt: field.timestamptz().notNull().defaultNow(),
  },
  abilities: [
    can('read', { owner: 'accountId' }),
    can('create', { owner: 'accountId' }),
    can('update', { owner: 'accountId' }),
    can('delete', { owner: 'accountId' }),
  ],
  constraints: [
    unique('accountId', 'title'),                  // composite unique
    index('createdAt').where(sql`pinned = true`),  // partial index
    check(sql`char_length(title) > 0`),            // table-level CHECK
  ],
});

export const models = [Account, Note];

Fields

field.*() builds a column. The type is the Postgres type; the chained modifiers are the constraints.

| Factory | Postgres type | |---------|---------------| | field.uuid() | uuid | | field.text() | text | | field.varchar(n?) | character varying(n) | | field.integer() | integer | | field.bigint() / field.bigserial() | bigint / bigserial | | field.numeric(p?, s?) | numeric(p, s) | | field.boolean() | boolean | | field.timestamptz() / field.timestamp() | timestamp with[out] time zone | | field.date() | date | | field.jsonb() | jsonb | | field.enum('name', [...]) | a CREATE TYPE … AS ENUM (name is explicit, so db:pull round-trips it) |

Modifiers: .primaryKey(), .notNull() / .nullable(), .unique(), .default(value) / .defaultNow() / .defaultRandom(), .references(() => Other, { onDelete, onUpdate }), .private() (never serialized), .readonly() (never accepted on write), .validate(zodSchema), .renamedFrom('oldField') (turns a drop+add into a data-preserving RENAME COLUMN), and .deprecated() (the contract phase of contraction: the column stays in the database and stays readable — old clients keep working — while new writes are rejected and generated types strike it through; deleting the field later is the actual drop, destructive and gated).

Renaming a whole table works the same way, at the model level: defineModel('articles', { renamedFrom: 'posts', ... }) compiles to one ALTER TABLE … RENAME TO … (data preserved, policies ride along) instead of CREATE plus a held DROP. Both markers are one-shot — remove them once every environment is past the state; a satisfied rename is an inert notice.

.validate(...) does double duty: the SQL-expressible refinements (min/max/length/range/enum) become a Postgres CHECK — the backstop for the SSR-direct write path that never runs app-layer Zod — while richer refinements stay app-layer only.

Constraints

The table-level surface, composed in constraints: [...], for what a column-scoped field() can't carry:

  • unique('a', 'b') — a composite unique constraint.
  • index('a').unique().where(sql\…`)` — a standalone index, optionally unique and/or partial.
  • check(sql\a >= b`)` — a multi-column CHECK.
  • foreignKey(['a', 'b'], () => Other, ['x', 'y'], { onDelete }) — a composite foreign key.

Authorization

can(action, condition) declares one rule (default-deny: no matching can() means denied). Conditions:

  • { owner: 'column' } — row owner, via the JWT sub claim by default (override with userField). Also feeds the handler's rowOwnership IDOR backstop.
  • { role: 'authenticated' } — role-gated (policy TO <role> + grant).
  • { where: 'deleted_at IS NULL' } — a column/scalar predicate, compiled to the policy's USING clause.
  • { sql: … } — a verbatim RLS predicate escape hatch.

These compile to RLS policies and grants (via @everystack/cli's compileTableContract, what db:generate emits). can('manage', …) covers all actions.

Relations

import { belongsTo, hasMany } from '@everystack/model';

defineModel('notes', {
  fields: { /* … */ },
  relations: {
    account: belongsTo(() => Account, 'accountId'),
    comments: hasMany(() => Comment, 'noteId'),
  },
});

Compile

@everystack/model is the declaration surface; @everystack/cli is the compiler. With your models array:

everystack db:generate --stage dev   # diff models vs the live DB → one ordered migration (data + authz)
everystack db:pull     --stage dev   # reverse an existing database into model source (the brownfield on-ramp)

db:generate is non-destructive by default — drops are held back as comments unless you pass --allow-drops. A faithful db:pull then db:generate is a no-op: the declaration and the database it built are the same thing.

Handler config

deriveHandlerConfig(models) compiles the same Models into the authorization config createHandler consumes (exposed tables, hidden/protected columns, relations, row ownership) — so the HTTP handler and the database enforce the same declared rules, with no second source of truth.

import { deriveHandlerConfig } from '@everystack/model';
const handler = createHandler(db, schema, deriveHandlerConfig(models));

Programmatic API (@everystack/cli/model)

The same compiler the CLI uses is importable for your own CI and tests — point it at your live DB and assert the declaration and the database agree, or red-team the deployed authorization:

import {
  compileMigration, generateMigrationSql, introspectSchema, diffSchema,   // data layer
  compileTableContract, introspectContract, diffContracts,                 // authz layer
  buildOwnerProbeSql, evaluateOwnerProbe,                                   // owner-isolation red-team
} from '@everystack/cli/model';
  • DataintrospectSchema(run) reads the whole DB; diffSchema(desired, current) / generateMigrationSql(models, current) produce the structural delta. A green db:generate is this returning empty.
  • AuthzcompileTableContract(model) is the declared RLS/grants; introspectContract(run) is what's live; diffContracts(declared, live) is the drift audit (db:authz:diff).
  • Red-teambuildOwnerProbeSql / evaluateOwnerProbe drive two JWT identities per owner-scoped table to catch IDOR (db:authz:owner).

run is any QueryRunner — a function that executes SQL against your DB (e.g. wrapping an everystack db:query invoke or a direct connection). This is what the example app's dogfood test uses to prove its real Models round-trip against real Postgres.

License

AGPL-3.0-only