Skip to content

Latest commit

 

History

History
597 lines (456 loc) · 13.3 KB

File metadata and controls

597 lines (456 loc) · 13.3 KB

Vanilla TypeScript Guide

Framework-agnostic usage of the 23blocks SDK. Works with any JavaScript environment.

Installation

npm install @23blocks/sdk

Quick Start (Recommended)

The simplest way to use the SDK with automatic token management:

import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
  urls: { authentication: 'https://api.yourapp.com' },
  apiKey: 'your-api-key',
});

// Sign in - tokens are stored automatically
const { user } = await client.auth.signIn({
  email: 'user@example.com',
  password: 'password',
});
console.log('Welcome', user.email);

// All requests now include auth automatically
const results = await client.search.search.search({
  query: 'hello world',
  limit: 10,
});

const products = await client.products.products.list({ limit: 20 });

// Sign out - tokens are cleared automatically
await client.auth.signOut();

Client Configuration

Token Mode (Default)

Tokens are stored in localStorage and attached to requests automatically:

const client = create23BlocksClient({
  urls: { authentication: 'https://api.yourapp.com' },
  apiKey: 'your-api-key',
  // authMode: 'token', // default
  // storage: 'localStorage', // 'sessionStorage' | 'memory'
});

Cookie Mode (Recommended for Security)

Backend manages authentication via httpOnly cookies:

const client = create23BlocksClient({
  urls: { authentication: 'https://api.yourapp.com' },
  apiKey: 'your-api-key',
  authMode: 'cookie',
});

// Sign in - backend sets httpOnly cookie
await client.auth.signIn({ email, password });

// Requests automatically include cookies
const products = await client.products.products.list();

SSR / Server-Side Usage

For server-side rendering, use memory storage and pass tokens manually:

const client = create23BlocksClient({
  urls: { authentication: 'https://api.yourapp.com' },
  apiKey: 'your-api-key',
  storage: 'memory',
  headers: {
    Authorization: `Bearer ${tokenFromRequest}`,
  },
});

Multi-Tenant Setup

const client = create23BlocksClient({
  urls: { authentication: 'https://api.yourapp.com' },
  apiKey: 'your-api-key',
  tenantId: 'tenant-123',
});

Token Utilities

// Check if authenticated (token mode only)
const isLoggedIn = client.isAuthenticated();

// Get tokens manually
const accessToken = client.getAccessToken();
const refreshToken = client.getRefreshToken();

// Set tokens manually (useful for SSR hydration)
client.setTokens(accessToken, refreshToken);

// Clear session
client.clearSession();

Advanced Setup (Custom Transport)

For advanced use cases requiring custom transport configuration:

npm install @23blocks/transport-http @23blocks/block-authentication @23blocks/block-search

1. Create the transport

import { createHttpTransport } from '@23blocks/transport-http';

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  headers: () => {
    const token = localStorage.getItem('access_token');
    return token ? { Authorization: `Bearer ${token}` } : {};
  },
});

2. Create blocks

import { createAuthenticationBlock } from '@23blocks/block-authentication';
import { createSearchBlock } from '@23blocks/block-search';

const auth = createAuthenticationBlock(transport, {
  apiKey: 'your-api-key',
});

const search = createSearchBlock(transport, {
  apiKey: 'your-api-key',
});

3. Use the blocks

// Sign in
const { user, accessToken } = await auth.auth.signIn({
  email: 'user@example.com',
  password: 'password',
});

localStorage.setItem('access_token', accessToken);
console.log('Welcome', user.email);

// Search
const results = await search.search.search({
  query: 'hello world',
  limit: 10,
});

console.log(results.data);

Transport Configuration

Basic Configuration

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
});

With Authentication Headers

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  headers: () => ({
    Authorization: `Bearer ${getAccessToken()}`,
    'X-Custom-Header': 'value',
  }),
});

With Retry Configuration

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  retry: {
    maxRetries: 3,
    retryDelay: 1000,
    retryOn: [500, 502, 503, 504],
  },
});

With Timeout

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  timeout: 30000, // 30 seconds
});

Health Check

Every block exposes a health() method to verify service connectivity before making real API calls:

const status = await client.authentication.health();
console.log(status);
// { service: "auth", status: "ok", version: "v4.4.0", timestamp: "2026-02-16T23:19:52Z" }

// Check multiple services in parallel
const results = await Promise.all([
  client.authentication.health(),
  client.search.health(),
  client.products.health(),
]);
results.forEach((r) => console.log(`${r.service}: ${r.status}`));

With standalone blocks:

const auth = createAuthenticationBlock(transport, { apiKey: 'your-api-key' });
const status = await auth.health();

Authentication Examples

Sign In

const auth = createAuthenticationBlock(transport, { apiKey: 'your-api-key' });

try {
  const { user, accessToken, refreshToken } = await auth.auth.signIn({
    email: 'user@example.com',
    password: 'password',
  });

  // Store tokens
  localStorage.setItem('access_token', accessToken);
  localStorage.setItem('refresh_token', refreshToken);

  console.log('Signed in as', user.email);
} catch (error) {
  if (isBlockErrorException(error)) {
    console.error('Sign in failed:', error.message);
  }
}

Sign Up

const { user, accessToken } = await auth.auth.signUp({
  email: 'newuser@example.com',
  password: 'securepassword',
  firstName: 'John',
  lastName: 'Doe',
});

Password Reset

// Request reset
await auth.auth.requestPasswordReset({
  email: 'user@example.com',
});

// Complete reset (with token from email)
await auth.auth.resetPassword({
  token: 'reset-token-from-email',
  password: 'newpassword',
});

Token Refresh

const refreshToken = localStorage.getItem('refresh_token');

const { accessToken, refreshToken: newRefreshToken } = await auth.auth.refreshToken({
  refreshToken,
});

localStorage.setItem('access_token', accessToken);
localStorage.setItem('refresh_token', newRefreshToken);

Social Login with OAuth Mode

Pass oauthMode: true to receive an OAuth 2.0 token pair (access + refresh tokens) from Google or Facebook login:

const { user, accessToken, refreshToken } = await auth.oauth.googleLogin({
  token: googleIdToken,
  oauthMode: true,
});

localStorage.setItem('access_token', accessToken);
localStorage.setItem('refresh_token', refreshToken!);

Get Current User

const user = await auth.users.me();
console.log(user.email, user.firstName, user.lastName);

Service Tokens (Machine-to-Machine Auth)

Create JWT tokens for AI agents and backend services:

const auth = createAuthenticationBlock(transport, { apiKey: 'your-api-key' });

// Create a short-lived token for an AI agent (1-24h)
const agentToken = await auth.serviceTokens.create({
  name: 'support-chatbot',
  tokenCategory: 'agent',
  scopes: ['conversations:read', 'conversations:write'],
  expiresInDays: 1,
});
// agentToken.jwt - store immediately, only returned once

// Create a long-lived token for a backend service (30-365 days)
const svcToken = await auth.serviceTokens.create({
  name: 'billing-sync',
  tokenCategory: 'service',
  scopes: ['wallet:read', 'crm:write'],
  expiresInDays: 90,
});

// List all tokens with pagination
const tokens = await auth.serviceTokens.list({ page: 1, perPage: 50 });

// Get a specific token (JWT is NOT included in get/list responses)
const token = await auth.serviceTokens.get('token-unique-id');
console.log(token.active, token.useCount, token.lastUsedAt);

// Regenerate a compromised token (old JWT is invalidated)
const newToken = await auth.serviceTokens.regenerate('token-unique-id');
// newToken.jwt - new JWT, store immediately

// Revoke a token
await auth.serviceTokens.revoke('token-unique-id');

Search Examples

Basic Search

const search = createSearchBlock(transport, { apiKey: 'your-api-key' });

const results = await search.search.search({
  query: 'product name',
  limit: 20,
  offset: 0,
});

results.data.forEach((item) => {
  console.log(item.id, item.title, item.score);
});

Search with Filters

const results = await search.search.search({
  query: 'laptop',
  filters: {
    category: 'electronics',
    priceMin: 500,
    priceMax: 2000,
  },
  sort: { field: 'price', direction: 'asc' },
  limit: 10,
});

Favorites

// Add favorite
await search.favorites.create({
  favoriteableId: 'product-123',
  favoriteableType: 'Product',
});

// List favorites
const favorites = await search.favorites.list({ limit: 50 });

// Remove favorite
await search.favorites.delete('favorite-id');

Products Examples

import { createProductsBlock } from '@23blocks/block-products';

const products = createProductsBlock(transport, { apiKey: 'your-api-key' });

// List products
const { data, meta } = await products.products.list({
  limit: 20,
  sort: { field: 'createdAt', direction: 'desc' },
});

console.log(`Found ${meta.total} products`);

// Get single product
const product = await products.products.get('product-id');

// List categories
const categories = await products.categories.list();

// Get product variants
const variants = await products.variants.listByProduct('product-id');

Error Handling

import { BlockErrorException, isBlockErrorException, ErrorCodes } from '@23blocks/contracts';

try {
  await auth.auth.signIn({ email, password });
} catch (error) {
  if (isBlockErrorException(error)) {
    // Typed error with code, message, and optional details
    console.error('Error code:', error.code);
    console.error('Message:', error.message);

    switch (error.code) {
      case ErrorCodes.INVALID_CREDENTIALS:
        showError('Invalid email or password');
        break;
      case ErrorCodes.ACCOUNT_LOCKED:
        showError('Account locked. Contact support.');
        break;
      case ErrorCodes.NETWORK_ERROR:
        showError('Network error. Please try again.');
        break;
      default:
        showError(error.message);
    }
  } else {
    // Unknown error
    throw error;
  }
}

Pagination

import type { PageResult, ListParams } from '@23blocks/contracts';

async function fetchAllProducts(): Promise<Product[]> {
  const allProducts: Product[] = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const { data, meta } = await products.products.list({ limit, offset });
    allProducts.push(...data);

    if (allProducts.length >= meta.total) {
      break;
    }

    offset += limit;
  }

  return allProducts;
}

Creating a Service Layer

For larger applications, wrap blocks in a service layer:

// services/api.ts
import { createHttpTransport } from '@23blocks/transport-http';
import { createAuthenticationBlock } from '@23blocks/block-authentication';
import { createSearchBlock } from '@23blocks/block-search';
import { createProductsBlock } from '@23blocks/block-products';

const transport = createHttpTransport({
  baseUrl: import.meta.env.VITE_API_URL,
  headers: () => {
    const token = localStorage.getItem('access_token');
    return token ? { Authorization: `Bearer ${token}` } : {};
  },
});

const config = { apiKey: import.meta.env.VITE_API_KEY };

export const api = {
  auth: createAuthenticationBlock(transport, config),
  search: createSearchBlock(transport, config),
  products: createProductsBlock(transport, config),
};

// Usage
import { api } from './services/api';

await api.auth.auth.signIn({ email, password });
const results = await api.search.search.search({ query: 'test' });

TypeScript Types

All types are exported from each package:

import type {
  User,
  SignInRequest,
  SignInResponse,
  Company,
  ApiKey,
  ServiceToken,
  ServiceTokenWithJwt,
  CreateServiceTokenRequest,
} from '@23blocks/block-authentication';

import type {
  SearchResult,
  Favorite,
} from '@23blocks/block-search';

import type {
  Product,
  Category,
  Variant,
} from '@23blocks/block-products';

import type {
  Transport,
  BlockError,
  PageResult,
  ListParams,
} from '@23blocks/contracts';

Node.js / Server Usage

The SDK works in Node.js environments:

// server.ts
import { createHttpTransport } from '@23blocks/transport-http';
import { createAuthenticationBlock } from '@23blocks/block-authentication';

const transport = createHttpTransport({
  baseUrl: process.env.API_URL!,
  headers: () => ({
    'X-Api-Key': process.env.API_KEY!,
  }),
});

const auth = createAuthenticationBlock(transport, {
  apiKey: process.env.API_KEY!,
});

// Use in your server routes
app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  const result = await auth.auth.signIn({ email, password });
  res.json(result);
});