# https://github.com/arcjet/arcjet-js Project Manual

Generated at: 2026-07-19 08:57:01 UTC

## Table of Contents

- [Getting Started and Package Selection](#page-1)
- [Framework SDKs (Request Protection)](#page-2)
- [Arcjet Guard (Non-HTTP Protection)](#page-3)
- [Core Architecture and Shared Internals](#page-4)

<a id='page-1'></a>

## Getting Started and Package Selection

### Related Pages

Related topics: [Framework SDKs (Request Protection)](#page-2), [Arcjet Guard (Non-HTTP Protection)](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/arcjet/arcjet-js/blob/main/README.md)
- [package.json](https://github.com/arcjet/arcjet-js/blob/main/package.json)
- [CONTRIBUTING.md](https://github.com/arcjet/arcjet-js/blob/main/CONTRIBUTING.md)
- [CHANGELOG.md](https://github.com/arcjet/arcjet-js/blob/main/CHANGELOG.md)
- [pnpm-workspace.yaml](https://github.com/arcjet/arcjet-js/blob/main/pnpm-workspace.yaml)
</details>

# Getting Started and Package Selection

## Purpose and Scope

Arcjet JS is a multi-package monorepo that ships security primitives (rate limiting, bot detection, email validation, sensitive info redaction, and shielding) as a unified SDK plus framework-specific adapters. The "Getting Started" path begins with choosing the right package for your runtime, then installing it and providing an API key. Because the repository is a workspace, contributors and integrators must also understand how the packages are organized before importing anything.

Source: [README.md:1-40]()

## Repository Structure and Workspaces

The repository is managed with pnpm workspaces, and the workspace declaration lists every package that ships under the `@arcjet/*` and `@arcjet-protocol/*` namespaces. A typical layout includes:

- `@arcjet/protocol` — generated client/server stubs for the gRPC API.
- `@arcjet/next`, `@arcjet/sveltekit`, `@arcjet/astro`, `@arcjet/bun`, `@arcjet/deno`, `@arcjet/nestjs`, `@arcjet/express`, `@arcjet/hono` — first-party framework adapters.
- `@arcjet/node`, `@arcjet/edge`, `@arcjet/web` — generic runtimes used internally by adapters.
- `@arcjet/redact`, `@arcjet/ip`, `@arcjet/headers`, `@arcjet/transport`, `@arcjet/runtime`, `@arcjet/analyze`, `@arcjet/sensitive-info` — building blocks consumed by the adapters.

Source: [package.json:1-60](), [pnpm-workspace.yaml:1-20]()

The top-level `package.json` defines shared scripts (`build`, `test`, `lint`, `format`) and pinned `engines` for Node, pnpm, and Corepack, so contributors must use the matching toolchain before building any adapter.

Source: [package.json:30-80]()

## Selecting the Right Package

Choosing the package is a function of your deployment target rather than your business logic. A simplified decision map:

| Runtime / Framework | Package to install |
|---|---|
| Next.js (App Router or Pages) | `@arcjet/next` |
| SvelteKit | `@arcjet/sveltekit` |
| Astro | `@arcjet/astro` |
| Bun (HTTP server) | `@arcjet/bun` |
| Deno | `@arcjet/deno` |
| NestJS | `@arcjet/nestjs` |
| Express / Hono on Node | `@arcjet/express`, `@arcjet/hono` |
| Generic edge function / custom runtime | `@arcjet/edge` |

Source: [README.md:60-140]()

If you target an environment without a dedicated adapter, fall back to `@arcjet/edge` and wire `protect()` into your request handler manually. Community request #5152 explicitly notes that some runtimes, such as Netlify Edge Functions, still need their own adapter rather than reusing an existing one, so check the adapter list before assuming compatibility.

Source: [README.md:60-140](), [CHANGELOG.md:1-40]()

## Installation and Initial Configuration

Once the adapter is selected, install it as a single dependency. The monorepo tracks zero external runtime dependencies as a goal — see community issue #44 ("Zero dependencies for JS SDK") — so each adapter should add only itself to your application.

Source: [CHANGELOG.md:1-20]()

The minimum configuration is:

1. Set `ARCJET_KEY` in your environment.
2. Call `arcjet({ key, rules: [...] })` once at module scope.
3. Invoke `protect(request)` inside your route handler and branch on `decision.isDenied()` / `decision.reason`.

Source: [README.md:140-260]()

Because rules are evaluated in the order you supply them, place cheap checks (for example, `validateEmail`) before expensive ones (`shield`, `detectBot`) when you stack them.

Source: [README.md:180-220]()

## Development Mode Behavior

In local development, Arcjet cannot resolve a real client IP, so the SDK emits `✦Aj WARN Using 127.0.0.1 as IP address in development mode`. Community issue #1781 reports that this warning is logged on every request and pollutes logs. The currently recommended workaround is to silence it once during client initialization by setting `characteristics: ["ip"]` explicitly or filtering on the `✦Aj` prefix, until upstream reduces the log frequency.

Source: [README.md:260-310]()

## Error Handling and Structured Errors

Right now, missing or invalid configuration surfaces as a thrown error that propagates as a 500 to the caller. Community issue #1855 proposes structured errors so an integrator can map a "missing header" failure to a 400 instead. Until that lands, wrap `protect()` in `try/catch` and inspect `error.code` if you need that level of control today.

Source: [README.md:310-360]()

## Contributing and Release Cadence

Contributors should read `CONTRIBUTING.md` before opening a pull request; it documents the pnpm-based workflow, the required `pnpm build && pnpm test` check, and the commit message convention enforced by release-please. Releases are published automatically through a Changesets/renovate-style pipeline, and the latest published version is reflected in `CHANGELOG.md` (currently `v1.9.1`).

Source: [CONTRIBUTING.md:1-60](), [CHANGELOG.md:1-20]()

Dependency updates are increasingly managed through Renovate (community issue #5201) rather than Dependabot, which means contributors should expect grouped upgrade PRs and a dashboard rather than per-package bumps.

Source: [CHANGELOG.md:1-20]()

## Summary

To onboard:

1. Pick the adapter that matches your framework, or `@arcjet/edge` if none fits.
2. Install it as a single dependency, set `ARCJET_KEY`, and call `protect()` in your handler.
3. Order rules from cheap to expensive, branch on the decision, and handle thrown errors defensively until structured errors land.
4. Suppress the dev-mode IP warning during initialization to keep logs readable.

This selection-first flow keeps the integration surface narrow and aligns with the monorepo's goal of one focused package per runtime.

---

<a id='page-2'></a>

## Framework SDKs (Request Protection)

### Related Pages

Related topics: [Getting Started and Package Selection](#page-1), [Core Architecture and Shared Internals](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [arcjet/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet/src/index.ts)
- [arcjet/src/arcjet.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet/src/arcjet.ts)
- [arcjet/src/client.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet/src/client.ts)
- [arcjet-next/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-next/src/index.ts)
- [arcjet-node/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-node/src/index.ts)
- [arcjet-bun/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-bun/src/index.ts)
- [arcjet-deno/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-deno/src/index.ts)
- [arcjet-fastify/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-fastify/src/index.ts)
</details>

# Framework SDKs (Request Protection)

The `arcjet-js` monorepo ships a core engine alongside a family of framework-specific SDKs ("adapters"). Each adapter wraps the core `arcjet` package, exposing a uniform `protect()` entry point that maps the framework's incoming request into the shape required by the rule engine, evaluates the configured rules, and returns a normalized decision. Source: [arcjet/src/index.ts:1-80]()

## Purpose and Scope

Framework SDKs are responsible for three concerns only:

1. **Adapting the request** — extracting the IP, headers, method, and path from a framework-native object (`Request`, `http.IncomingMessage`, Fastify `Request`, Bun `Request`, Deno `Request`, etc.).
2. **Delegating to the core engine** — handing the normalized inputs to `arcjet.protect()`.
3. **Surfacing the result** — returning a framework-friendly response object so user code can branch on `decision.isDenied()` and friends.

The core engine, transport, rule evaluation, and decision shape all live in [`arcjet/`](arcjet/src/arcjet.ts). Adapters intentionally stay thin so that the same rule configuration works across runtimes. Source: [arcjet/src/arcjet.ts:1-120]()

## Adapter Inventory

The repository publishes one adapter per supported platform. Each one re-exports the public surface of `arcjet` and adds a `protect()` helper tuned to the runtime's request type.

| Adapter | Entry file | Target runtime | Request source |
|---|---|---|---|
| `arcjet-next` | [arcjet-next/src/index.ts](arcjet-next/src/index.ts) | Next.js (App Router, Pages, Edge & Node) | NextRequest |
| `arcjet-node` | [arcjet-node/src/index.ts](arcjet-node/src/index.ts) | Node.js `http` server | `http.IncomingMessage` |
| `arcjet-bun` | [arcjet-bun/src/index.ts](arcjet-bun/src/index.ts) | Bun.serve | Bun `Request` |
| `arcjet-deno` | [arcjet-deno/src/index.ts](arcjet-deno/src/index.ts) | Deno.serve / `Deno.serveHttp` | Deno `Request` |
| `arcjet-fastify` | [arcjet-fastify/src/index.ts](arcjet-fastify/src/index.ts) | Fastify | Fastify `Request` |

The pattern in every adapter is consistent: import `Arcjet`, the rule builders, and `createRemoteClient`/`createLocalClient` from `@arcjet/core` (or the local core under `arcjet/src`), then expose a configured `protect()` function. Source: [arcjet-next/src/index.ts:1-60](); [arcjet-node/src/index.ts:1-60](); [arcjet-bun/src/index.ts:1-60](); [arcjet-deno/src/index.ts:1-60](); [arcjet-fastify/src/index.ts:1-60]()

## The `protect()` Flow

Across adapters the call sequence is:

1. **Construct the client** — adapters call `Arcjet({ key, rules, ... })` which internally sets up logging, environment detection, and either a local stub or remote gRPC client via `createRemoteClient`. Source: [arcjet/src/client.ts:1-200]()
2. **Build an `ArcjetRequest`** — the adapter reads `headers`, `method`, `url`, and a remote IP from the framework request, defaulting to `127.0.0.1` in development.
3. **Invoke `arcjet.protect(request)`** — the core engine runs the configured rules (rate limit, shield, bot detection, sensitive info, email validation, etc.), produces a `Decision`, and attaches `results` describing which rule concluded.
4. **Return the `Decision`** — adapters pass the decision back untouched so user code can call `decision.isDenied()`, `decision.isAllowed()`, `decision.reason`, or `decision.results`.

A simplified view of this flow:

```mermaid
flowchart LR
    A[Framework Request] --> B[Adapter protect()]
    B --> C[ArcjetRequest adapter]
    C --> D[Arcjet Core protect]
    D --> E[Decision]
    E --> F[isAllowed / isDenied]
```

Source: [arcjet/src/arcjet.ts:1-200](); [arcjet/src/index.ts:1-80]()

## Request Normalization and Edge Concerns

Because every runtime exposes a slightly different request primitive, each adapter must decide how to extract the client IP. The shared convention is documented in the core `protect()` implementation and adapter wrappers: prefer `x-forwarded-for`, fall back to the connection peer address, and emit a one-time warning when no remote address is available. Source: [arcjet/src/arcjet.ts:120-220]()

A few practical considerations surface from this design:

- **Development-mode log noise** — the warning `✦Aj WARN Using 127.0.0.1 as IP address in development mode` is emitted per `protect()` call today. The team is tracking whether it should be rate-limited to client init (community issue #1781).
- **Missing-header errors** — when a rule needs a header that the framework strips (common in some edge runtimes), the core currently throws, which surfaces as a 500. Structured errors (community issue #1855) would let adapters translate this into a 400.
- **Adapter coverage gaps** — platforms not yet covered require their own adapter; for example, Netlify Edge Functions (community issue #5152) is a known gap because of its specialized global object.
- **Dependency footprint** — adapters keep their `dependencies` minimal and rely on the core package; issue #44 tracks the goal of "zero dependencies" beyond the core engine.

## Choosing an Adapter

Pick the adapter that matches your deployment target, not your language. For example, a Next.js app deployed to the Edge runtime should use `arcjet-next` rather than `arcjet-node`, even though both can theoretically run on Vercel — the Next.js adapter is the only one that understands `NextRequest` and middleware conventions. Source: [arcjet-next/src/index.ts:1-80]()

If you are writing a generic Node HTTP handler with no framework, `arcjet-node` is the lowest-level option; Fastify, Bun, and Deno users should use the matching adapter to get first-class request typing. Source: [arcjet-node/src/index.ts:1-80](); [arcjet-fastify/src/index.ts:1-80]()

## Summary

Framework SDKs in `arcjet-js` are thin, consistent wrappers over a single core engine. They convert a framework-native request into the core's `ArcjetRequest`, return the same `Decision` shape regardless of runtime, and let users write one set of rules that travels across Node, Bun, Deno, Next.js, and Fastify. The shared responsibilities — IP extraction, defaulting, logging, and dependency surface — are all centralized so that new adapters (Netlify edge, etc.) can be added with minimal duplication. Source: [arcjet/src/index.ts:1-80](); [arcjet/src/client.ts:1-200](); [arcjet/src/arcjet.ts:1-220]()

---

<a id='page-3'></a>

## Arcjet Guard (Non-HTTP Protection)

### Related Pages

Related topics: [Getting Started and Package Selection](#page-1), [Core Architecture and Shared Internals](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [arcjet-guard/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-guard/src/index.ts)
- [arcjet-guard/src/client.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-guard/src/client.ts)
- [arcjet-guard/src/rules.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-guard/src/rules.ts)
- [arcjet-guard/src/convert.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-guard/src/convert.ts)
- [arcjet-guard/src/types.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-guard/src/types.ts)
- [arcjet-guard/src/fetch.ts](https://github.com/arcjet/arcjet-js/blob/main/arcjet-guard/src/fetch.ts)
</details>

# Arcjet Guard (Non-HTTP Protection)

Arcjet Guard is the non-HTTP counterpart of the Arcjet SDK. Where `@arcjet/next`, `@arcjet/node`, or `@arcjet/bun` adapters protect request/response cycles driven by a web framework, `@arcjet/guard` protects arbitrary execution contexts that do not have an HTTP request: cron jobs, message queue workers, scheduled tasks, CLI tools, background scripts, and any code path that just wants rate-limiting or shielding rules applied to a discrete unit of work.

## Purpose and Scope

The package exists to let developers call the Arcjet decision engine without needing to construct an HTTP `Request` object. It exposes a small façade that:

- Accepts a `protect()` payload with an arbitrary `userId`-style identifier (or no identifier for IP-less flows).
- Forwards rules to Arcjet's hosted decision API via the same transport layer that the HTTP adapters use.
- Returns an `ArcjetGuardDecision` carrying `conclusion`, `reason`, and rule results so callers can branch in plain JavaScript.

It is deliberately minimal. It is not a server framework and does not attempt to interpret transports; that responsibility belongs to the platform-specific adapter packages (`Source: [arcjet-guard/src/index.ts:1-80]()`).

## Architecture and Module Layout

The package is organized around a thin client wrapper that reuses the shared Arcjet primitives.

```
index.ts   ── public surface (protect, ArcjetGuard)
client.ts  ── runtime logic + remote call orchestration
rules.ts   ── canonical rule builders (shield, rate-limit, bot, sensitive-info, email)
convert.ts ── normalization of guard inputs into SDK decision calls
types.ts   ── public TypeScript contracts (ProtectInput, ArcjetGuardDecision, etc.)
fetch.ts   ── WHATWG fetch abstraction with timeout/retry knobs
```

`index.ts` is a façade: it re-exports `ArcjetGuard` and `protect` and pulls in optional shared type aliases. The real work happens in `client.ts`, which composes a `RemoteClient` configured against `ARCJET_BASE_URL` and the API key resolved from environment (`Source: [arcjet-guard/src/client.ts:1-120]()`).

### Data Flow

When a caller invokes `protect({ userId, rules })`:

1. `convert.ts` transforms the guard-specific payload into the generic SDK decision request shape, including fingerprint, characteristics, and rule definitions (`Source: [arcjet-guard/src/convert.ts:1-90]()`).
2. `rules.ts` produces rule instances via fluent builders (`shield()`, `rateLimit()`, `bot()`, etc.) and ensures each carries a stable `version` so the server side can deserialize them correctly (`Source: [arcjet-guard/src/rules.ts:1-150]()`).
3. `client.ts` posts the decision call to Arcjet over `fetch.ts`, which wraps the global `fetch` with `AbortController`-based timeouts and centralized error reporting (`Source: [arcjet-guard/src/fetch.ts:1-70]()`).
4. The remote response is normalized into `ArcjetGuardDecision`, exposing `allowed: boolean`, `conclusion`, and structured `results` arrays (`Source: [arcjet-guard/src/types.ts:1-110]()`).

| Concern | Guard package | HTTP adapter (e.g. `@arcjet/next`) |
| --- | --- | --- |
| Input | `userId` / arbitrary key | `Request` / headers / IP |
| Fingerprint source | Caller-supplied identifier | Request-derived (IP, headers) |
| Decision surface | `protect()` Promise | Middleware/`protect()` Promise |
| Transport | `fetch.ts` (environment) | `edge`/`runtime` specific fetch |

The asymmetry above is intentional: Guard lets a developer choose the identity (a user, a tenant, a job name) instead of inheriting it from request metadata (`Source: [arcjet-guard/src/convert.ts:20-60]()`).

## Usage Patterns

A typical cron-job use case identifies each invocation by a stable string so the rate-limit rule can bucket correctly:

```ts
import arcjet, { shield, tokenBucket } from "@arcjet/guard";

const aj = arcjet({
  characteristics: ["userId"],
  rules: [
    shield({ mode: "LIVE" }),
    tokenBucket({ refillRate: 60, interval: 60, capacity: 100 }),
  ],
});

const decision = await aj.protect({ userId: "nightly-export" });
if (!decision.allowed) throw new Error("Job throttled");
```

Because Guard does not infer IP, users wanting IP-based protection must supply it explicitly or combine it with another identifier. This trade-off is what `types.ts` documents: `ProtectInput` is restricted to non-HTTP fields so misuse surfaces at compile time (`Source: [arcjet-guard/src/types.ts:30-90]()`).

### Errors and Observability

When the remote API is unreachable or returns an unexpected status, `fetch.ts` raises a categorized error. In development, the client logs a single warning per process — directly addressing the repeated-warning complaint raised in community issue #1781, where users asked that the `Using 127.0.0.1 as IP address` log fire only once during init rather than on every call (`Source: [arcjet-guard/src/client.ts:60-90]()`). Related to issue #1855, `ArcjetGuardDecision` exposes the rule reasons as a discriminated union so callers can map a missing context (e.g. empty `userId`) to a `400`-style response inside a queue handler instead of treating it as a server failure (`Source: [arcjet-guard/src/types.ts:60-110]()`).

## Limitations and Boundaries

Guard inherits the shared dependency set documented in community issue #44 ("Zero dependencies for JS SDK"): the package deliberately delegates HTTP transport to the runtime so its own dependency graph stays minimal. It also assumes the caller has configured `ARCJET_API_KEY` (and optionally `ARCJET_BASE_URL`) before `protect()` resolves (`Source: [arcjet-guard/src/index.ts:30-80]()`).

It is **not** a replacement for HTTP adapters — there is no automatic IP extraction, cookie inspection, or response gating. For Netlify Edge Functions and similar environments a separate adapter is required, as tracked in issue #5152; Guard remains a complementary building block for non-request-driven code paths (`Source: [arcjet-guard/src/client.ts:90-120]()`).

---

<a id='page-4'></a>

## Core Architecture and Shared Internals

### Related Pages

Related topics: [Framework SDKs (Request Protection)](#page-2), [Arcjet Guard (Non-HTTP Protection)](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [protocol/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/protocol/src/index.ts)
- [protocol/src/client.ts](https://github.com/arcjet/arcjet-js/blob/main/protocol/src/client.ts)
- [protocol/src/convert.ts](https://github.com/arcjet/arcjet-js/blob/main/protocol/src/convert.ts)
- [transport/src/index.ts](https://github.com/arcjet/arcjet-js/blob/main/transport/src/index.ts)
- [transport/src/proxy-tunnel.ts](https://github.com/arcjet/arcjet-js/blob/main/transport/src/proxy-tunnel.ts)
- [transport/src/detect-proxy.ts](https://github.com/arcjet/arcjet-js/blob/main/transport/src/detect-proxy.ts)
- [analyze/index.ts](https://github.com/arcjet/arcjet-js/blob/main/analyze/index.ts)
- [analyze/logger.ts](https://github.com/arcjet/arcjet-js/blob/main/analyze/logger.ts)
- [ip/index.ts](https://github.com/arcjet/arcjet-js/blob/main/ip/index.ts)
</details>

# Core Architecture and Shared Internals

## Architecture Overview

The `arcjet-js` repository is a pnpm monorepo of small, composable packages. The public SDKs (`@arcjet/next`, `@arcjet/bun`, `@arcjet/sveltekit`, `@arcjet/deno`, etc.) are thin adapters that share a stable internal stack of three layers:

1. **`protocol`** — the wire format and HTTP client that talks to Arcjet's decision service.
2. **`transport`** — networking plumbing: HTTPS by default, with HTTPS/HTTP `CONNECT` proxy tunneling when a proxy is detected.
3. **`analyze` and `ip`** — framework-agnostic "shared internals": decision types, reasons, a logger interface, and IP address helpers.

This split exists so each adapter only has to translate framework-shaped requests into the neutral `SerializableRequest`, after which every rule, transport decision, and error path is identical across runtimes. Keeping the core small is a deliberate design goal tracked in issue #44 ("Zero dependencies for JS SDK").

## Protocol and Transport Layers

The **protocol** package is the contract with Arcjet's backend.

- `protocol/src/index.ts` re-exports the canonical inputs (`Decide`, `DecideDetails`, `SerializableRequest`) and the canonical outputs (`Decision` plus reason types such as `RateLimitReason`, `BotReason`, `EmailReason`, `ShieldReason`). Source: [protocol/src/index.ts:1-80]()
- `protocol/src/client.ts` implements `createClient()`, which wraps `undici`/`fetch` with retries, a request timeout, and structured error subclasses (`ArcjetNetworkError`, `ArcjetServerError`) instead of leaking raw fetch errors. Source: [protocol/src/client.ts:40-160]()
- `protocol/src/convert.ts` performs two-way translation between SDK-shaped `ArcjetDecision` objects (which carry local rule state) and wire-shaped `Decision` objects. It also evaluates local-only rules so that a round-trip to the server can be skipped when the answer can be derived from headers or the request body alone. Source: [protocol/src/convert.ts:25-140]()

The **transport** package isolates everything network-related.

- `transport/src/index.ts` exposes `createTransport({ endpoint, proxy? })`, returning a transport whose `dispatch()` method posts a `DecideRequest`. Source: [transport/src/index.ts:20-90]()
- `transport/src/detect-proxy.ts` reads `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY`, and runtime-specific proxy settings, returning a normalized proxy URL or `undefined`. Source: [transport/src/detect-proxy.ts:1-60]()
- `transport/src/proxy-tunnel.ts` implements HTTP `CONNECT` tunneling: it opens a TCP socket to the proxy, sends `CONNECT host:port HTTP/1.1`, waits for `200 Connection established`, then wraps that socket with TLS and forwards the original HTTPS request. Source: [transport/src/proxy-tunnel.ts:30-150]()

Proxy support is centralized here on purpose. The Netlify Edge adapter tracked in issue #5152 will extend `detect-proxy.ts` because Edge runtimes expose a different set of proxy hints than Node.

## Shared Internals: Decisions, Logger, and IP

Several small packages contain types and helpers imported by both the protocol layer and every adapter.

- `analyze/index.ts` defines `ArcjetDecision`, `ArcjetReason`, and `ArcjetConclusion`. These are the framework-agnostic shapes returned from `await aj.protect(req)`. Source: [analyze/index.ts:1-120]()
- `analyze/logger.ts` defines a minimal `Logger` interface (`debug`, `info`, `warn`, `error`). Adapters supply a runtime-specific implementation (`pino` for Node, `console` for Edge runtimes), but rule and protocol code depend only on this interface, which keeps the core dep-free. Source: [analyze/logger.ts:1-40]() The "noisy" `✦Aj WARN Using 127.0.0.1 as IP address in development mode` log that motivated issue #1781 is emitted from this logger surface.
- `ip/index.ts` is the single source of truth for IP parsing, CIDR search, and anonymization helpers ("is this a private/local IP?", "what's the client IP from these headers?"). Source: [ip/index.ts:1-180]()

Errors currently surface as plain `Error` subclasses thrown from `protocol/src/client.ts`. Issue #1855 ("Consider building structured errors") proposes richer error metadata so an adapter can map a missing header to a `400` instead of a `500`. Because every error originates in the protocol layer and propagates through every transport, any structured-error design must be introduced at that boundary to stay uniform across adapters.

## Typical Request Flow

```mermaid
sequenceDiagram
    participant App as User app
    participant Adapter as Framework adapter
    participant Core as analyze + ip
    participant Protocol as protocol/client
    participant Transport as transport
    participant Arcjet as Arcjet service

    App->>Adapter: protect(request)
    Adapter->>Core: extract IP, build SerializableRequest
    Adapter->>Protocol: createClient(endpoint, options)
    Protocol->>Transport: dispatch(DecideRequest)
    alt Proxy detected (HTTPS_PROXY, etc.)
        Transport->>Transport: detect-proxy + CONNECT tunnel
    end
    Transport->>Arcjet: HTTPS POST /decide
    Arcjet-->>Transport: Decision JSON
    Transport-->>Protocol: parsed response
    Protocol->>Protocol: convert.ts maps wire -> ArcjetDecision
    Protocol-->>Adapter: ArcjetDecision
    Adapter-->>App: { allowed, results, ... }
```

The diagram shows why the layering matters: an Edge runtime adapter and a Node HTTP adapter produce the same `ArcjetDecision` because everything below `protocol/src/client.ts` — proxy detection, tunneling, error wrapping, decision translation — is shared.

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: arcjet/arcjet-js

Summary: Found 22 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Identity risk - Identity risk requires verification.

## 1. Identity risk - Identity risk requires verification

- Severity: medium
- Evidence strength: runtime_trace
- Finding: Project evidence flags a identity risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Repro command: `npm i @arcjet/next`
- Evidence: identity.distribution | https://www.npmjs.com/package/@arcjet/next

## 2. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: Dependency Dashboard
- User impact: Developers may fail before the first successful local run: Dependency Dashboard
- Evidence: failure_mode_cluster:github_issue | https://github.com/arcjet/arcjet-js/issues/5907

## 3. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: arcjet 1.8.0 wont resolve on windows, dist/_virtual/_. folder has a trailing dot
- User impact: Developers may fail before the first successful local run: arcjet 1.8.0 wont resolve on windows, dist/_virtual/_. folder has a trailing dot
- Evidence: failure_mode_cluster:github_issue | https://github.com/arcjet/arcjet-js/issues/6136

## 4. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: v1.3.0
- User impact: Upgrade or migration may change expected behavior: v1.3.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.3.0

## 5. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: v1.3.1
- User impact: Upgrade or migration may change expected behavior: v1.3.1
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.3.1

## 6. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: v1.6.0
- User impact: Upgrade or migration may change expected behavior: v1.6.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.6.0

## 7. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this installation risk before relying on the project: v1.7.0
- User impact: Upgrade or migration may change expected behavior: v1.7.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.7.0

## 8. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/arcjet/arcjet-js/issues/6136

## 9. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.host_targets | https://www.npmjs.com/package/@arcjet/next

## 10. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: v1.5.0
- User impact: Upgrade or migration may change expected behavior: v1.5.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.5.0

## 11. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this configuration risk before relying on the project: v1.9.0
- User impact: Upgrade or migration may change expected behavior: v1.9.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.9.0

## 12. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://www.npmjs.com/package/@arcjet/next

## 13. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Developers should check this migration risk before relying on the project: v1.4.0
- User impact: Upgrade or migration may change expected behavior: v1.4.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.4.0

## 14. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://www.npmjs.com/package/@arcjet/next

## 15. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://www.npmjs.com/package/@arcjet/next

## 16. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://www.npmjs.com/package/@arcjet/next

## 17. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/arcjet/arcjet-js/issues/5907

## 18. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://www.npmjs.com/package/@arcjet/next

## 19. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://www.npmjs.com/package/@arcjet/next

## 20. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v1.6.1
- User impact: Upgrade or migration may change expected behavior: v1.6.1
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.6.1

## 21. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v1.8.0
- User impact: Upgrade or migration may change expected behavior: v1.8.0
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.8.0

## 22. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: Developers should check this maintenance risk before relying on the project: v1.9.1
- User impact: Upgrade or migration may change expected behavior: v1.9.1
- Evidence: failure_mode_cluster:github_release | https://github.com/arcjet/arcjet-js/releases/tag/v1.9.1

<!-- canonical_name: arcjet/arcjet-js; human_manual_source: deepwiki_human_wiki -->
