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

@parcae/model

v0.8.0

Published

Parcae Model — typed ORM base class with adapter pattern

Readme

@parcae/model

The core Model system for Parcae. Class properties are the schema. Direct property access via Proxy with change tracking, lazy-loading references, and a pluggable adapter pattern that runs the same code on frontend and backend.

Install

npm install @parcae/model

Define a Model

import { Model } from "@parcae/model";

class Post extends Model {
  static type = "post" as const;

  user!: User; // reference -> VARCHAR storing ID
  title: string = ""; // string -> VARCHAR
  body: PostBody = {}; // object -> JSONB
  tags: string[] = []; // array -> JSONB
  published: boolean = false; // boolean -> BOOLEAN
  views: number = 0; // number -> DOUBLE PRECISION
}

No decorators, no separate schema definition, no Zod. The class properties are the schema.

Property Access

The Model constructor returns a Proxy. Data properties read/write to an internal store with automatic change tracking.

const post = await Post.findById("abc");

post.title; // "Hello" — reads from data store
post.title = "Updated"; // change tracked automatically
post.published; // false — typed as boolean

await post.save(); // flushes tracked changes

References

Properties typed as another Model class become lazy-loading proxies. The $ prefix gives raw ID access.

post.user; // User proxy — loads on property access, Suspense-compatible
post.$user; // "user_k8f2m9x" — raw string ID, no loading

// Setting a reference accepts a Model instance or raw ID
post.user = someUser; // extracts someUser.id
post.$user = "user_abc123"; // sets raw ID directly

The reference proxy throws a Promise on first property access for React Suspense integration:

<Suspense fallback={<span>Loading...</span>}>
  <span>{post.user.name}</span>
</Suspense>

Static Query Methods

All query methods go through the global adapter set via Model.use().

// Find by ID
const post = await Post.findById("abc");

// Query builder
const published = await Post.where({ published: true })
  .orderBy("createdAt", "desc")
  .limit(10)
  .find();

// Other entry points
Post.whereIn("id", ["a", "b", "c"]);
Post.whereNot({ published: false });
Post.whereNotIn("status", ["draft", "archived"]);
Post.whereRaw("views > ?", 100);
Post.select("title", "views");
Post.count();

// Convenience: paginated, sorted
Post.basic(25, "createdAt", "desc", 0);

Instance Methods

// Create
const post = Post.create({ title: "New Post" });
post.id; // auto-generated 20-char ID

// Save (insert or update)
await post.save();

// Save with debounce (frontend batching)
post.__debounceMs = 500;
post.title = "A";
post.title = "AB"; // batched into single save
await post.save();

// Atomic JSON Patch (RFC 6902)
await post.patch([
  { op: "replace", path: "/title", value: "Patched" },
  { op: "add", path: "/body/blocks/-", value: { type: "text" } },
]);

// Delete
await post.remove();

// Reload from adapter (skips in-flight changes)
await post.refresh();

// Serialize
post.toJSON(); // { type, id, title, ... }
await post.sanitize(user); // override in subclass to strip fields

Query Chain

The QueryChain<T> interface supports 40+ chainable methods:

Filtering: where, andWhere, orWhere, whereIn, whereNot, whereNotIn, whereNull, whereNotNull, whereBetween, whereRaw, orWhereRaw, orWhereIn, orWhereNull, whereExists

Ordering & Pagination: orderBy, orderByRaw, limit, offset

Selection & Grouping: select, distinct, distinctOn, groupBy, groupByRaw, having, havingRaw

Joins: join, innerJoin, leftJoin, rightJoin

Aggregates: sum, avg, min, max, increment, decrement

Terminal: find(), first(), count()

On the backend, each method directly mutates a Knex query. On the frontend, each method records a serializable QueryStep sent to the server for execution.

Adapter Pattern

The ModelAdapter interface decouples the Model from persistence:

interface ModelAdapter {
  createStore(data): Record<string, any>;
  save(model, changes): Promise<void>;
  remove(model): Promise<void>;
  findById(modelClass, id): Promise<T | null>;
  query(modelClass): QueryChain<T>;
  patch(model, ops): Promise<void>;
}

| Adapter | Store | Persistence | | ----------------- | ----------------------- | ------------------------------- | | FrontendAdapter | Valtio proxy (reactive) | Transport RPC (Socket.IO / SSE) | | BackendAdapter | Plain object | Knex + PostgreSQL |

Set the adapter once at startup:

import { Model } from "@parcae/model";

Model.use(adapter);

FrontendAdapter

Included in this package. Wraps a Transport to handle client-side persistence.

import { FrontendAdapter } from "@parcae/model";

const adapter = new FrontendAdapter(transport);
Model.use(adapter);

The Transport interface is protocol-agnostic:

interface Transport {
  get(path, data?): Promise<any>;
  post(path, data?): Promise<any>;
  put(path, data?): Promise<any>;
  patch(path, data?): Promise<any>;
  delete(path, data?): Promise<any>;
  subscribe?(event, handler): () => void;
  unsubscribe?(event, handler?): void;
  send?(event, ...args): void;
  readonly isConnected?: boolean;
  readonly isLoading?: boolean;
  on?(event, handler): void;
  off?(event, handler?): void;
  disconnect?(): void;
  reconnect?(): Promise<void>;
}

Static Properties

| Property | Type | Description | | ---------- | -------------------- | ------------------------------------------------------------------- | | type | string | Model identifier. Used for table naming and routing. | | path | string? | Custom API path. Defaults to /v1/{type}s. | | scope | ModelScope? | Row-level security rules. | | indexes | IndexDefinition[]? | Database index definitions. | | managed | boolean | false for externally managed tables (e.g. auth). Default: true. | | __schema | SchemaDefinition? | Resolved at startup by RTTIST. Maps properties to column types. |

Type Mapping

| TypeScript | ColumnType | Postgres | | ---------------- | ----------------- | --------------------- | | string | "string" | VARCHAR(2048) | | string (long) | "text" | TEXT | | number (int) | "integer" | INTEGER | | number (float) | "number" | DOUBLE PRECISION | | boolean | "boolean" | BOOLEAN | | Date | "datetime" | TIMESTAMP | | SomeModel | { kind: "ref" } | VARCHAR (foreign key) | | object / array | "json" | JSONB |

Exports

import { Model, generateId, FrontendAdapter } from "@parcae/model";
import type {
  Transport,
  ModelAdapter,
  ModelConstructor,
  ChangeSet,
  QueryChain,
  QueryStep,
  SchemaDefinition,
  ColumnType,
  PrimitiveColumnType,
  IndexDefinition,
  ModelScope,
  ScopeContext,
  ScopeResult,
  ScopeFunction,
  PatchOp,
} from "@parcae/model";

Deep imports also available:

import { FrontendAdapter } from "@parcae/model/adapters/client";
import type { ModelAdapter } from "@parcae/model/adapters/types";

License

MIT