# JavaScript and TypeScript

> Instrument Node.js, browser, Express, Fastify, NestJS GraphQL, Prisma, and React applications with the Anectico SDK.

Canonical page: https://anectico.com/docs/instrument/javascript/


Use `@anectico/sdk` for Node.js, browser, Express, Fastify, NestJS GraphQL, Prisma, and React applications. The base package sends
traces, metrics, logs, errors, diagnostic events, and customer identity; replay and LLM wrappers
are opt-in subpath imports.

## Install and start

During early access, install the artifact supplied during onboarding. Replace the path with the
actual downloaded package:

```bash
npm install /path/to/anectico-sdk.tgz
```

After the public registry release, use `npm install @anectico/sdk` instead.

```typescript
import { initNode } from '@anectico/sdk/node';

const anectico = await initNode({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'orders-api',
  serviceVersion: process.env.GIT_SHA,
  environment: process.env.NODE_ENV,
});
```

This bootstrap is for Node.js and installs asynchronous trace context propagation. For a browser,
use the browser setup below; the base `init()` does not install Node's context manager.
Keep one client for the process. Call `await anectico.stop()` during graceful shutdown so buffered
telemetry can finish exporting.

For a raw Node HTTP server in managed mode, exclude health and internal control
traffic from the generic incoming HTTP spans:

```typescript
const anectico = await initNode({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'orders-api',
  ignoreIncomingRequestPaths: ['/healthz', '/ready', '/internal/*'],
});
```

The equivalent `ANECTICO_IGNORE_INCOMING_REQUEST_PATHS` environment variable is a
comma-separated list. Exact paths and trailing-`*` prefixes are supported, and
query strings are ignored for matching.

`initNode()` provides default `SIGTERM` and `SIGINT` flushing only while Anectico is the sole listener
for that signal. When your application registers its own handler—for example to drain a queue or
close WebSocket sessions—it owns the ordering. Stop accepting work, close the connections and end
their spans, then `await anectico.stop()`. Anectico detects application listeners whether they were
registered before or after `initNode()` and will not race them by stopping the exporter first.
Concurrent `stop()` calls share the same in-flight shutdown. The first uncaught exception or
unhandled rejection owns one fatal capture, flush, and exit sequence, so a secondary terminal event
cannot return early and cut off the original flush.

For Kubernetes, set `terminationGracePeriodSeconds` to cover application draining plus Anectico's two
managed shutdown phases. Identity/group delivery is bounded by `shutdownTimeout`, then the trace,
metric, and log providers shut down concurrently under another `shutdownTimeout`; the defaults
therefore need at least 10 seconds after application work closes.

`SIGKILL`, `kill -9`, forced zero-grace pod deletion, and host loss cannot run JavaScript handlers.
They create no terminal capture and perform no final flush. The lost tail may include active spans,
in-flight identity/group requests, up to `maxQueueSize` ended spans and logs per batched signal
(2,048 by default), and metrics accumulated since the last `metricExportInterval`. Use `SIGTERM`
and a non-zero grace period for deploys and ordinary shutdowns.

### Serverless invocation lifecycle

Initialize one client promise at module scope so a warm AWS Lambda-style runtime reuses the same
providers. At the end of every invocation, call `await anectico.flush()` rather than `stop()`: `flush()`
waits for identity requests and forces buffered traces, logs, and metrics out before the runtime may
freeze, while keeping the client running for the next invocation.

```typescript
import { context } from '@opentelemetry/api';
import { contextWithDistinctId, initNode } from '@anectico/sdk/node';

const anecticoPromise = initNode({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'invoice-automation',
});

export async function handler(event: { customerId: string }) {
  const anectico = await anecticoPromise;
  const invocationContext = contextWithDistinctId(context.active(), event.customerId);

  try {
    return await context.with(invocationContext, async () => {
      return await anectico.tracer.startActiveSpan('lambda.invoke', async span => {
        try {
          return await automateInvoice(event);
        } finally {
          span.end();
        }
      });
    });
  } finally {
    await anectico.flush();
  }
}
```

Do not call `initNode()` inside the handler or call `stop()` after a successful invocation. Repeated
in-handler initialization creates competing providers, while `stop()` permanently closes the
client. Reserve `stop()` for controlled process teardown. Keep person identity invocation-scoped
with `contextWithDistinctId`; do not switch the process-global identity for concurrent requests.

## Capture a connected operation

With the Node bootstrap above, make the operation's span active so errors and child spans share
its trace. The identity below is for a single-user script; in a concurrent server, supply identity
through the framework's request context instead.

```typescript
anectico.identify('user_8842', { email: 'buyer@acme.example', plan: 'pro' });

await anectico.tracer.startActiveSpan('process-order', async span => {
  try {
    await processOrder();
  } catch (error) {
    anectico.captureError(error as Error, { tags: { component: 'checkout' } });
    throw error;
  } finally {
    span.end();
  }
});
```

Call `anectico.reset()` on logout. In a browser it first stops active replay recorders and flushes their
prior-user tail before rotating identity/session. It also clears uncaptured breadcrumbs, groups,
and global error-user context; other already-captured telemetry keeps its original attribution.
Start a new recorder after identifying the next user if replay should resume. In a multi-user
server, prefer framework request context over changing the process-wide identity for every request.

## Record an application metric

```typescript
anectico.recordMetric('checkout.duration_ms', durationMs, {
  route: '/checkout',
  result: 'success',
});
```

`recordMetric` records one histogram observation and reuses the instrument by name. Keep labels
low-cardinality; do not add customer, order, request, trace, or session IDs. Use an existing
OpenTelemetry metrics pipeline when you need an explicit counter or gauge.

## Express, Fastify, and service propagation

```typescript
import express, { type Request } from 'express';
import { AnecticoClient } from '@anectico/sdk';
import { anecticoExpressErrorHandler, anecticoExpressMiddleware } from '@anectico/sdk/node';

const app = express();
const anectico = await new AnecticoClient({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'orders-api',
}).start();

// Install your authentication middleware before this middleware. It must verify
// credentials before setting request.user; never copy an untrusted identity header.
app.use(anecticoExpressMiddleware(anectico, {
  requestIdentityHook: request =>
    (request as Request & { user?: { id: string } }).user?.id,
}));
// Register routes here.
app.use(anecticoExpressErrorHandler(anectico)); // Always last.
```

Resolve Express identity from server-authenticated request state with
`requestIdentityHook`; the SDK scopes it to that request, propagates it to trusted
downstream services, and applies it to the preload HTTP span. Do not call the
process-global `identify()` method for each concurrent server request.

For Fastify, register the Anectico plugin before application hooks and routes. Resolve
identity from authenticated request state; do not call the process-global
`identify()` method for each concurrent server request.

```typescript
import Fastify, { type FastifyRequest } from 'fastify';
import { initNode, anecticoFastifyPlugin } from '@anectico/sdk/node';

const app = Fastify();
const anectico = await initNode({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'payments-api',
  propagateTraceHeaderUrls: ['https://authorization.internal.example'],
});

await app.register(anecticoFastifyPlugin, {
  client: anectico,
  ignorePaths: ['/health', '/internal/*'],
  requestIdentityHook: request =>
    (request as FastifyRequest & { user?: { id: string } }).user?.id,
});
// Add your authentication hook before registering routes. Only assign request.user
// after verifying credentials. Anonymous requests may leave it undefined.
```

The Fastify plugin keeps trace and identity context active across async lifecycle
hooks and route handlers. It ends each request span once on a response, timeout,
request-body abort, or response-socket disconnect. Exact and trailing-`*`
`ignorePaths` suppress both the plugin span and the generic incoming HTTP span
installed by the Node preload. When `requestIdentityHook` resolves an authenticated
customer, that identity is applied to both spans so the complete server trace has
one customer even though the preload span starts before Fastify authentication.

The middleware extracts W3C trace context. Identity baggage is ignored by default because a public
client can forge it. Change `trustIncomingIdentity` to `true` only behind a gateway that strips and
recreates client-supplied baggage.

For outbound Node `http`/`https` propagation, preload the SDK before application modules and allow
only trusted destinations:

```bash
node --import @anectico/sdk/node/register ./app.mjs
```

```typescript
import { initNode } from '@anectico/sdk/node';

const anectico = await initNode({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'orders-api',
  propagateTraceHeaderUrls: ['https://billing.internal.example'],
});
```

Trusted Node `fetch` calls are traced and receive `traceparent` plus baggage automatically.
The preload is still required when `http`/`https` modules may load before Anectico; the `fetch`
wrapper itself is installed by `initNode`, either framework integration, or the preload.

The preload, `initNode`, Express middleware, and Fastify plugin share one idempotent setup path, so
using them together does not register OpenTelemetry globals more than once. If your application installs a global
propagator before Anectico, Anectico preserves it; include both W3C Trace Context and W3C Baggage in that
propagator when you want `anectico.distinct_id` to cross trusted service boundaries.

If the application already owns a complete OpenTelemetry SDK, do not add the preload or let Anectico
register a second set of providers and instrumentations. Start the application SDK first and pass
`openTelemetryMode: 'existing'` to `initNode()`. Anectico then uses the registered global tracer, meter,
and logger while the application retains flush and shutdown ownership. See
[OpenTelemetry migration](/docs/instrument/opentelemetry).

### NestJS, Apollo GraphQL, and Prisma

Use the same preload before the NestJS entry point:

```bash
node --require @anectico/sdk/node/register dist/main.js
```

For an ESM build, use `node --import @anectico/sdk/node/register dist/main.mjs`.
The preload registers GraphQL and Prisma instrumentation before those packages
load. Anectico records named operation spans, bounded resolver spans, and Prisma
operation/query children without customer-authored spans.

Privacy defaults are fixed: GraphQL literal values become `*`, variables are not
attached to spans, repeated list paths are merged, and trivial property resolvers
are omitted. When Apollo returns GraphQL `errors` with HTTP `200`, Anectico marks the
named operation span as failed, records the original resolver exception, and adds
`graphql.error.count`. Parameterize Prisma queries and avoid including secrets in
exception messages.

If a fatal Node exception or unhandled rejection occurs during startup, Anectico prints
the original error and stack to stderr before its best-effort flush. The SDK does
not hide the diagnostic by installing its process handlers.

### Next.js App Router and Server Actions

Use Next's root `instrumentation.ts` hook to start one server client, and preload
Anectico before the Next production server so generic inbound HTTP instrumentation is
installed before Next imports Node's HTTP modules:

```bash
NODE_OPTIONS='--require @anectico/sdk/node/register' next start
```

```typescript
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME !== 'nodejs') return;
  const { getAnectico } = await import('./src/observability');
  await getAnectico();
}

export async function onRequestError(error: unknown) {
  if (process.env.NEXT_RUNTIME !== 'nodejs') return;
  const { getAnectico } = await import('./src/observability');
  const anectico = await getAnectico();
  anectico.captureError(
    error instanceof Error ? error : new Error('Next request failed'),
    { tags: { 'error.mechanism': 'next.onRequestError' } },
  );
}
```

`getAnectico()` should memoize one `initNode()` promise for the process. Configure
`propagateTraceHeaderUrls` with only the trusted origins that Server Actions call,
including the application's own origin when an Action uses an internal Route
Handler. This lets the browser request, Server Action, outbound `fetch`, and Route
Handler retain one trace and `anectico.distinct_id`:

```typescript
const anectico = await initNode({
  apiKey: process.env.ANECTICO_API_KEY,
  serviceName: 'subscription-commerce',
  propagateTraceHeaderUrls: ['https://shop.example.com'],
});
```

Next emits App Router render, Action, Route Handler, and `fetch` spans through the
global OpenTelemetry provider. For a business Action, give the active span a safe
operation name and bounded correlation attributes with `@opentelemetry/api`.
Never attach `FormData`, action payloads, cookies, authorization headers, payment
tokens, or unbounded business objects. Use the same safe idempotency identifier on
failure and retry, and capture the exception once so one failure remains visible
without multiplying Issue occurrences.

Initialize `@anectico/sdk/browser` from a Client Component after hydration and start
`@anectico/sdk/replay` only after consent. The browser key is intentionally public:
scope it to the project and grant only `ingest:write`, `analytics:write`, and—when
recording—`replay:write`. Keep management and read scopes on the server.

## Explicit product page views

Create an analytics client after any consent your application requires, using a project key
with `analytics:write` and your ingestion endpoint:

```typescript
import { AnalyticsClient } from '@anectico/sdk/analytics';

const events = new AnalyticsClient({
  endpoint: 'https://api.anectico.com',
  apiKey: import.meta.env.VITE_ANECTICO_API_KEY,
  release: import.meta.env.VITE_ANECTICO_RELEASE,
  appVersion: import.meta.env.VITE_ANECTICO_RELEASE,
});
events.page('Pricing', { plan: 'free' });
```

Call `page()` explicitly when the page or client-side route is ready. It records `$pageview`
with `$page_name` and the current identity, session and groups. It does not install navigation
tracking or collect the browser URL, title or referrer. Use stable page names and approved
properties; the SDK does not automatically redact arbitrary supplied properties.
`await events.disable()` withdraws analytics collection and discards queued events. Use
`await events.flush()` when checking queue acknowledgement; check `stats()` for drops or
uncertain delivery. See the [JavaScript SDK reference](/docs/reference/javascript-sdk) for
retry and in-memory queue limits.

### Reserved `$release` / `$app_version` properties

Every event `capture()`s (including `page()`) carries the reserved `$release` and
`$app_version` string properties when configured, never invented when they are not. Set them
explicitly with the `release`/`appVersion` constructor options above. When you omit them, Node
processes fall back to the `ANECTICO_RELEASE`/`ANECTICO_APP_VERSION` environment variables, and
browser builds fall back to `<meta name="anectico-release" content="…">` /
`<meta name="anectico-app-version" content="…">` tags — the same build-time-injection pattern
already used for source-map `release`. An app that sets none of these sends events with neither
property. See [Reserved event properties](/docs/investigate/event-schema#reserved-properties).

## React and browser credentials

```tsx
import { AnecticoClient } from '@anectico/sdk';
import { AnecticoErrorBoundary, AnecticoProvider } from '@anectico/sdk/react';

const anectico = await new AnecticoClient({
  apiKey: import.meta.env.VITE_ANECTICO_API_KEY,
  serviceName: 'web',
  release: import.meta.env.VITE_APP_RELEASE,
}).start();

root.render(
  <AnecticoProvider client={anectico}>
    <AnecticoErrorBoundary client={anectico} fallback={<ErrorPage />}>
      <App />
    </AnecticoErrorBoundary>
  </AnecticoProvider>,
);
```

Use a project-scoped custom key with `ingest:write` and `analytics:write` in browser builds. Add
`replay:write` only when recording replay. Client-side feature flags additionally need `flags:read`;
because that exposes flag configuration to the client, never put secrets in targeting rules or
payloads. Never expose other read, management, or MCP scopes. Restrict cross-origin propagation with
`propagateTraceHeaderCorsUrls`.

## Logs, replay, and AI calls

Use the client's log and error methods for manual signals. Import `@anectico/sdk/replay` only on pages
where browser replay is needed; inputs are masked by default. The `@anectico/sdk/openai` and
`@anectico/sdk/anthropic` wrappers both record streaming and non-streaming model calls — a streamed
call is recorded as one span when the stream completes, fails, or is abandoned — with content capture
off by default. For an OpenAI stream, pass `stream_options: { include_usage: true }` so token counts
and cost are available.

## Verify and recover

Generate one request for a known test customer, then open **Customers** and confirm the error, trace,
and logs share that customer and trace. If nothing arrives, enable `ANECTICO_DEBUG=true`, confirm the
key and endpoint, and stop the process gracefully before checking again.

- [Identify customers](/docs/instrument/identity)
- [Record application metrics](/docs/instrument/metrics)
- [Record session replay](/docs/instrument/session-replay)
- [Record LLM calls](/docs/instrument/llm-calls)
- [JavaScript SDK API reference](/docs/reference/javascript-sdk)
- [Configuration reference](/docs/reference/configuration)
