@molecule/api-resource

Infrastructure · resource · API (Node) · v1.0.5 · Apache-2.0

CRUD resource framework

npm install @molecule/api-resource

npm · Source on GitHub

How it works

@molecule/api-resource is a infrastructure package for the API (Node) side (resource).

import { z } from 'zod'

import {
  createRequestHandler,
  create,
  read,
  update,
  del,
  query,
  type MoleculeRequest,
} from '@molecule/api-resource'
import { types } from '@molecule/api-resource'
import { basePropsSchema } from '@molecule/api-resource/schema'

// Every resource schema extends basePropsSchema (id/createdAt/updatedAt).
const userSchema = basePropsSchema.extend({ name: z.string(), email: z.string() })
type UserProps = z.infer<typeof userSchema>

// Each CRUD factory takes the resource descriptor and returns an async
// operation, e.g. ({ props }) or ({ id }) => { statusCode, body }
// One descriptor, typed as Resource, drives every CRUD factory:
const userResource: types.Resource = { name: 'User', tableName: 'users', schema: userSchema }

const createUser = create<UserProps>(userResource)
const updateUser = update<UserProps>(userResource)
// read/del type their descriptor with the concrete props — cast the shared one:
const readUser = read(userResource as types.Resource<UserProps>)
const deleteUser = del(userResource as types.Resource<UserProps>)

// Operations do NOT take (req, res) directly — adapt each one by mapping
// request fields (req.body, req.params.id) to its arguments, then wrap
// with createRequestHandler for Express
const requestHandlerMap = {
  create: createRequestHandler(
    async (req: MoleculeRequest) => await createUser({ props: req.body }),
  ),
  read: createRequestHandler(async (req: MoleculeRequest) => await readUser({ id: req.params.id })),
  // ...
}

Works with: @molecule/api-bond, @molecule/api-database, @molecule/api-i18n, @molecule/api-locales-resource, @molecule/api-utilities-validation

Reference

Base resource patterns for molecule.dev.

Provides CRUD operation factories and request handler utilities for building RESTful API resources. Every @molecule/api-resource-* package (user, project, device, ...) is built on these factories.

A handler returns { statusCode, headers?, body } and NEVER writes to res — not through res.json, not through res.send, not through res.end. createRequestHandler is the wrapper that applies the returned object (res.set(headers), res.status(statusCode), res.send(body)), and a handler returns null only when the request should fall through to next(). Calling a handler directly from your own route is therefore only correct if you apply that return value yourself:

const { statusCode, headers, body } = await handler(req, res)
if (headers) res.set(headers)
res.status(statusCode).send(body)

Monkey-patching res.json (or res.send/res.end) to re-shape a handler's answer intercepts nothing the handler produces, so the request it was meant to answer is left unanswered and hangs.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { z } from 'zod'

import {
  createRequestHandler,
  create,
  read,
  update,
  del,
  query,
  type MoleculeRequest,
} from '@molecule/api-resource'
import { types } from '@molecule/api-resource'
import { basePropsSchema } from '@molecule/api-resource/schema'

// Every resource schema extends basePropsSchema (id/createdAt/updatedAt).
const userSchema = basePropsSchema.extend({ name: z.string(), email: z.string() })
type UserProps = z.infer<typeof userSchema>

// Each CRUD factory takes the resource descriptor and returns an async
// operation, e.g. ({ props }) or ({ id }) => { statusCode, body }
// One descriptor, typed as Resource, drives every CRUD factory:
const userResource: types.Resource = { name: 'User', tableName: 'users', schema: userSchema }

const createUser = create<UserProps>(userResource)
const updateUser = update<UserProps>(userResource)
// read/del type their descriptor with the concrete props — cast the shared one:
const readUser = read(userResource as types.Resource<UserProps>)
const deleteUser = del(userResource as types.Resource<UserProps>)

// Operations do NOT take (req, res) directly — adapt each one by mapping
// request fields (req.body, req.params.id) to its arguments, then wrap
// with createRequestHandler for Express
const requestHandlerMap = {
  create: createRequestHandler(
    async (req: MoleculeRequest) => await createUser({ props: req.body }),
  ),
  read: createRequestHandler(async (req: MoleculeRequest) => await readUser({ id: req.params.id })),
  // ...
}

Type

infrastructure

Installation

npm install @molecule/api-resource @molecule/api-bond @molecule/api-database @molecule/api-i18n @molecule/api-locales-resource @molecule/api-utilities-validation uuid zod

API

Interfaces

Response

Structured response object returned by resource handlers. Contains the HTTP status code, optional headers, and the response body payload.

interface Response {
  /**
   * HTTP status code to return.
   */
  statusCode: number
  /**
   * Optional map of HTTP header keys to values.
   */
  headers?: Record<string, string>
  /**
   * The response payload to return.
   */
  body: any
}

MoleculeRequest

Framework-agnostic HTTP request interface. Structurally compatible with Express's Request — Express objects satisfy this interface without adapters, but the type doesn't depend on Express, allowing use with Fastify, Hono, or Lambda.

params is Record<string, any> on purpose. Express 5 declares ParamsDictionary as { [key: string]: string | string[] } — a wildcard or a repeated parameter yields an array — so Record<string, string> is NOT a supertype of it, and every Express handler passing its own Request to a function declared on this interface (authorization.set(req, …)) failed with TS2345 and was told to cast. This is the same shape body and query already use. A reader that needs one segment as a string checks it (typeof req.params.id === 'string') rather than trusting the annotation; the packages that own the routes do exactly that where it matters (see @molecule/api-bonds-default-express's getParamId).

interface MoleculeRequest {
  body: any
  params: Record<string, any>
  query: Record<string, any>
  headers: Record<string, string | string[] | undefined>
  cookies?: Record<string, string>
  /**
   * Client IP address. Populated by Express (honoring `trust proxy`) and other
   * frameworks; optional because it isn't guaranteed in every adapter. Used for
   * IP-keyed rate limiting on auth endpoints.
   */
  ip?: string
  on?(event: string, listener: (...args: unknown[]) => void): void
}

MoleculeResponse

Framework-agnostic HTTP response interface. Structurally compatible with Express's Response — includes status(), json(), send(), set(), cookie methods, streaming (write), and locals for per-request data sharing.

interface MoleculeResponse {
  status(code: number): this
  json(body: unknown): void
  send(body: unknown): void
  end(): void
  set(headers: Record<string, string>): void
  setHeader(name: string, value: string): void
  cookie(name: string, value: string, options?: Record<string, unknown>): void
  write(chunk: string | Buffer): boolean
  flushHeaders?(): void
  locals: Record<string, any>
}

Query

The available resource query options.

interface Query {
  /**
   * The maximum number of query results.
   */
  limit?: number
  /**
   * An indexed property to order the results by.
   */
  orderBy?: `createdAt` | `updatedAt`
  /**
   * The direction to order the results.
   */
  orderDirection?: `asc` | `desc`
  /**
   * Constrains the results to values less than these properties, when provided.
   */
  before?: {
    /**
     * Constrains the results to resources created before the provided timestamp.
     *
     * Usually an ISO 8601 timestamp.
     */
    createdAt?: string
    /**
     * Constrains the results to resources updated before the provided timestamp.
     *
     * Usually an ISO 8601 timestamp.
     */
    updatedAt?: string
  }
  /**
   * Constrains the results to values greater than these properties, when provided.
   */
  after?: {
    /**
     * Constrains the results to resources created after the provided timestamp.
     *
     * Usually an ISO 8601 timestamp.
     */
    createdAt?: string
    /**
     * Constrains the results to resources updated after the provided timestamp.
     *
     * Usually an ISO 8601 timestamp.
     */
    updatedAt?: string
  }
}

ErrorFallback

Fallback HTTP response used when a caught error is NOT a molecule-tagged error (i.e. a genuinely-unexpected failure).

interface ErrorFallback {
  /** HTTP status for the fallback (typically 500). */
  status: number
  /** Human-readable fallback message (already localized by the caller). */
  message: string
  /** Machine-readable key the frontend maps to a localized message. */
  errorKey: string
}

Types

Handler

A resource handler function that processes a request and returns a structured response object. If it returns null or undefined, the request is passed to the next middleware via next().

type Handler = (
  req: MoleculeRequest,
  res: MoleculeResponse,
) => Promise<Response | null | undefined> | Response | null | undefined
  • req — The incoming HTTP request.
  • res — The HTTP response (available but handlers should return a Response instead of calling res directly).

MoleculeNextFunction

Framework-agnostic next function. Call with no arguments to pass to the next middleware, or with an error to trigger error-handling middleware.

type MoleculeNextFunction = (err?: unknown) => void
  • err — Optional error to forward to error-handling middleware.

MoleculeRequestHandler

Framework-agnostic middleware function signature matching Express's (req, res, next) => void pattern.

type MoleculeRequestHandler = (
  req: MoleculeRequest,
  res: MoleculeResponse,
  next: MoleculeNextFunction,
) => void | Promise<void>

Functions

create(resource)

Creates a handler to create a resource.

Uses the bonded DataStore from @molecule/api-database for database-agnostic operations.

Example usage:

import { create as resourceCreate } from '@molecule/api-resource'
import type * as types from '../../types.js'

export const create = ({ name, tableName, schema }: types.Resource) => {
  const create = resourceCreate({ name, tableName, schema })

  return async (req: MoleculeRequest) => {
    const props = req.body
    return await create({ props })
  }
}
function create({
  name,
  tableName,
  schema,
}: types.Resource<unknown>): ({
  props: createProps,
  id,
}: {
  props: CreateProps
  id?: CreatedProps['id']
}) => Promise<
  | { statusCode: number; body: { error: string; errorKey: string; props?: undefined } }
  | { statusCode: number; body: { props: CreatedProps; error?: undefined; errorKey?: undefined } }
>
  • resource — The resource descriptor.
  • resource.name — Human-readable resource name used in error messages (e.g. 'User').
  • resource.tableName — Database table to insert the new row into.
  • resource.schema — Zod schema to validate the incoming props against.

Returns: A curried async function that accepts { props, id? } and returns a { statusCode, body } response.

createRequestHandler(handler)

Wraps a resource handler into Express-compatible middleware. Calls the handler, then applies statusCode, headers, and body to the response. If the handler returns null/undefined, calls next(). Catches errors and forwards them as i18n-translated error messages.

function createRequestHandler(
  handler: Handler,
): (req: MoleculeRequest, res: MoleculeResponse, next: MoleculeNextFunction) => Promise<void>
  • handler — The resource handler function to wrap.

Returns: An Express-compatible middleware function (req, res, next) => void.

del(resource)

Creates a handler to delete a resource by its id.

Note: We use del everywhere instead of delete since delete is a reserved keyword in JavaScript.

Uses the bonded DataStore from @molecule/api-database for database-agnostic operations.

Example usage:

import { del as resourceDel } from '@molecule/api-resource'
import type * as types from '../../types.js'

export const del = ({ name, tableName, schema }: types.Resource) => {
  const del = resourceDel({ name, tableName, schema })

  return async (req: MoleculeRequest) => {
    const id = req.params.id
    return await del({ id })
  }
}
function del({ name, tableName }: DeletedResource): ({ id }: { id: Props['id'] }) => Promise<
  | { statusCode: number; body: { error: string; errorKey: string; props?: undefined } }
  | {
      statusCode: number
      body: { props: { id: Props['id'] }; error?: undefined; errorKey?: undefined }
    }
>
  • resource — The resource descriptor.
  • resource.name — Human-readable resource name used in error messages (e.g. 'User').
  • resource.tableName — Database table to delete the row from.

Returns: A curried async function that accepts { id } and returns a { statusCode, body } response.

query(resource)

Creates a request handler to be used to query resources.

Uses the bonded DataStore from @molecule/api-database for database-agnostic operations.

function query({
  tableName,
}: QueriedResource): (
  req: MoleculeRequest,
) => Promise<
  | { statusCode: number; body: { error: string; errorKey: string } }
  | { statusCode: number; body: Props[] }
>
  • resource — The resource descriptor.
  • resource.tableName — Database table to query resources from.

Returns: A curried async handler that accepts a MoleculeRequest (with query params for pagination/sorting) and returns a { statusCode, body } response.

read(resource)

Creates a handler to read a resource by its id.

Uses the bonded DataStore from @molecule/api-database for database-agnostic operations.

Example usage:

import { read as resourceRead } from '@molecule/api-resource'
import type * as types from '../../types.js'

export const read = ({ name, tableName, schema }: types.Resource) => {
  const read = resourceRead({ name, tableName, schema })

  return async (req: MoleculeRequest) => {
    const id = req.params.id
    return await read({ id })
  }
}
function read({
  tableName,
}: ReadResource): ({
  id,
  props,
}: {
  id: ReadProps['id']
  props?: ReadProps
}) => Promise<
  | { statusCode: number; body: { props: ReadProps; error?: undefined; errorKey?: undefined } }
  | { statusCode: number; body: { error: string; errorKey: string; props?: undefined } }
>
  • resource — The resource descriptor.
  • resource.tableName — Database table to query the resource from.

Returns: A curried async function that accepts { id, props? } and returns a { statusCode, body } response.

respondError(res, error, fallback)

Respond to a caught handler error, honoring molecule's "tagged error" convention.

A tagged error carries BOTH a numeric statusCode AND a string errorKey — the same contract @molecule/api-server-default-express's error middleware (classifyTaggedError) uses. Providers throw these for expected, user-actionable conditions — e.g. a config-missing throw (503 + 'config.notConfigured') when STRIPE_SECRET_KEY is unset.

Handlers that catch errors MUST funnel through this instead of a blanket res.status(500), so a tagged error surfaces its REAL status + errorKey rather than being flattened to a generic 500. Otherwise the app looks broken at the exact moment the user tries to use the feature — the worst possible time for a payment (the conversion path). A swallowed tag is why the middleware-only fix was insufficient: ~185 handlers catch-and-500, so the tag never reached the middleware.

Tagged errors are logged at warn (expected/actionable, not a server fault); everything else is logged at error and returns the caller's fallback. Requiring BOTH fields is deliberate: it keeps arbitrary library errors that merely carry a .statusCode (e.g. an AWS SDK 403) from being surfaced with a status molecule never chose.

function respondError(res: MoleculeResponse, error: unknown, fallback: ErrorFallback): void
  • res — The response object.
  • error — The caught error.
  • fallback — Status/message/errorKey to use when error isn't a tagged error.

update(resource)

Creates a handler to update a resource by its id and some props.

Uses the bonded DataStore from @molecule/api-database for database-agnostic operations.

Example usage:

import { update as resourceUpdate } from '@molecule/api-resource'
import type * as types from '../../types.js'

export const update = ({ name, tableName, schema }: types.Resource) => {
  const update = resourceUpdate({ name, tableName, schema })

  return async (req: MoleculeRequest) => {
    const id = req.params.id
    const props = req.body
    return await update({ id, props })
  }
}
function update({
  name,
  tableName,
  schema,
}: types.Resource<unknown>): ({
  id,
  props: updateProps,
}: {
  id: UpdatedProps['id'] | types.Props['id']
  props: UpdateProps
}) => Promise<
  | { statusCode: number; body: { error: string; errorKey: string; props?: undefined } }
  | { statusCode: number; body: { props: UpdatedProps; error?: undefined; errorKey?: undefined } }
>
  • resource — The resource descriptor.
  • resource.name — Human-readable resource name used in error messages (e.g. 'User').
  • resource.tableName — Database table to update the row in.
  • resource.schema — Zod schema to validate the incoming props against.

Returns: A curried async function that accepts { id, props } and returns a { statusCode, body } response.

Constants

i18nRegistered

Sentinel value confirming that locale translations for the resource package have been registered (or attempted). Always true after this module loads.

const i18nRegistered: true

querySchema

The resource query Zod schema.

const querySchema: z.ZodObject<
  {
    limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>
    orderBy: z.ZodDefault<z.ZodEnum<{ createdAt: 'createdAt'; updatedAt: 'updatedAt' }>>
    orderDirection: z.ZodDefault<z.ZodEnum<{ asc: 'asc'; desc: 'desc' }>>
    before: z.ZodOptional<
      z.ZodObject<
        { createdAt: z.ZodOptional<z.ZodString>; updatedAt: z.ZodOptional<z.ZodString> },
        z.core.$strip
      >
    >
    after: z.ZodOptional<
      z.ZodObject<
        { createdAt: z.ZodOptional<z.ZodString>; updatedAt: z.ZodOptional<z.ZodString> },
        z.core.$strip
      >
    >
  },
  z.core.$strip
>

Namespaces

schema

Re-exported as a namespace — reach every member below through it (e.g. schema.<name>).

schema.basePropsSchema

Base Zod schema with the three fields every resource row must have: id (UUID), createdAt (ISO datetime), and updatedAt (ISO datetime).

const basePropsSchema: z.ZodObject<
  { id: z.ZodString; createdAt: z.ZodString; updatedAt: z.ZodString },
  z.core.$strip
>
schema.BaseProps

TypeScript type inferred from basePropsSchema, containing id, createdAt, and updatedAt.

type BaseProps = z.infer<typeof basePropsSchema>
schema.responseSchema(propsSchema)

Creates a Zod schema for the standard { statusCode, body } response returned by resource CRUD operations.

function responseSchema(
  propsSchema: T,
): z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>
  • propsSchema — The Zod schema for the resource's props (e.g. userPropsSchema).

Returns: A Zod schema that validates { statusCode: number, body: { props? } | T[] }.

schema.resourceSchema(_propsSchema)

Creates a Zod schema that validates a resource descriptor object ({ name, tableName, schema }).

function resourceSchema(
  _propsSchema: T,
): z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>
  • _propsSchema — The Zod schema for the resource's props (used for type inference only).

Returns: A Zod schema that validates the resource descriptor shape.

schema.validate(schema, data)

Validates data against a Zod schema, throwing an Error with a human-readable message listing all validation failures if invalid.

function validate(
  schema: z.ZodType<T, unknown, z.core.$ZodTypeInternals<T, unknown>>,
  data: unknown,
): T
  • schema — The Zod schema to validate against.
  • data — The data to validate.

Returns: The parsed and typed data if validation succeeds.

schema.safeParse(schema, data)

Validates data against a Zod schema without throwing. Returns a discriminated union: { success: true, data } on success or { success: false, error } on failure.

function safeParse(
  schema: z.ZodType<T, unknown, z.core.$ZodTypeInternals<T, unknown>>,
  data: unknown,
): { success: true; data: T } | { success: false; error: z.ZodError }
  • schema — The Zod schema to validate against.
  • data — The data to validate.

Returns: A discriminated union indicating success with parsed data, or failure with a ZodError.

schema.partial(schema)

Creates a schema where all fields are optional, suitable for validating partial update payloads where only changed fields are sent.

function partial(
  schema: z.ZodObject<T, z.core.$strip>,
): z.ZodObject<{ [K in keyof T]: z.ZodOptional<T[K]> }, z.core.$strip>
  • schema — The Zod object schema to make partial.

Returns: A new Zod schema with all fields marked optional.

schema.pick(schema, keys)

Creates a schema with only the specified fields, useful for create payloads where only a subset of the full props should be provided by the client.

function pick(
  schema: z.ZodObject<T, z.core.$strip>,
  keys: readonly K[],
): z.ZodObject<Pick<T, K>, z.core.$strip>
  • schema — The Zod object schema to pick from.
  • keys — The field names to include in the new schema.

Returns: A new Zod schema containing only the picked fields.

types

Re-exported as a namespace — reach every member below through it (e.g. types.<name>).

types.BaseProps

TypeScript type inferred from basePropsSchema, containing id, createdAt, and updatedAt.

type BaseProps = z.infer<typeof basePropsSchema>
types.Props

The resource's properties to be stored in the database.

Used by CRUD operations for type inference.

interface Props {
  /**
   * Usually a UUID.
   */
  id: string
  /**
   * When the resource was created.
   *
   * Usually an ISO 8601 timestamp.
   */
  createdAt: string
  /**
   * When the resource was last updated.
   *
   * Usually an ISO 8601 timestamp.
   */
  updatedAt: string
}
types.Resource

An object describing the resource.

The schema can be a Zod schema (z.ZodTypeAny) for new code, or any other schema type for backward compatibility.

interface Resource<T = unknown> {
  /**
   * The name of the resource.
   *
   * @example User
   */
  name: string
  /**
   * The database table name for the resource.
   *
   * @example users
   */
  tableName: string
  /**
   * The schema used when validating props.
   *
   * Can be a Zod schema (z.ZodTypeAny) or other schema formats.
   */
  schema: T
}
types.ZodResource

An object describing the resource with a Zod schema.

Use this type when you want to enforce Zod schema usage.

interface ZodResource<T extends z.ZodTypeAny = z.ZodTypeAny> {
  name: string
  tableName: string
  schema: T
}
types.Response

Standard response from resource operations.

interface Response<T = unknown> {
  /**
   * HTTP status code to return.
   */
  statusCode: number
  /**
   * The response payload.
   */
  body:
    | {
        props?: T
        error?: string
        errorKey?: string
      }
    | T[]
}

utilities

Re-exported as a namespace — reach every member below through it (e.g. utilities.<name>).

utilities.ZodError

An Error-like class used to store Zod validation issues.

interface ZodError<T = unknown> extends $ZodError<T> {
  /** @deprecated Use the `z.treeifyError(err)` function instead. */
  format(): core.$ZodFormattedError<T>
  format<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFormattedError<T, U>
  /** @deprecated Use the `z.treeifyError(err)` function instead. */
  flatten(): core.$ZodFlattenedError<T>
  flatten<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFlattenedError<T, U>
  /** @deprecated Push directly to `.issues` instead. */
  addIssue(issue: core.$ZodIssue): void
  /** @deprecated Push directly to `.issues` instead. */
  addIssues(issues: core.$ZodIssue[]): void
  /** @deprecated Check `err.issues.length === 0` instead. */
  isEmpty: boolean
}
utilities.ZodSchema
interface ZodType<
  out Output = unknown,
  out Input = unknown,
  out Internals extends core.$ZodTypeInternals<Output, Input> = core.$ZodTypeInternals<
    Output,
    Input
  >,
> extends core.$ZodType<Output, Input, Internals> {
  def: Internals['def']
  type: Internals['def']['type']
  /** @deprecated Use `.def` instead. */
  _def: Internals['def']
  /** @deprecated Use `z.output<typeof schema>` instead. */
  _output: Internals['output']
  /** @deprecated Use `z.input<typeof schema>` instead. */
  _input: Internals['input']
  '~standard': ZodStandardSchemaWithJSON<this>
  /** Converts this schema to a JSON Schema representation. */
  toJSONSchema(params?: core.ToJSONSchemaParams): core.ZodStandardJSONSchemaPayload<this>
  check(...checks: (core.CheckFn<core.output<this>> | core.$ZodCheck<core.output<this>>)[]): this
  with(...checks: (core.CheckFn<core.output<this>> | core.$ZodCheck<core.output<this>>)[]): this
  clone(
    def?: Internals['def'],
    params?: {
      parent: boolean
    },
  ): this
  register<R extends core.$ZodRegistry>(
    registry: R,
    ...meta: this extends R['_schema']
      ? undefined extends R['_meta']
        ? [core.$replace<R['_meta'], this>?]
        : [core.$replace<R['_meta'], this>]
      : ['Incompatible schema']
  ): this
  brand<T extends PropertyKey = PropertyKey, Dir extends 'in' | 'out' | 'inout' = 'out'>(
    value?: T,
  ): PropertyKey extends T ? this : core.$ZodBranded<this, T, Dir>
  parse(data: unknown, params?: core.ParseContext<core.$ZodIssue>): core.output<this>
  safeParse(
    data: unknown,
    params?: core.ParseContext<core.$ZodIssue>,
  ): parse.ZodSafeParseResult<core.output<this>>
  parseAsync(data: unknown, params?: core.ParseContext<core.$ZodIssue>): Promise<core.output<this>>
  safeParseAsync(
    data: unknown,
    params?: core.ParseContext<core.$ZodIssue>,
  ): Promise<parse.ZodSafeParseResult<core.output<this>>>
  spa: (
    data: unknown,
    params?: core.ParseContext<core.$ZodIssue>,
  ) => Promise<parse.ZodSafeParseResult<core.output<this>>>
  encode(data: core.output<this>, params?: core.ParseContext<core.$ZodIssue>): core.input<this>
  decode(data: core.input<this>, params?: core.ParseContext<core.$ZodIssue>): core.output<this>
  encodeAsync(
    data: core.output<this>,
    params?: core.ParseContext<core.$ZodIssue>,
  ): Promise<core.input<this>>
  decodeAsync(
    data: core.input<this>,
    params?: core.ParseContext<core.$ZodIssue>,
  ): Promise<core.output<this>>
  safeEncode(
    data: core.output<this>,
    params?: core.ParseContext<core.$ZodIssue>,
  ): parse.ZodSafeParseResult<core.input<this>>
  safeDecode(
    data: core.input<this>,
    params?: core.ParseContext<core.$ZodIssue>,
  ): parse.ZodSafeParseResult<core.output<this>>
  safeEncodeAsync(
    data: core.output<this>,
    params?: core.ParseContext<core.$ZodIssue>,
  ): Promise<parse.ZodSafeParseResult<core.input<this>>>
  safeDecodeAsync(
    data: core.input<this>,
    params?: core.ParseContext<core.$ZodIssue>,
  ): Promise<parse.ZodSafeParseResult<core.output<this>>>
  refine<Ch extends (arg: core.output<this>) => unknown | Promise<unknown>>(
    check: Ch,
    params?: string | core.$ZodCustomParams,
  ): Ch extends (arg: any) => arg is infer R ? this & ZodType<R, core.input<this>> : this
  superRefine(
    refinement: (
      arg: core.output<this>,
      ctx: core.$RefinementCtx<core.output<this>>,
    ) => void | Promise<void>,
    params?: core.$ZodSuperRefineParams,
  ): this
  overwrite(fn: (x: core.output<this>) => core.output<this>): this
  optional(): ZodOptional<this>
  exactOptional(): ZodExactOptional<this>
  nonoptional(params?: string | core.$ZodNonOptionalParams): ZodNonOptional<this>
  nullable(): ZodNullable<this>
  nullish(): ZodOptional<ZodNullable<this>>
  default(def: util.NoUndefined<core.output<this>>): ZodDefault<this>
  default(def: () => util.NoUndefined<core.output<this>>): ZodDefault<this>
  prefault(def: () => core.input<this>): ZodPrefault<this>
  prefault(def: core.input<this>): ZodPrefault<this>
  array(): ZodArray<this>
  or<T extends core.SomeType>(option: T): ZodUnion<[this, T]>
  and<T extends core.SomeType>(incoming: T): ZodIntersection<this, T>
  transform<NewOut>(
    transform: (
      arg: core.output<this>,
      ctx: core.$RefinementCtx<core.output<this>>,
    ) => NewOut | Promise<NewOut>,
  ): ZodPipe<this, ZodTransform<Awaited<NewOut>, core.output<this>>>
  catch(def: core.output<this>): ZodCatch<this>
  catch(def: (ctx: core.$ZodCatchCtx) => core.output<this>): ZodCatch<this>
  pipe<T extends core.$ZodType<any, core.output<this>>>(
    target: T | core.$ZodType<any, core.output<this>>,
  ): ZodPipe<this, T>
  readonly(): ZodReadonly<this>
  /** Returns a new instance that has been registered in `z.globalRegistry` with the specified description */
  describe(description: string): this
  description?: string
  /** Returns the metadata associated with this instance in `z.globalRegistry` */
  meta(): core.$replace<core.GlobalMeta, this> | undefined
  /** Returns a new instance that has been registered in `z.globalRegistry` with the specified metadata */
  meta(data: core.$replace<core.GlobalMeta, this>): this
  /** @deprecated Try safe-parsing `undefined` (this is what `isOptional` does internally):
   *
   * ```ts
   * const schema = z.string().optional();
   * const isOptional = schema.safeParse(undefined).success; // true
   * ```
   */
  isOptional(): boolean
  /**
   * @deprecated Try safe-parsing `null` (this is what `isNullable` does internally):
   *
   * ```ts
   * const schema = z.string().nullable();
   * const isNullable = schema.safeParse(null).success; // true
   * ```
   */
  isNullable(): boolean
  apply<T>(fn: (schema: this) => T): T
}
utilities.getValidProps

Validates resource props against a Zod schema, returning typed valid props or throwing a descriptive error with field-level messages prefixed by the resource name.

const getValidProps: <Props>({
  name,
  schema,
  props,
}: {
  name: string
  schema: ZodSchema<Props>
  props: unknown
}) => Props
  • options — Validation options.
  • options.name — The resource name (used in error messages, e.g., "User.email: Required").
  • options.schema — The Zod schema to validate against.
  • options.props — The raw props to validate.

Returns: The validated and typed props.

utilities.safeParse

Validates data against a Zod schema without throwing, returning a discriminated union.

const safeParse: <T>(
  schema: ZodSchema<T>,
  data: unknown,
) => { success: true; data: T } | { success: false; error: ZodError }
  • schema — The Zod schema to validate against.
  • data — The raw data to validate.

Returns: { success: true, data } on success, or { success: false, error } with a ZodError.

utilities.validate

Validates data against a Zod schema, throwing a ZodError on failure.

const validate: <T>(schema: ZodSchema<T>, data: unknown) => T
  • schema — The Zod schema to validate against.
  • data — The raw data to validate.

Returns: The validated and typed data.

utilities.isEmail

Basic regex for determining whether or not a string is an email address.

const isEmail: (string: string, strict?: boolean) => boolean
  • string — The string.
  • strict — Whether strict.

Returns: Whether email.

utilities.isUuid

Basic regex for determining whether or not a string is a UUID.

const isUuid: (uuid: string) => boolean
  • uuid — The uuid.

Returns: Whether uuid.

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-database ^1.0.1
  • @molecule/api-i18n ^1.0.1
  • @molecule/api-locales-resource ^1.0.1
  • @molecule/api-utilities-validation ^1.0.1

Runtime Dependencies

  • @molecule/api-bond
  • @molecule/api-database
  • @molecule/api-i18n
  • @molecule/api-locales-resource
  • @molecule/api-utilities-validation
  • uuid
  • zod

Working WITH installed @molecule/api-resource-* packages (auth getUserId, table ownership, migrations) — what generated code most often breaks:

  • Auth inside a handler is const userId = getUserId(res), imported from @molecule/api-bonds-default-express — NOT from this package (this package's utilities namespace holds only validation helpers). Return 401 when it is missing. There is NO requireAuth middleware or file to import or create; auth is applied at the router level. A route's authorizer may also stash the already-loaded row on the response context and hand it to read({ id, props }) to skip a second database fetch.
  • A selected @molecule/api-resource-* package OWNS its table AND its already-wired routes with a FIXED schema (e.g. api-resource-project owns projects keyed by user_id with a live /projects handler). Do NOT write a migration that re-creates such a table with a different schema — your CREATE TABLE shadows theirs (or vice-versa) and the package's still-wired handler then 500s (column "user_id" does not exist). To model a different shape, use a DIFFERENT table name (e.g. workspace_projects) + your own handler. Check api/migrations/* + api/__setup__/ for tables packages already provide.
  • Adding a resource package AFTER scaffold? Copy its migration. Such a package ships its table DDL at node_modules/@molecule/<pkg>/src/__setup__/*.sql — copy it into api/migrations/<next-number>_<name>.sql. Wiring its routes/handler alone does NOT create the table: every request 500s with "relation does not exist" even though the build type-checks clean. (Only packages present at scaffold time get their migration auto-created.)
  • Zod/validation helpers are NAMESPACED exports, not top-level: schema.basePropsSchema / schema.validate / schema.safeParse / schema.partial / schema.pick, and utilities.getValidProps (re-exported from @molecule/api-utilities-validation). Import the namespace: import { schema, utilities } from '@molecule/api-resource'.

Translations

Translation strings are provided by @molecule/api-locales-resource.