Skip to content

createRestAdapter for your API · Browser + Node

@openseat/adapter-rest

Optional REST adapter connecting OpenSeat to your own inventory, hold and layout endpoints.

An optional adapter that connects OpenSeat to your REST API. OpenSeat runs no backend; this package only calls endpoints you host.

  • 1 function
  • 5 interfaces
  • 2 types
  • 2 constants
  • MIT
npm i @openseat/adapter-rest
first npm release pending

Install commands show the public npm names. The first npm release is pending; the packages are free and MIT licensed.

depends on @openseat/booking, @openseat/core, zod

Import
import {
  createRestAdapter,
  DEFAULT_PATHS,
} from "@openseat/adapter-rest";
import type {
  RestAdapterConfig,
  RestPaths,
  RealtimeConfig,
} from "@openseat/adapter-rest";

Functions1

function

createRestAdapter

Creates the adapter. Holds no state except the configuration you pass in.

function createRestAdapter(config: RestAdapterConfig): RestAdapter;

Parameters

ParameterTypeDescription
configRestAdapterConfig

config takes RestAdapterConfig:

Returns

Example

api.ts
import { createRestAdapter } from "@openseat/adapter-rest";

// Your API follows the reference REST contract…
export const api = createRestAdapter({
  baseUrl: "https://api.example.com",
  credentials: "include", // cookie sessions; or pass getAccessToken for a bearer token
  realtime: { kind: "poll", intervalMs: 5000 },
});

// …or implement BookingAdapter from @openseat/booking yourself:
// getStatuses, holdSeats, confirmHold and releaseHold.

Your backend: the REST contract

@openseat/adapter-rest/src/index.ts

Interfaces5

interface

LayoutSaveRequest

Request of RestAdapter.saveLayout; matches @openseat/editor's SaveRequest.

interface LayoutSaveRequest

Members

MemberTypeDescription
layoutLayoutDocument
baseRevisionnumber
forceoptionalboolean
manifestoptionalInventoryManifest

Sent only with sendManifest: true.

Every member is readonly.

@openseat/adapter-rest/src/index.ts

interface

RestAdapter

What createRestAdapter returns: a complete BookingAdapter (statuses, holds, real-time updates) for the booking hooks, plus getLayout, saveLayout (with If-Match revisions) and createLayout for LayoutEditor.

interface RestAdapter extends BookingAdapter

Extends BookingAdapter

Members

MemberTypeDescription
getLayoutmethod(layoutId: string, options?: RequestOptions) => Promise<LayoutDocument>
saveLayoutmethod(request: LayoutSaveRequest, options?: RequestOptions) => Promise<LayoutSaveResult>

Saves with optimistic concurrency: sends If-Match: "<baseRevision>". A 409 or 412 response with the stored layout resolves as a conflict. Matches the SaveHandler signature of @openseat/editor.

createLayoutmethod(layout: LayoutDocument, options?: CreateLayoutOptions) => Promise<{ readonly revision: number; }>
holdSeatsmethod(input: HoldInput, options?: RequestOptions) => Promise<Hold>

Holds all seats and area quantities, or none. Rejects with SEATS_UNAVAILABLE (listing the seats in unavailableSeatIds) when any seat is taken, AREA_SOLD_OUT (listing unavailableAreaIds) when an area has too few places left, and LIMIT_EXCEEDED when the request is over the per-buyer ticket limit.

From BookingAdapter

confirmHoldmethod(input: { readonly holdId: string; readonly paymentReference: string; readonly idempotencyKey?: string; }, options?: RequestOptions) => Promise<Booking>

Converts a hold into a booking after the host's payment flow succeeds. paymentReference is whatever your payment provider returns; the server must verify it. The SDK never handles card data.

From BookingAdapter

releaseHoldmethod(holdId: string, options?: RequestOptions) => Promise<void>

From BookingAdapter

getHoldoptionalmethod(holdId: string, options?: RequestOptions) => Promise<Hold>

Optional: current hold state, for recovering after a reload.

From BookingAdapter

getStatusesmethod(input: { readonly eventId: string; readonly seatIds?: readonly string[]; }, options?: RequestOptions) => Promise<SeatStatusRecord[]>

From InventoryAdapter

subscribeoptionalmethod(eventId: string, listener: (changes: SeatStatusRecord[]) => void, onError?: (error: BookingError) => void) => () => void

Optional real-time updates. Return an unsubscribe function. Updates are hints that make the map fresher; they do not make it authoritative.

From InventoryAdapter

getAreaStatusesoptionalmethod(input: { readonly eventId: string; readonly inventoryIds?: readonly string[]; }, options?: RequestOptions) => Promise<AreaStatusRecord[]>

Optional: availability of GA areas, booths and whole tables (remaining capacity and status). Adapters without it simply do not report areas.

From InventoryAdapter

subscribeAreasoptionalmethod(eventId: string, listener: (changes: AreaStatusRecord[]) => void, onError?: (error: BookingError) => void) => () => void

Optional real-time area updates (remaining capacity), like subscribe for seats.

From InventoryAdapter

Every member is readonly.

@openseat/adapter-rest/src/index.ts

interface

RestAdapterConfig

Options for createRestAdapter. Only baseUrl is required: the paths of the REST contract are appended to it.

interface RestAdapterConfig

Members

MemberTypeDescription
baseUrlstring

For example https://api.example.com. Paths are appended.

fetchoptionaltypeof fetch

Defaults to the global fetch.

headersoptionalHeadersInit | (() => HeadersInit | Promise<HeadersInit>)

Extra headers, static or computed per request (for example a CSRF token).

getAccessTokenoptional() => string | null | undefined | Promise<string | null | undefined>

Returns a bearer token, sent as Authorization: Bearer <token>. Never stored by the adapter.

onUnauthorizedoptional() => boolean | Promise<boolean>

Called on 401. Return true (for example after refreshing a token) to retry the request once.

credentialsoptionalRequestCredentials

fetch credentials mode, for cookie-based sessions.

timeoutMsoptionalnumber

Per-request timeout. Default 15 seconds.

pathsoptionalPartial<RestPaths>
realtimeoptionalRealtimeConfig

Real-time status updates. Default: poll every 10 seconds.

EventSourceoptionaltypeof EventSource

EventSource constructor, for environments without a global one.

sendManifestoptionalboolean

Send the inventory manifest with saves: when true and a save (or create) has a manifest, the body is {"layout": <layout>, "manifest": <manifest>} instead of the bare layout. Default false, the bare-layout wire format.

Every member is readonly.

@openseat/adapter-rest/src/index.ts

interface

RestPaths

The URL of each endpoint, built from the ID it needs. The defaults (DEFAULT_PATHS) follow the REST contract; pass any of them as paths to createRestAdapter to rename an endpoint.

interface RestPaths

Members

MemberTypeDescription
availabilitymethod(eventId: string) => string
holdsmethod(eventId: string) => string
holdmethod(holdId: string) => string
confirmmethod(holdId: string) => string
layoutmethod(layoutId: string) => string
layoutsmethod() => string

Every member is readonly.

@openseat/adapter-rest/src/index.ts

Types2

type

LayoutSaveResult

What saveLayout resolves with: saved and the new revision, or conflict with the stored layout when a 409 or 412 response carries it.

type LayoutSaveResult =
  | {
      readonly status: "saved";
      readonly revision: number;
      readonly updatedAt?: string;
    }
  | {
      readonly status: "conflict";
      readonly remote: LayoutDocument;
      readonly message?: string;
    };

Variants · 2

DiscriminantTypeDescription
status: "saved"{ readonly status: "saved"; readonly revision: number; readonly updatedAt?: string; }
status: "conflict"{ readonly status: "conflict"; readonly remote: LayoutDocument; readonly message?: string; }

@openseat/adapter-rest/src/index.ts

type

RealtimeConfig

How the adapter keeps statuses fresh: false for no updates, { kind: "poll" } to refetch availability every intervalMs (10 seconds by default), or { kind: "sse", url } for a server-sent events stream.

type RealtimeConfig =
  | false
  | {
      readonly kind: "poll";
      readonly intervalMs?: number;
    }
  | {
      readonly kind: "sse";
      readonly url: (eventId: string) => string;
      readonly withCredentials?: boolean;
    };

Variants · 3

DiscriminantTypeDescription
false
kind: "poll"{ readonly kind: "poll"; readonly intervalMs?: number; }
kind: "sse"{ readonly kind: "sse"; readonly url: (eventId: string) => string; readonly withCredentials?: boolean; }

@openseat/adapter-rest/src/index.ts

Constants2

const

DEFAULT_PATHS

The reference contract's paths: /v1/events/:eventId/availability, /v1/events/:eventId/holds, /v1/holds/:holdId, /v1/holds/:holdId/confirm, /v1/layouts/:layoutId and /v1/layouts, with every ID URL-encoded.

const DEFAULT_PATHS: RestPaths

@openseat/adapter-rest/src/index.ts

const

MAX_SSE_MESSAGE_LENGTH

Server-sent events larger than this (in characters) are reported and ignored.

const MAX_SSE_MESSAGE_LENGTH = 1048576

@openseat/adapter-rest/src/index.ts