npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@davidoost/eva-nextjs-sdk

v0.1.11

Published

Lightweight Next.js SDK for Eva OMS

Downloads

1,326

Readme

@davidoost/eva-nextjs-sdk

Lightweight Next.js SDK for the EVA OMS API.

Fully-typed HTTP client following the same SSR pattern as Supabase. Types are generated into your project against your specific EVA environment — so you always have accurate types regardless of which services your instance has enabled, and you're never blocked by a new SDK release when EVA ships weekly updates.


Installation

npm install @davidoost/eva-nextjs-sdk

Requires next >= 15.0.0 as a peer dependency.


Setup

1. Environment variables

# .env.local
EVA_BASE_URL=https://api.euw.your-store.eva-online.cloud
EVA_API_KEY=your-api-key
NEXT_PUBLIC_EVA_BASE_URL=https://api.euw.your-store.eva-online.cloud

# Optional — sets EVA-Requested-OrganizationUnitID on every request
EVA_ORGANIZATION_UNIT_ID=1
NEXT_PUBLIC_EVA_ORGANIZATION_UNIT_ID=1

2. Generate types

Run the generate command once (and again whenever your EVA environment gets updated):

EVA_BASE_URL=https://api.euw.your-store.eva-online.cloud npx @davidoost/eva-nextjs-sdk generate

This downloads the eva-apispec generator binary, runs it against your environment, and writes the following into your project:

src/eva/
  generated/          ← EVA service types, specific to your environment
    eva-services-*/
    service-map.ts
  server.ts           ← typed createClient() and createApiClient()
  client.ts           ← typed createClient() for Client Components

Commit the generated files. Re-run the command when your EVA environment updates.

3. Options

# Custom output directory (default: src/eva)
npx @davidoost/eva-nextjs-sdk generate --output src/lib/eva

# Use latest public spec instead of your environment
npx @davidoost/eva-nextjs-sdk generate

Usage

Server Components

import { createClient } from '@/eva/server';
import { asProduct } from '@davidoost/eva-nextjs-sdk';

export default async function ProductPage({ params }) {
  const client = await createClient();
  const response = await client.GetProductDetail({ ProductID: params.id });
  const product = asProduct(response.Result);
  return <div>{product?.display_value}</div>;
}

Server Actions

'use server';
import { createClient } from '@/eva/server';

export async function login(email: string, password: string) {
  const client = await createClient();
  await client.login({ EmailAddress: email, Password: password });
  // session cookies are set automatically
}

export async function logout() {
  const client = await createClient();
  await client.logout();
}

export async function addToCart(productId: number, quantity: number) {
  const client = await createClient();
  await client.AddProductToOrder({ ProductID: productId, Quantity: quantity });
}

Route Handlers

// app/api/products/route.ts
import { createClient } from '@/eva/server';

export async function GET() {
  const client = await createClient();
  const data = await client.SearchProducts({ Query: '' });
  return Response.json(data);
}

Client Components

Session cookies are httpOnly — the browser can't read them. For authenticated mutations prefer Server Actions. For public calls use the browser client directly:

'use client';
import { createClient } from '@/eva/client';

export function SearchBar() {
  const handleSearch = async (query: string) => {
    const client = createClient();
    const results = await client.SearchProducts({ Query: query });
  };
}

API key client

createApiClient() uses your EVA_API_KEY and has access to all EVA services — including the ~30 that are API-key-only and unavailable via session token:

import { createApiClient } from '@/eva/server';

export async function GET() {
  const client = createApiClient();
  const data = await client.SomeService({});
  return Response.json(data);
}

Where things can run

| | Server Component | Server Action | Route Handler | Client Component | |---|:---:|:---:|:---:|:---:| | createClient() from eva/server | ✅ | ✅ | ✅ | ❌ | | createApiClient() from eva/server | ✅ | ✅ | ✅ | ❌ | | createClient() from eva/client | ❌ | ❌ | ❌ | ✅ | | client.login() | ❌ | ✅ | ✅ | ❌ | | client.logout() | ❌ | ✅ | ✅ | ❌ |

login() and logout() write cookies — only allowed in Server Actions and Route Handlers.


Silent token refresh

The server client automatically refreshes the access token when it's missing but a refresh token is present. In a Server Action or Route Handler the new token is written to cookies. In a Server Component the write fails silently — middleware handles persistence on the next navigation.


Middleware

Use checkAuth in middleware.ts to refresh tokens and guard routes. Always return result.response — it carries refreshed cookies to both the browser and Server Components in the same request.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { checkAuth } from '@davidoost/eva-nextjs-sdk';

export async function middleware(request: NextRequest) {
  const { valid, refreshed, response } = await checkAuth(request);

  if (!valid && !refreshed) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return response;
}

export const config = {
  matcher: ['/account/:path*', '/checkout/:path*'],
};

| Result | Meaning | |---|---| | valid: true | Access token present | | refreshed: true | Token refreshed — new cookies on response | | valid: false, refreshed: false | No session — redirect to login |

Gating by role

const { valid, refreshed, role, response } = await checkAuth(request, {
  requiredRole: 'employee', // 'customer' | 'employee'
});

if (!valid && !refreshed) return NextResponse.redirect(new URL('/login', request.url));
if (role !== 'employee')  return NextResponse.redirect(new URL('/403', request.url));

return response;

The role is derived from data.User.Type at login and stored in eva_user_role cookie automatically.


Debug mode

Set debug: true on any client to log all requests and responses to the console. Auth tokens are redacted.

const client = await createServerClient({ ..., debug: true });
// or
const client = createApiClient({ debug: true });
// or
const client = createClient({ debug: true });

Server-side output appears in your terminal. Browser client output appears in the browser console (requests are also visible in the Network tab since they originate from the browser).

[eva-sdk] POST https://api.euw.store.eva-online.cloud/message/SearchProducts
[eva-sdk] Headers: { EVA-User-Agent: 'eva-nextjs-sdk/0.1.8', EVA-App-Token: '...' }
[eva-sdk] Body: {"Query":"roses"}
[eva-sdk] 200 OK — https://api.euw.store.eva-online.cloud/message/SearchProducts

App token (anonymous sessions)

EVA returns an EVA-App-Token header on responses. The SDK stores it automatically in an eva_app_token cookie and sends it back as an EVA-App-Token request header on every subsequent request — enabling anonymous/guest sessions without any extra code. It is sent alongside the Authorization header when a user is logged in.

| Client | Stores app token | Uses app token as fallback | |---|---|---| | createClient() (server) | ✅ via cookieMethods.setAll | ✅ | | createClient() (browser) | ✅ via document.cookie | ✅ | | createApiClient() | — | — |

The eva_app_token cookie is non-httpOnly on the browser client so JavaScript can read and write it.


Organization unit

The EVA-Requested-OrganizationUnitID header is resolved with this precedence for each client:

| Client | Precedence | |---|---| | createClient() (server) | organizationUnitId option → eva_ou cookie → EVA_ORGANIZATION_UNIT_ID env var | | createApiClient() | organizationUnitId option → EVA_ORGANIZATION_UNIT_ID env var | | createClient() (browser) | organizationUnitId option → eva_ou cookie → NEXT_PUBLIC_EVA_ORGANIZATION_UNIT_ID env var |

The eva_ou cookie must be non-httpOnly so the browser client can read it.

Via env var (global default, set once):

EVA_ORGANIZATION_UNIT_ID=1
NEXT_PUBLIC_EVA_ORGANIZATION_UNIT_ID=1

Per client instance (overrides env var):

const client = await createServerClient({ ..., organizationUnitId: 456 });

Via cookie (useful for letting users switch between org units dynamically):

// Set a non-httpOnly cookie named 'eva_ou'
cookieStore.set('eva_ou', '456', { path: '/' }); // no httpOnly

Product types

EVA product data is schema-less at the API level. Use asProduct to cast to a typed interface:

import { asProduct } from '@davidoost/eva-nextjs-sdk';

const response = await client.GetProductDetail({ ProductID: 123 });
const product  = asProduct(response.Result);

product?.display_value;  // string
product?.display_price;  // number
product?.primary_image;  // { Url?, Blob?, Name?, Type? }

Standard fields on EvaProduct:

| Field | Type | Description | |---|---|---| | product_id | number | EVA internal product ID | | backend_id | string? | External / backend identifier | | display_value | string | Product name | | slug | string? | URL-friendly slug | | barcode | string? | Barcode | | custom_id | string? | Custom identifier | | product_type | number? | 1 Simple · 2 Bundle · 4 Configurable · 8 Variant | | logical_level | number? | 0 root configurable · 1 variant | | primary_image | object? | { Url, Blob, Name, Type } | | media | object? | Gallery, swatches, image count | | category_names | string[]? | Category names | | category_paths | string[][]? | Full category paths | | brand_name | string? | Brand display name | | brand_id | number? | EVA brand ID | | display_price | number? | Current selling price | | original_price | number? | List price before discounts | | discount_percentage | number? | e.g. 25 for 25% off |

Custom product properties

Declare your EVA-configured properties once — all asProduct() calls pick them up automatically:

// eva.d.ts (anywhere in your project)
import '@davidoost/eva-nextjs-sdk';

declare module '@davidoost/eva-nextjs-sdk' {
  interface EvaProductExtension {
    colour?: string;
    size_label?: string;
  }
}

Importing request/response types

The generated files in src/eva/generated/ export all request and response types for your environment. Import from the relevant assembly:

import type { SearchProducts, SearchProductsResponse } from '@/eva/generated/eva-services-core';

async function search(req: SearchProducts): Promise<SearchProductsResponse> {
  const client = await createClient();
  return client.SearchProducts(req);
}

Explicit call() API

import { SvcGetOrder } from '@/eva/generated/eva-services-core';

const order = await client.call(SvcGetOrder, { ID: 123 });

Keeping types up to date

Re-run the generate command whenever your EVA environment gets updated:

EVA_BASE_URL=https://api.euw.your-store.eva-online.cloud npx @davidoost/eva-nextjs-sdk generate

The generator binary is cached in node_modules/.cache/eva-sdk-generator/ and reused on subsequent runs. It auto-updates to the latest eva-apispec release.

Types are environment-specific — each customer's src/eva/generated/ will only contain services enabled for their EVA instance. This means you won't see services in autocomplete that aren't available to you.