Skip to main content
The Node.js application SDK is @varianz/node (install); it works from TypeScript or plain JavaScript. Test-side integrations (@varianz/vitest, @varianz/playwright) are covered under Testing. Requires Node.js 22+ and, for the decorator API, TC39 (stage-3) decorators — TypeScript ≥ 5.0 with experimentalDecorators: false.

Two registration styles

Both produce the same runtime handles and stage API.

Decorators

TC39 decorators register via context.addInitializer(...), so registration happens when the class is instantiated, not at import time. Construct the service before calling getVPointHandle(...).

createVPoints

Patch modes (opts.patch):
  • 'wrap' (default) — returns a proxy; the original object is untouched. Calls on the original object are not intercepted — use the returned instrumented, or:
  • 'mutate' — replaces methods on the original object in place (logs a one-time warning).
  • 'off' — registers without intercepting; dispatch manually via callVPoint().

Other registration paths

  • registerVPoint(vpdBytes, api) — from an offline-built descriptor; encodeVpd(spec) builds one from a DynamicSpec.
  • discover('src/services/**/*.service.ts') — loads matching files and instantiates exported classes so @vpoint initializers fire. Runs in-process, so .ts globs need a TS loader (tsx, ts-node, or --experimental-strip-types).

Schemas

Schemas come from four sources, in fixed precedence: explicit registration options → the @varianz/scanner build-time manifest → first successful invocation → auto-derived Unknown. Run the scanner and your TS types become the schema source — decorators like @struct/@field are then overrides for special cases (canonical names, Kind.Int32 instead of the Float64 default for number, field access modes). Values typed Unknown still support CEL field access at runtime; only CEL struct-literal construction needs a typed schema. Kinds cover the numeric range (Int8Int128, UInt8UInt128, Float32/64), Bool, Utf8, Bytes, Struct, and Unknown. Named struct kinds link a parameter to a @struct declaration:

Local stages

Uniquely, the Node SDK can attach stages in-process without the registry — JS functions, CEL, or debug logging — at any anchor:
Stage handles are async-disposable on Node 23+ (using stage = attachStage(...)); on Node 22 call stage.remove() explicitly. attachCelStage and attachDebugStage also accept a VPoint name instead of a handle.

Sessions

The SDK holds the session in AsyncLocalStorage, exposed as VarianzContext:
A VPoint method that receives the session ID as a parameter can declare it with varianzId('sessionId') in its params, and the SDK enters the scope automatically. Framework-specific wiring (Fastify, Koa, Hono, NestJS, gRPC): Propagate sessions.

Registry connection

initRegistry is process-global and throws on a second call — call it exactly once at startup (the test fixtures guard this for you). The environment tags determine which environment-scoped stages reach this instance. For diagnostics: initTracing('info') or initTracing('debug', { json: true }).

Troubleshooting

  • Decorators don’t register — decoration runs on instantiation. Construct the service before getVPointHandle(...).
  • initRegistry throws “already initialised” — it’s process-global. In Vitest, set fileParallelism: false.
  • Calls aren’t intercepted — you’re calling the original object with the default wrap patch mode. Call through instrumented or use patch: 'mutate'.
  • varianz-scan finds no VPoints — the scanner pre-filters files containing vpoint/createVPoints call sites; importing types alone registers nothing.