jit
Guides

Boundary recipes

Practical schemas for HTTP, forms, environment variables, dates, databases and queues.

HTTP JSON request

const CreateUser = JIT.object({
  name: JIT.string().trim().min(2).max(80),
  email: JIT.string().trim().toLowerCase().email(),
  birthday: JIT.iso.date(),
}).strict();

const parse = JIT.json(CreateUser).parse().compile();
const input = parse(await request.text());

Strict keys catch client/server drift. Keep birthday as a calendar string or decode it to Temporal.PlainDate; do not invent a UTC midnight Date.

HTML form

const Filters = JIT.object({
  query: JIT.string().trim().noEmpty().optional(),
  page: JIT.coerce.number().int32().min(1).default(1),
  active: JIT.coerce.boolean().optional(),
});

Forms encode numbers and booleans as text and commonly use "" for missing controls. Coercion and noEmpty belong at this boundary, not in the internal domain model.

Environment variables

const Environment = JIT.object({
  PORT: JIT.coerce.number().int32().min(1).max(65_535).default(3000),
  LOG_LEVEL: JIT.string()
    .oneOf(["debug", "info", "warn", "error"] as const)
    .default("info"),
  DATABASE_URL: JIT.string().url(),
}).strict();

export const env = Environment.parse(process.env);

Parse once at startup. Do not repeatedly validate environment values in request handlers.

ISO to domain value

const Timestamp = JIT.codec(JIT.iso.datetime({ offset: true }), JIT.date(), {
  decode: (text) => new Date(text),
  encode: (date) => date.toISOString(),
});

Codecs document both directions. Use a one-way pipe only when reverse serialization is not a domain requirement.

Conditional checkout

const Checkout = JIT.object({
  hasDiscount: JIT.boolean(),
  coupon: JIT.string().when("hasDiscount", {
    is: true,
    then: (schema) => schema.required("coupon is required").min(3),
    otherwise: (schema) => schema.optional(),
  }),
});

If discounted and non-discounted checkouts diverge in many fields, model them as a discriminated union instead of stacking conditions.

Public DTO

const PublicUser = User.pick("id", "name", "avatarUrl");
const toPublic = JIT.mapper(User, PublicUser).get("many");

return Response.json(toPublic.many(users));

Destination-schema whitelisting prevents accidental password/token leakage. For very large responses, combine mapping/query projection with chunked JSON.

Repeated analytics batch

const processAdmins = JIT.process(User)
  .binary({ strategy: "dynamic", memoryLayout: "columnar" })
  .filter((q) => q.eq("role", "admin"))
  .select("id", "name")
  .compile();

const result = processAdmins.execute(rows);

Filtered strings retain canonical integer dictionaries. Projection-only unique strings can use identity codes and skip the large lookup Map.

Queue batching

const batches = JIT.query(Events)
  .filter((q) => q.eq("valid", true))
  .chunk(1_000)
  .compileAsyncIterator();

for await (const batch of batches(cursor)) await database.insert(batch);

Chunk size should be chosen from destination limits and measured working-set memory, not copied blindly from this example.

Safe logs

const User = JIT.object({
  id: JIT.string().uuid(),
  email: JIT.string().email().pii("mask"),
  token: JIT.string().pii("redact"),
});

const safeLog = JIT.mask(User);
logger.info(safeLog(user));

Masking is a final logging boundary. It does not replace authorization or data minimization upstream.

On this page