@open-kingdom/shared-frontend-data-access-api-client

v0.0.2-16

Published

RTK Query base API instance with JWT auth header injection, auth state management (Redux slice), token persistence via a `StorageProvider`, and an adapter pattern for custom auth header formatting.

Readme

@open-kingdom/shared-frontend-data-access-api-client

RTK Query base API instance with JWT auth header injection, auth state management (Redux slice), token persistence via a StorageProvider, and an adapter pattern for custom auth header formatting.


Exports

Base API

| Export | Type | Description | | --------------- | ------------ | ---------------------------------------------------- | | baseApi | Api<...> | RTK Query createApi instance, reducerPath: 'api' | | ApiKey | 'api' | The reducer path string constant | | apiReducer | Reducer | baseApi.reducer — add to store under ApiKey | | apiMiddleware | Middleware | baseApi.middleware — add to store middleware chain |

Auth Slice

| Export | Type | Description | | ----------------------- | ------------------------------------------------ | ----------------------------------------------------------------------- | | authSlice | Slice<AuthState> | Redux slice, name: 'auth' | | AuthKey | 'auth' | The slice name string constant | | AuthState | interface | { token: string \| null } | | AuthPersistence | interface | { getToken(): string \| null; setToken(token: string \| null): void } | | authReducer | Reducer<AuthState> | authSlice.reducer | | setToken | ActionCreator<string \| null> | Sets the JWT token in Redux state | | logout | ActionCreator<void> | Clears token and resets RTK Query API state | | selectToken | (state: { auth: AuthState }) => string \| null | Selector for current token | | selectIsAuthenticated | (state: { auth: AuthState }) => boolean | Selector for auth status |

Auth Listener Middleware

| Export | Type | Description | | ----------------------------------- | ------------------ | --------------------------------------------------------------------------------------- | | configureAuth(config: AuthConfig) | function | Configures global persistence and logout callback — call before store creation | | createAuthListenerMiddleware() | () => Middleware | RTK listener that persists token on setToken/logout and handles logout side effects | | createAuthHydrationMiddleware() | () => Middleware | Middleware that hydrates token from persistence on the first dispatched action | | AuthConfig | interface | { persistence?: AuthPersistence; onLogout?: () => void } |

Auth Adapter

| Export | Type | Description | | ----------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------- | | AuthAdapter | interface | { prepareHeaders(headers: Headers, token: string): void } | | setAuthAdapter(adapter) | (adapter: AuthAdapter \| null) => void | Registers a module-level custom adapter; replaces default Authorization: Bearer injection | | getAuthAdapter() | () => AuthAdapter \| null | Returns the currently registered adapter | | createCustomHeaderAdapter(config) | (config: CustomHeaderAdapterConfig) => AuthAdapter | Factory for adapters that set a named header | | CustomHeaderAdapterConfig | interface | { headerName: string; formatValue?: (token: string) => string } |

Token Persistence

| Export | Type | Description | | ----------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | storagePersistence(storage, key?) | (storage: StorageLike, key?: string) => AuthPersistence | Creates AuthPersistence backed by any localStorage-compatible storage; key defaults to 'token' |


Type Definitions

AuthState

| Property | Type | Required | Default | Description | | -------- | ---------------- | -------- | ------- | ----------------------------------------------------- | | token | string \| null | Yes | — | The current JWT token, or null when unauthenticated |

AuthPersistence

| Method | Parameters | Returns | Description | | ---------- | ----------------------- | ---------------- | ----------------------------- | | getToken | — | string \| null | Retrieves the persisted token | | setToken | token: string \| null | void | Persists or clears the token |

AuthConfig

| Property | Type | Required | Default | Description | | ------------- | ----------------- | -------- | ------- | ------------------------------------------------- | | persistence | AuthPersistence | No | — | Storage backend for token persistence | | onLogout | () => void | No | — | Callback fired when logout action is dispatched |

AuthAdapter

| Method | Parameters | Returns | Description | | ---------------- | --------------------------------- | ------- | ---------------------------------------------------------- | | prepareHeaders | headers: Headers, token: string | void | Injects auth credentials into the outgoing request headers |

CustomHeaderAdapterConfig

| Property | Type | Required | Default | Description | | ------------- | --------------------------- | -------- | ----------------- | ------------------------------------------------ | | headerName | string | Yes | — | The HTTP header name to set (e.g. 'X-Api-Key') | | formatValue | (token: string) => string | No | Identity function | Transforms the raw token into the header value |

StorageLike

Any localStorage-compatible object that implements getItem(key), setItem(key, value), and removeItem(key).


Base API Behavior

  • baseUrl reads process.env.VITE_API_BASE_URL (empty string '' if unset)
  • On every outgoing request, reads state['auth'].token from Redux state
  • If a token is present:
    • If a custom AuthAdapter is registered via setAuthAdapter(), calls adapter.prepareHeaders(headers, token)
    • Otherwise, sets Authorization: Bearer <token> by default
  • endpoints is empty (endpoints: () => ({})) — extended via baseApi.injectEndpoints() in generated code
  • Generated endpoint hooks are auto-injected at import time via import './lib/demo-scaffold-backend/api' in src/index.ts

Setup

Store Registration

import { configureStore } from '@reduxjs/toolkit';
import { ApiKey, apiReducer, apiMiddleware, AuthKey, authReducer, configureAuth, createAuthListenerMiddleware, createAuthHydrationMiddleware, storagePersistence } from '@open-kingdom/shared-frontend-data-access-api-client';

// Configure persistence BEFORE creating the store
configureAuth({
  persistence: storagePersistence(localStorage, 'auth_token'),
  onLogout: () => {
    window.location.href = '/login';
  },
});

export const store = configureStore({
  reducer: {
    [ApiKey]: apiReducer, // reducerPath: 'api'
    [AuthKey]: authReducer, // name: 'auth'
  },
  middleware: (getDefault) => getDefault().concat(apiMiddleware).concat(createAuthListenerMiddleware()).concat(createAuthHydrationMiddleware()),
});

Usage Examples

Dispatch login / set token

import { setToken } from '@open-kingdom/shared-frontend-data-access-api-client';
import { useDispatch } from 'react-redux';

function LoginHandler() {
  const dispatch = useDispatch();

  async function handleLogin(email: string, password: string) {
    const response = await authApi.login({ email, password });
    dispatch(setToken(response.access_token));
  }
}

Dispatch logout

import { logout } from '@open-kingdom/shared-frontend-data-access-api-client';

// Clears token from Redux, resets all RTK Query cache, fires onLogout callback
dispatch(logout());

Read auth state in components

import { selectToken, selectIsAuthenticated } from '@open-kingdom/shared-frontend-data-access-api-client';
import { useSelector } from 'react-redux';

function AuthStatus() {
  const isAuthenticated = useSelector(selectIsAuthenticated);
  const token = useSelector(selectToken);
  return <span>{isAuthenticated ? 'Logged in' : 'Logged out'}</span>;
}

Custom auth header adapter

import { createCustomHeaderAdapter, setAuthAdapter } from '@open-kingdom/shared-frontend-data-access-api-client';

// Set a custom header instead of the default Authorization: Bearer
setAuthAdapter(
  createCustomHeaderAdapter({
    headerName: 'X-Api-Key',
    formatValue: (token) => token, // raw value, no "Bearer" prefix
  })
);

Custom AuthAdapter implementation

import type { AuthAdapter } from '@open-kingdom/shared-frontend-data-access-api-client';
import { setAuthAdapter } from '@open-kingdom/shared-frontend-data-access-api-client';

const myAdapter: AuthAdapter = {
  prepareHeaders(headers, token) {
    headers.set('Authorization', `Token ${token}`);
    headers.set('X-Client-Version', '2.0.0');
  },
};

setAuthAdapter(myAdapter);

Injecting endpoints (generated pattern)

import { baseApi } from '@open-kingdom/shared-frontend-data-access-api-client';

// This pattern is used by the OpenAPI code generator plugin:
const extendedApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    getUsers: builder.query<User[], void>({
      query: () => '/users',
    }),
    createUser: builder.mutation<User, CreateUserDto>({
      query: (body) => ({ url: '/users', method: 'POST', body }),
    }),
  }),
});

export const { useGetUsersQuery, useCreateUserMutation } = extendedApi;

Testing

nx test shared-frontend-data-access-api-client