Point0by 1gr14
  • Menu
    • GitHub
    • Blog
  • Introduction
    • Overview
    • Getting Started
    • Full Overview
    • Benchmarks
    • Points
  • Points
    • Page
    • Layout
    • Component
    • Provider
    • Mountable
    • Query
    • Infinite Query
    • Mutation
    • Action
    • Subscription
    • Root
    • Base
    • Plugin
  • Methods
    • Validation
    • Loader
    • Context
    • Middleware
    • Loading & error
    • .with
    • Mapper
    • Transformer
    • Stage Methods
  • Core
    • Navigation
    • SSR
    • RSC
    • Socket
    • Request
    • Response
    • Error handling
    • Env
    • Head
    • MDX
    • Assets
    • File upload
    • OpenAPI
    • Query client
    • Events
    • Infer
  • Engine
    • Engine Config
    • Engine Runtime
    • CLI
    • Dev
    • Build
    • Compiler
    • Generator
    • Project MCP
    • Docs MCP
    • Importer
    • Public dir
    • Testing
    • Deploy
    • Bun or Vite
  • Extra
    • SsrStore
    • CookieStore
    • Basic Auth
    • CORS
    • Cache-Control
    • Compress
  • Examples
    • Basic
    • Vite
    • Better Auth
    • Socket
    • Capacitor
    • Expo
  • 1gr14
    • Home
    • Start0
    • Support
    • Education
    • Group
    • Blog
    • Author
  • Community
    • Discord
    • Telegram
  • Open Source
    • Point0
    • Route0
    • Error0
    • Flat
    • Agents Party
  • Account
    • Sign In
    • Sign Up
Point0by 1gr14
Building open-source software for the glory of the Lord Jesus Christ ☦️
With love for developers of all backgrounds around the world ❤️
Terms of ServicePrivacy PolicySergei Dmitriev 2026 😎

Events

  • Category: Core

On this page

  1. Subscribing
  2. By name, by list, or everything
  3. The full event set
  4. Two altitudes: the work and the wire
  5. Lifecycle phases
  6. Why engineFetch* is server-only
  7. The 'error' shorthand
  8. The event object
  9. meta: the log-friendly projection
  10. Binaries in the input are sanitized
  11. Subscriber errors and emitError
  12. Wiring events to your observability
  13. Reference
  14. Event names
  15. Subscription methods
  16. Event object fields
  17. Per-event data
  18. Public types

Point0 emits a lifecycle event for every query, mutation, and fetch it runs. Subscribe to them on any point with .on / .serverOn / .clientOn to log, report errors, or collect metrics — without touching the loaders themselves. The common case is one error subscriber on the root:

export const root = Point0.lets
  .root()
  .on('error', ({ side, name, error, meta }) => {
    // 'error' is sugar for the four error events; meta is a log-friendly projection
    console.error({ side, name, error, ...meta })
  })
  .root()

Every failed query or mutation now reaches your handler, with side, the event name, the typed error, and a slim meta you can spread straight in.

Subscribing

Three methods, all on every point type, all chainable (they return the same point):

export const root = Point0.lets
  .root()
  .on('pointQuerySuccess', (e) => {
    /* kept in both bundles */
  })
  .serverOn('engineFetchError', (e) => {
    /* cut from the client bundle */
  })
  .clientOn('pointMutationSuccess', (e) => {
    /* cut from the server bundle */
  })
  .root()
  • .on — not cut from either bundle: kept in both (isomorphic), so it fires on both sides.
  • .serverOn — cut from the client bundle: its body and the imports it uses are removed, so the handler — server secrets included — never ships to the browser. It fires only for server-side events.
  • .clientOn — cut from the server bundle: body and its imports removed. It fires only for client-side events.

The side is decided when the event is emitted, from where the emitting code runs — not from the event name. A .serverOn callback never sees a client-side event, and vice versa.

Subscriptions accumulate: each call adds to the list, and a point inherits every subscription from its parents up the chain. Put app-wide logging on the root and it covers everything beneath.

By name, by list, or everything

export const root = Point0.lets
  .root()
  .on('pointQueryStart', (e) => {
    /* one event */
  })
  .on(['pointQuerySuccess', 'pointMutationSuccess'], (e) => {
    /* several */
  })
  .on('*', (e) => {
    /* every event */
  })
  .root()

With '*' the callback receives the full event union; with a single name it's narrowed to that event. The wildcard still respects the side filter — .serverOn('*') fires only for server-side events.

The full event set

Most families come in four lifecycle phases — Start, Settled, Success, Error. The subscription family streams, so its four are Start, Data, Settled, Error — a value per Data, the outcome on Settled. The table below lists the families; the Reference enumerates every name:

FamilyWhat it tracksSide
pointQuery*a query running (useQuery / fetchQuery)client | server
pointInfiniteQuery*an infinite query runningclient | server
pointMutation*a mutation runningclient | server
pointSubscriptionServer* / Client*a subscription stream: the loader run / a fetch attemptper side
pointChannelConnectServer* / Client*a channel connect: the connector run / the connect, settled by its claimper side
pointChannelOpenServer / pointChannelCloseServera connection went live / away (singles)server only
pointChannelClaimServerErrora connection failed to claim its place on the socket (single)server only
pointSpaceJoinServer* / Client*a space join: the .joiner run / the join frameper side
pointSpaceLeaveServera membership left its rooms (single)server only
pointHandlerServer* / Client*a message: .serverReply / a clientHandler dispatchper side
pointHandlerServerLateErrora .serverReply that threw after its early reply (single)server only
pointHandlerSendClient* / SendServer*TRANSMITTING a message: sendToServer / sendToClientper side
socketServer* / socketClient*the socket itself: upgrades, opens, drops, refusals (singles)per side
pointFetchServer*a point's server-fetch step (the SSR / fetch machinery)client | server
pointPrefetchPage*a page being prefetched before navigationclient | server
engineFetch*the engine's outgoing HTTP fetch (the actual request)server only
emitErrora subscriber callback itself threw (see below)client | server

Each family gives you <Family>Start, <Family>Settled, <Family>Success, and <Family>Error — except the subscription families, whose set is Start, Data, Settled (with outcome: 'completed' | 'failed' | 'broken'), and Error. Three families add a fifth phase, Cancelled — pointQuery, pointInfiniteQuery, and pointFetchServer: a run whose AbortSignal fired (you navigated away, the component unmounted, a refetch superseded it) is a settled non-error outcome, emitted instead of Error and deliberately kept out of the 'error' shorthand, so cancellations never reach your reporter.

Two altitudes: the work and the wire

Some operations are reported twice, from different heights, and the pair is not a duplicate:

  • HTTP — pointQuery* / pointMutation* describe the loader running; engineFetch* describes the outgoing HTTP request underneath it.
  • Sockets — pointHandlerServer* / pointHandlerClient* describe the receiving side RUNNING the message (a .serverReply, a clientHandler dispatch); pointHandlerSendClient* / pointHandlerSendServer* describe the sending side TRANSMITTING it. A send that never left — no live connection, a dead socket, a timeout — produces no execution event anywhere, and only the transport family reports it. Below both sits the socket itself: a message the engine refused before any point ran (socketServerSendRefused), a connection that never claimed its place (pointChannelClaimServerError), a transport that never came up (socketClientError).

What "success" means on the transport families is per side: pointHandlerSendClientSuccess fires when the server's reply resolves the send (that is what sendToServer() awaits), while pointHandlerSendServerSuccess fires when the engine accepted the push for delivery — a push is fire-and-forget, so it means handed to the transport, never delivered. A push addressed to a room nobody is in is a successful send.

Lifecycle phases

For any one run:

  • Start fires before the work begins.
  • Settled fires on every outcome — success or error.
  • Success fires on a successful result.
  • Error fires only on a genuine error.
// a query that succeeds:  pointQueryStart → pointQuerySettled → pointQuerySuccess
// a query that throws:     pointQueryStart → pointQuerySettled → pointQueryError

One edge case: a redirect is a success, not an error. When a loader redirects (throw redirect(...)), the query settles down the success path — Settled then Success fire, not Error. See Navigation for redirects.

Why engineFetch* is server-only

engineFetch* wraps the actual outgoing HTTP request, which only the server makes — so those events are typed 'server' and are only reachable through .on and .serverOn. Naming engineFetch* inside .clientOn is a type error: it isn't in the client event set.

The other point* events report side: 'client' even during SSR, because the query/fetch code that emits them is client-authored (it runs on the server under SSR, but it's the same code). Only engineFetch* — the HTTP layer — reports side: 'server'. During one SSR page load you'll see both:

export const root = Point0.lets
  .root()
  .on('*', (e) => order.push([e.name, e.side]))
  .root()
// pointQueryStart        client
// pointFetchServerStart  client
// engineFetchStart       server   ← the actual HTTP request
// engineFetchSettled     server
// engineFetchSuccess     server
// pointFetchServerSettled client
// pointFetchServerSuccess client
// pointQuerySettled      client
// pointQuerySuccess      client

The 'error' shorthand

.on('error', cb) is sugar — it expands to nineteen subscriptions, one per error event:

export const root = Point0.lets
  .root()
  .on('error', (e) => {
    /* … */
  })
  .root()

Spelled out, that one call is:

export const root = Point0.lets
  .root()
  .on(
    [
      'pointMutationError',
      'pointQueryError',
      'pointInfiniteQueryError',
      'pointChannelConnectServerError',
      'pointChannelConnectClientError',
      'pointChannelClaimServerError',
      'pointHandlerServerError',
      'pointHandlerServerLateError',
      'pointHandlerClientError',
      'pointHandlerSendClientError',
      'pointHandlerSendServerError',
      'pointSpaceJoinServerError',
      'pointSpaceJoinClientError',
      'pointSubscriptionServerError',
      'pointSubscriptionClientError',
      'socketServerSendRefused',
      'socketClientError',
      'engineFetchError',
      'rscError',
    ],
    (e) => {
      /* … */
    },
  )
  .root()

Inside the callback, error is narrowed to a non-undefined error instance. One user throw can produce more than one of them: a failing server query surfaces as engineFetchError (server) and pointQueryError (client), so an .on('error') logger may see the same failure from two angles.

GOTCHA: the shorthand covers query, infinite-query, mutation, every socket error (channel connect, space join, the handler families, both transport families, the late-reply single, and the three refusal singles — a claim, a refused send, a transport that never came up), engineFetch, and rscError (a failed defer() subtree) — not pointFetchServerError or pointPrefetchPageError, and never a Cancelled event. To catch those two, name them explicitly: .on(['pointFetchServerError', 'pointPrefetchPageError'], cb).

Because pointHandlerSendClientError is in there, a fire-and-forget send — void handler.sendToServer(...), where nobody awaits the promise — is still reported: the root .on('error') subscriber sees the failure the caller chose not to await.

The event object

Every callback receives one object with the same five fields:

export const root = Point0.lets
  .root()
  .on('pointQueryError', ({ side, name, data, error, meta }) => {
    side // => 'client' | 'server' — where the emitting code ran
    name // => 'pointQueryError'
    data // the raw payload — rich, but heavy to log
    error // the typed error instance (your error class — ErrorPoint0 by default;
    //       undefined on non-error events)
    meta // a slim, log-friendly projection of data
  })
  .root()
  • side — 'client' or 'server', set at emit time (see side above).
  • name — the event name.
  • data — the full payload (the query result, the request object, the QueryClient, …). Prefer meta for logging.
  • error — the error instance, hoisted to the top level so an 'error' handler can read it directly — the same object as data.error. Typed (your error class) on error events, undefined on every other event (the key is always present).
  • meta — the log-friendly projection, below.

meta: the log-friendly projection

meta is a plain record (Record<string, unknown>) built per event from data, meant to go straight into a logger:

export const root = Point0.lets
  .root()
  .on('pointQueryStart', ({ meta }) => {
    // the point's id (<scope>:<type>:<name>), not the object
    meta.point // => 'root:page:home'
    meta.input // => { id: 123 } — the input, sanitized (see below)
  })
  .root()

data carries heavy objects (responses, requests, query results) that you don't want in a log line; meta replaces them with compact forms: points become their string id (<scope>:<type>:<name>), requests become { method, path }, errors and redirects are serialized, and it drops bulky members. For an engineFetch event, meta.result and meta.response are dropped; for the SSR case, a settled event's meta.request.renders reports how many SSR render passes ran.

meta does not carry the error — on an error event, meta.error is undefined. The error lives on the envelope error (and data.error). So a typical error log spreads meta and adds the parts it needs:

export const root = Point0.lets
  .root()
  .on('error', ({ side, name, error, meta }) => {
    console.error({ ...meta, side, name, error })
  })
  .root()

Binaries in the input are sanitized

meta.input runs through a sanitizer that replaces binary values with placeholder strings, so file uploads never bloat or break a log line:

// a mutation input of { photo: File, note: 'hi' } logs as:
meta.input // => { photo: '[File: photo.png (5120 bytes)]', note: 'hi' }

File → [File: <name> (<size> bytes)], Blob → [Blob: <size> bytes], FormData → [FormData]. Nested binaries (inside arrays/objects) are replaced too; everything else passes through unchanged.

Subscriber errors and emitError

Callbacks are fire-and-forget: they may be sync or async, and Point0 does not await them. A slow handler never blocks a request, and the completion order of async handlers across subscriptions isn't guaranteed.

If a callback throws, the framework does not crash. The error is caught and re-emitted as an emitError event, carrying the original event and the thrown error — so you can observe your own handler failures:

export const root = Point0.lets
  .root()
  .on('emitError', ({ error, data, meta }) => {
    // error      — what your handler threw (coerced to the error class)
    // data.event — the full original event that was being handled
    // meta.event — its slim { name, meta } projection
    console.error('event handler failed', data.event.name, error)
  })
  .root()

A throw inside an emitError handler is swallowed silently — there's no recursion, so a broken error reporter can't cause an emit loop.

Wiring events to your observability

Funnel events through one subscriber on the root and let your logging stack fan out — the .on('error') subscriber at the top of this page is the whole pattern. Swap console.error for your own logger and you get error reporting with no extra call sites at the points. In Start0, for example, that subscriber writes to a LogTape logger whose sink forwards error records to Sentry — the same single root subscription, just a richer sink behind it.

Subscribe to the success/settled events the same way for request metrics or audit logs.

Reference

Event names

pointQueryStart          pointQuerySettled          pointQuerySuccess          pointQueryError          pointQueryCancelled
pointInfiniteQueryStart  pointInfiniteQuerySettled  pointInfiniteQuerySuccess  pointInfiniteQueryError  pointInfiniteQueryCancelled
pointMutationStart       pointMutationSettled       pointMutationSuccess       pointMutationError
pointSubscriptionServerStart    pointSubscriptionServerData      pointSubscriptionServerSettled    pointSubscriptionServerError
pointSubscriptionClientStart    pointSubscriptionClientData      pointSubscriptionClientSettled    pointSubscriptionClientError
pointChannelConnectServerStart  pointChannelConnectServerSettled pointChannelConnectServerSuccess  pointChannelConnectServerError
pointChannelConnectClientStart  pointChannelConnectClientSettled pointChannelConnectClientSuccess  pointChannelConnectClientError
pointSpaceJoinServerStart       pointSpaceJoinServerSettled      pointSpaceJoinServerSuccess       pointSpaceJoinServerError
pointSpaceJoinClientStart       pointSpaceJoinClientSettled      pointSpaceJoinClientSuccess       pointSpaceJoinClientError
pointHandlerServerStart         pointHandlerServerSettled        pointHandlerServerSuccess         pointHandlerServerError
pointHandlerClientStart         pointHandlerClientSettled        pointHandlerClientSuccess         pointHandlerClientError
pointHandlerSendClientStart     pointHandlerSendClientSettled    pointHandlerSendClientSuccess     pointHandlerSendClientError
pointHandlerSendServerStart     pointHandlerSendServerSettled    pointHandlerSendServerSuccess     pointHandlerSendServerError
pointFetchServerStart    pointFetchServerSettled    pointFetchServerSuccess    pointFetchServerError    pointFetchServerCancelled
pointPrefetchPageStart   pointPrefetchPageSettled   pointPrefetchPageSuccess   pointPrefetchPageError
engineFetchStart         engineFetchSettled         engineFetchSuccess         engineFetchError
pointChannelOpenServer   pointChannelCloseServer    pointSpaceLeaveServer      pointHandlerServerLateError
pointChannelClaimServerError
socketServerUpgrade    socketServerConnect      socketServerDisconnect   socketServerSendRefused
socketClientConnect    socketClientDisconnect   socketClientError
rscError                 emitError

The socket families (pointChannelConnect*, pointSpaceJoin*, pointHandler*, pointHandlerSend*), the server-only singles (pointChannelOpenServer / pointChannelCloseServer / pointSpaceLeaveServer / pointHandlerServerLateError / pointChannelClaimServerError), and the socket-level socket* singles are split by side — the two sides are genuinely different operations with different data, not one event observed twice. When each fires, the counters and entry markers the client families carry, and the open / close / leave reasons: Socket → The events.

The subscription families fire per stream ATTEMPT on the client (Start's attempt counts reconnects — there is no separate reconnect event) and per streamed response on the server: Data on every streamed value, Settled with the outcome (completed / failed / broken), nothing after a deliberate cancel. Details: Subscription.

Subscription methods

MethodFires onName argument accepts
.onboth sidesany event name, 'error', '*', or an array of names
.serverOnserver-side eventsserver event names, 'error', '*', array
.clientOnclient-side eventsclient event names, 'error', '*', array

All three are on every point type, accumulate, and are inherited down the chain. engineFetch* names are valid in .on / .serverOn only — a type error in .clientOn.

Event object fields

FieldTypeNotes
side'client' | 'server'where the emitting code ran, set at emit time
namethe event name
datathe raw payload (per event)rich; not log-friendly
errorerror instance, or undefinedpresent on error events; same object as data.error
metaRecord<string, unknown>log-friendly projection of data

Per-event data

Familydata carries
pointQuery*{ queryKey, point, input, mode, data?, error?, redirect? }
pointInfiniteQuery*same as query, for the infinite case
pointMutation*{ point, input } + one of output / error / redirect
pointSubscriptionServer* / Client*{ point, input } + value (Data) / outcome; attempt on ClientStart
pointChannelConnectServer* / Client*{ point, input } + connectionId / error; identity on Server; connectionIndex (every phase) + resumed/gapless (success) on Client, which settles at the CLAIM — its Error covers the claim refusals too
pointChannelOpenServer{ point, connectionId, identity, resumed } — server-only
pointChannelCloseServer{ point, connectionId, identity, reason } — server-only
pointChannelClaimServerError{ scope, point, connectionId, reason, error } — server-only; point/connectionId are undefined when the refusal came before the ticket resolved
pointSpaceJoinServer* / Client*{ point, input, connectionId } + rooms / error; identity + resumed on Server; membershipIndex (every phase) + resumed/gapless (success) on Client
pointSpaceLeaveServer{ point, connectionId, identity, rooms, reason } — server-only
pointHandlerServer* / Client*{ point, input, connectionId } + output / error; identity on Server
pointHandlerServerLateError{ point, input, connectionId, identity, error } — server-only
pointHandlerSendClient*{ point, input, connectionId } + output / error; connectionId is undefined on Start and on a failure with no connection
pointHandlerSendServer*{ point, input } + error — server-only; no connectionId (a push can address many connections at once)
socketServer* / socketClient*{ scope } — socket-level, no point; + socketIndex on socketClientConnect
socketServerSendRefused{ scope, reason, handlerName, connectionId, error } — server-only; a send the engine refused before any point ran
socketClientError{ scope, socketIndex, reason, error } — client-only; reason is 'open' (never came up) or 'exhausted' (the reconnect gave up)
pointFetchServer*{ input, point } + the fetch-server output on settled/success/error
pointPrefetchPage*{ point, input, options, error? }
engineFetch*{ request, scope, result?, error? }
rscError{ error, label, holeId } — a failed defer() subtree, server-side
emitError{ error, event } — the original event and the thrown error

Public types

The event types are exported from @point0/core: AnyEventerEvent, EventerEvent, EventerEventMeta, EventerSide, and the per-event types. The barrel is type-only (export type * from './eventer.js'), so the runtime uniqEventerErrorEventNames constant — the nineteen names the 'error' shorthand expands to — is not importable as a value.

PreviousQuery clientNextInfer

Enjoying Point0?

Give it a star on GitHub, ask questions, and follow what's new

Star Point0

A star on GitHub helps more developers find Point0
Star on GitHub

Start0

Point0 integrated into the Start0 SaaS boilerplate — the best way to use it
Explore Start0

YouTube

Tutorials, walkthroughs, and updates
YouTube

Discord

Questions and chat in English
Join Discord

Telegram

News channel and chat in Russian
ChannelChat

Twitter

Posts and updates
Twitter