Skip to content

Endpoints reference

EndpointHandle<T> is a conditional type:

  • T extends U[]ListHandle<U>
  • T extends objectRecordHandle<T>

server.endpoint(url) and ctx.endpoint(url) (inside a handler) both return one. The Endpoints map carries the per-URL type, so the handle is typed without a cast.

Stores are authored with .data(url, seed) on the builder, or as a plain def for file-backed data:

ts
mockGroup<Endpoints>()
  .data('/internal/todos', [{ id: 1, title: 'Buy milk', done: false }])  // → ListHandle<Todo>
  .done();

// file-backed store (plain def — the builder has no .dataFile)
{ url: '/api/todos', dataFile: file<Todo[]>('./todos.json') }            // → ListHandle<Todo>

A handler reaches any other store through ctx.endpoint(url):

ts
.get('/api/todos', (_req, ctx) => ctx.endpoint('/internal/todos').data)

ListHandle<U> — list endpoints (data: U[])

Backs every endpoint defined with an array data or an array dataFile. Mutations persist in memory across requests; replaceData is what the file watcher calls when the JSON changes on disk.

MemberTypeDescription
dataU[]Live, mutable backing array. Reads reflect inserts / updates / removes.
findById(id)U | undefinedLoose-equality lookup on the id field.
where(filter)U[]Partial<U> match — every key must equal.
where(predicate)U[]Predicate variant.
first()U | undefinedFirst item.
count()numberNumber of items.
has(id)booleanExistence check.
nextId()numbermax(id) + 1, 1 for empty list.
insert(item)UAppend; auto-generates id if missing.
update(id, patch)U | undefinedPartial update (Object.assign).
updateMany(ids, patch)U[]Batch update; missing ids skipped. patch may be a function (item) => Partial<U>.
patch(id, fields, defaults?)U | undefinedApply non-undefined fields, then unconditional defaults.
remove(id)booleanDelete; true if removed.
clear()voidEmpty the list.
reset()voidRestore the original (deep copied) baseline.
replaceData(items)voidReplace data and baseline. Used by dataFile hot-reload.
save(path)Promise<void>Persist current data as JSON.

The id field is 'id' by default. Override per handle via ListHandleOptions.idKey.

RecordHandle<T> — record endpoints (data: T)

Single-object endpoint. GET returns the object; PATCH shallow-merges; PUT replaces.

MemberDescription
dataRead-only getter — current object.
set(patch)Shallow-merge patch into the object.
replace(value)Overwrite.
reset()Restore the original baseline (deep copy).

WsHandle<Out> — WebSocket endpoints

ctx.endpoint('/ws/...') returns this when the URL was defined with ws({...}). See WebSocket reference.

MockrServer

Returned by await mockr<E>({...}). Stays alive until .close().

MemberDescription
endpoint(url)Typed EndpointHandle for a URL.
listEndpoints()All endpoints with { url, method, type, enabled }.
enableEndpoint(url) / disableEndpoint(url)Toggle one.
enableAll() / disableAll()Bulk toggle.
use(middleware)Add middleware at runtime.
scenario(name)Apply a named scenario.
reset()Reset every endpoint to its initial baseline.
save(path)Save full snapshot to file.
setPort(port)Move to a new port.
enableProxy() / disableProxy()Toggle proxy.
setProxyTarget(url)Change proxy target.
tui()Launch the terminal UI.
recorderRecorder API (if enabled in config).
close()Shut down.

EndpointDef

The shape each entry of endpoints: [...] accepts. The builder emits these for you; write them directly when you need dataFile or ws, which the builder doesn't cover. Mutually exclusive top-level shorthands plus optional cross-cutting config:

ts
interface EndpointDef<E, U extends keyof E = keyof E> {
  url: U | string | RegExp;
  // exactly one of:
  data?: E[U];                   // → list (array) or record (object)
  dataFile?: string | FileRef<E[U]>; // hot-reloaded JSON
  ws?: WsSpec;                   // WebSocket endpoint
  methods?: Record<HttpMethod, VerbSpec>;     // multi-verb (builder output)
  // optional:
  load?: (req, ctx) => E[U] | Promise<E[U]>;  // run-once loader (see below)
  method?: HttpMethod;
  enabled?: boolean;
  idKey?: string;                // override `'id'` for list endpoints
}

Most defs come from mockGroup().done(). Reach for a hand-written def only for file-backed stores (dataFile: file<T>('./x.json'), hot-reloaded on change) or WebSockets (ws({...})); both go straight into endpoints: [...] alongside any groups.

Loaders & partitioned stores

.data(url, fn) (or a hand-written load) makes the store computed once on first access from a function — typically ctx.forward() to seed from the real backend — after which it's owned and CRUD mutations stick. See builder → Loaders & partitions.

A data URL with path params keeps one owned store per resolved param-set (/projects/:projectId/companies/ → an independent store per projectId; static seeds and loaders alike). Inside a handler, ctx.endpoint('/projects/:projectId/companies/') resolves the current request's partition, so a write at a different URL carrying the same :projectId grows the right project's store. server.endpoint(url).reset() re-arms every partition; data access outside a request (no params to resolve) throws.

MIT License