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

@alphinex/api-laravel

v1.1.0

Published

Laravel Sanctum/Reverb/Horizon adapter implementing the @alphinex/api ApiClient contract.

Readme

@alphinex/api-laravel

Laravel Sanctum/Reverb adapter that implements @alphinex/api's ApiClient contract: CSRF-cookie or Bearer-token auth, Laravel's 422 validation shape mapped into ApiError.fieldErrors, a session-expiry event you can wire to @alphinex/auth, and hooks for real-time updates over Laravel Echo/Reverb.

createLaravelApiClient(options)

The main entry point — wires the right interceptors onto @alphinex/api's createApiClient for you and returns both the resulting client and an events emitter:

import { createLaravelApiClient } from "@alphinex/api-laravel";

export const { client, events } = createLaravelApiClient({
  baseUrl: "https://api.example.com",
});

const invoices = await client.get("/invoices");

mode defaults to "cookie" (Sanctum SPA session auth) and attaches the X-XSRF-TOKEN header from the XSRF-TOKEN cookie automatically. For Bearer-token auth (mobile/3rd-party clients), pass mode: "bearer" with a tokenStore and a refresh function — concurrent 401s share a single in-flight refresh call, and each queued request replays with the new token:

import { createLaravelApiClient, createInMemoryTokenStore } from "@alphinex/api-laravel";

const tokenStore = createInMemoryTokenStore();

const { client, events } = createLaravelApiClient({
  baseUrl: "https://api.example.com",
  mode: "bearer",
  bearer: {
    tokenStore,
    refresh: async (fetchFn) => {
      const response = await fetchFn("https://api.example.com/refresh", { method: "POST" });
      const { token } = await response.json();
      return token;
    },
  },
});

Every request/validation-error/session-expiry interceptor described below is already included; use extraRequestInterceptors/extraResponseInterceptors to append your own on top, and fetchFn to override the global fetch (mocking, tests). If you need fine-grained control instead of the bundled defaults, call the individual pieces below directly against @alphinex/api's createApiClient.

Composing with createApiClient yourself

createLaravelApiClient is really just a preset over @alphinex/api's createApiClient and the pieces below — reach for the pieces directly if you need a different combination:

import { createApiClient } from "@alphinex/api";
import {
  createCsrfInterceptor,
  createValidationErrorInterceptor,
  createSessionEvents,
  createSessionExpiryInterceptor,
} from "@alphinex/api-laravel";

const events = createSessionEvents();

const client = createApiClient({
  baseUrl: "https://api.example.com",
  requestInterceptors: [createCsrfInterceptor()],
  responseInterceptors: [
    createValidationErrorInterceptor(),
    createSessionExpiryInterceptor(events),
  ],
});

CSRF (createCsrfInterceptor, ensureCsrfCookie)

createCsrfInterceptor(cookieName?, headerName?) reads the XSRF-TOKEN cookie (defaults match Sanctum) and attaches it as X-XSRF-TOKEN on outgoing requests — already included by createLaravelApiClient in "cookie" mode. Call ensureCsrfCookie(baseUrl, fetchFn?, path?) once per session, before the first mutating request, to have the browser fetch and store that cookie:

import { ensureCsrfCookie } from "@alphinex/api-laravel";

await ensureCsrfCookie("https://api.example.com"); // GET /sanctum/csrf-cookie

Session events (createSessionEvents, createSessionExpiryInterceptor)

createSessionEvents() returns an EventEmitter<ApiLaravelEvents> emitting a "session-expired" event (with { reason: "cookie-401" | "bearer-refresh-failed" }) whenever the session is no longer valid. Create one instance per client and pass it to both createLaravelApiClient (or createSessionExpiryInterceptor directly) and to @alphinex/auth's AuthProvider so the app signs the user out and redirects automatically:

import { AuthProvider } from "@alphinex/auth";
import { createLaravelApiClient } from "@alphinex/api-laravel";

const { client, events } = createLaravelApiClient({ baseUrl: "https://api.example.com" });

<AuthProvider
  adapter={authAdapter}
  subscribeSessionExpired={(handler) => events.on("session-expired", () => handler())}
>
  <App />
</AuthProvider>;

Validation errors (createValidationErrorInterceptor, mapLaravelValidationErrors)

createValidationErrorInterceptor() intercepts Laravel's 422 responses and throws an ApiError with fieldErrors already mapped from { message, errors: { field: string[] } } into Record<string, string[]>. mapLaravelValidationErrors(body) is the underlying mapping function, exported separately if you need it outside the interceptor pipeline (e.g. mapping a body you already parsed):

import { ApiError } from "@alphinex/api";

try {
  await client.post("/invoices", payload);
} catch (error) {
  if (error instanceof ApiError && error.fieldErrors) {
    console.log(error.fieldErrors.amount?.[0]); // "The amount field is required."
  }
}

Bearer auth (createBearerAuthInterceptor, createBearerRefreshInterceptor, createInMemoryTokenStore)

The building blocks behind mode: "bearer" above, exported for custom wiring. createInMemoryTokenStore(initialToken?) implements the TokenStore contract (getToken/setToken) in memory — swap it for a persisted store (secure storage, etc.) in a real app. createBearerAuthInterceptor(tokenStore) attaches Authorization: Bearer <token> to every request. createBearerRefreshInterceptor(options) retries a 401 once, sharing a single in-flight refresh across concurrent failed requests, and emits "session-expired" (reason "bearer-refresh-failed") if the refresh call itself fails.

Laravel Echo (useEchoChannel, useEchoQuerySync)

useEchoChannel(echo, channelName, event, handler) subscribes to a Reverb channel/event for the lifetime of the component and cleans up on unmount. echo is typed against the structural EchoLike/EchoChannelLike interfaces (this package has no hard dependency on laravel-echo — any real Echo instance satisfies them):

import { useEchoChannel } from "@alphinex/api-laravel";
import Echo from "laravel-echo";

function InvoiceNotifications({ echo }: { echo: Echo }) {
  useEchoChannel(echo, "invoices", "InvoiceCreated", (payload) => {
    console.log("New invoice", payload);
  });
  return null;
}

useEchoQuerySync(echo, channelName, event, queryClient, queryKey) is the common case built on top: invalidate a TanStack Query key whenever the event fires, keeping WebSocket concerns out of component code:

import { useEchoQuerySync } from "@alphinex/api-laravel";
import { queryKeys } from "@alphinex/api";
import { useQueryClient } from "@tanstack/react-query";

function InvoicesLive({ echo }: { echo: Echo }) {
  const queryClient = useQueryClient();
  useEchoQuerySync(
    echo,
    "invoices",
    "InvoiceCreated",
    queryClient,
    queryKeys.resource("invoices").lists()
  );
  return null;
}

See documentation/ARCHITECTURE.md for the full package contract, dependency rules, and roadmap placement.