Skip to content

Zero-Drift Type Contracts ​

In traditional full-stack web applications, frontend API types and backend response structures frequently drift out of sync. A renamed backend column or altered enum causes silent runtime errors on the client.

MEGSTAT POS eliminates this class of defects through an end-to-end type contract architecture shared across npm workspaces: @megstat/db, @megstat/contracts, and @megstat/schemas.

The Contract Architecture ​

text
Packages Pipeline:

 packages/db                  packages/contracts               packages/schemas
 ───────────                  ──────────────────               ────────────────
 Prisma Schema                 Prisma Select Constants          Zod Schemas
       │                             │                                │
       ▼                             ▼                                ▼
 Generated Client ──────────▶ Derived Wire<T> Types ─────────▶ Shared Request
   (Model types)               (Dates -> ISO strings)           Validation
                               (Decimals -> numbers)
                                     │                                │
                                     ├────────────────────────────────┤
                                     ▼                                ▼
                           Frontend & Backend Codebases

1. Reusable Select Constants (@megstat/contracts) ​

Prisma queries in backend routes do not use inline object literals. Instead, each domain exports typed Select constants satisfying Prisma model types:

ts
// packages/contracts/src/selects/sales.ts
import type { SaleSelect } from "@megstat/db/types";

export const saleListItemSelect = {
  id: true,
  invoiceNo: true,
  status: true,
  total: true,
  paid: true,
  createdAt: true,
  customer: {
    select: { id: true, name: true, phone: true }
  }
} as const satisfies SaleSelect;

Using as const satisfies <Model>Select ensures that if a database column in schema.prisma is renamed or deleted, @megstat/contracts fails to compile immediately.

2. The Wire<T> Mapped Type ​

Prisma returns instances of Decimal for financial fields and JavaScript Date objects for timestamps. However, HTTP JSON responses serialize these into numbers (or strings) and ISO 8601 strings.

packages/contracts/src/wire.ts defines a recursive mapped type that converts backend runtime types into their exact wire representation:

ts
import type { Decimal, JsonValue } from "@prisma/client/runtime/client";

type WireLeaf = string | number | boolean | bigint | null | undefined;

export type Wire<T> =
  T extends Decimal ? number
  : T extends Date ? string
  : T extends WireLeaf ? T
  : T extends readonly (infer U)[] ? Wire<U>[]
  : T extends JsonValue ? T
  : T extends object ? { [K in keyof T]: Wire<T[K]> }
  : T;

Wire types are derived directly from the select constants:

ts
// packages/contracts/src/wire/sales.ts
import type { Prisma } from "@megstat/db/types";
import type { Wire } from "../wire";
import type { saleListItemSelect } from "../selects/sales";

export type SaleListItemWire = Wire<
  Prisma.SaleGetPayload<{ select: typeof saleListItemSelect }>
>;

3. Resolution Safety Guards ​

Because Prisma generated files can include // @ts-nocheck, a broken @prisma/client path could silently turn Decimal into any. In that scenario, T extends Decimal would match everything, collapsing all wire types to number without warnings.

packages/contracts/src/wire.ts includes compile-time assertions:

ts
type IsAny<T> = 0 extends 1 & T ? true : false;
type Equal<A, B> = (<G>() => G extends A ? 1 : 2) extends (<G>() => G extends B ? 1 : 2) ? true : false;
type Expect<T extends true> = T;

// If @prisma/client stops resolving, this fails compilation:
type _AssertDecimalResolved = Expect<Equal<IsAny<Decimal>, false>>;
type _AssertJsonValueResolved = Expect<Equal<IsAny<JsonValue>, false>>;

4. Shared Zod Schemas (@megstat/schemas) ​

All 92+ request validation schemas live in @megstat/schemas.

  • Backend routes validate inputs using validate("json", schema).
  • Frontend forms validate before dispatch using validateWithSchema(schema, data).
  • Form payloads submit parsed and coerced output (z.coerce.number(), .trim(), and schema defaults).

5. Automatic Wire Serialization (backend/src/http/serialize.ts) ​

To ensure compliance with Wire<T>, all response builders in backend/src/http/response.ts (ok, created, paginated, withData) route payloads through recursive toWire() serialization:

  • Decimal is converted to rounded 2-decimal numbers (round2).
  • Date is converted to ISO strings.
  • Eliminates stringified numbers from API payloads.

MEGSTAT POS — Built for Retail Stores & Multi-Branch Businesses