@boomevents/sdk
v0.2.0
Published
TypeScript SDK for the BOOM Organizer Storefront and Organizer Management APIs — generated from OpenAPI, running on fetch
Downloads
48
Maintainers
Readme
🤘 BOOM SDK
TypeScript client for the BOOM Organizer APIs — the storefront your customers buy through, and the management API behind it.
Generated from the platform's OpenAPI documents, running on plain fetch, so it works the same in Astro, Next.js, Node, Deno, Bun and Cloudflare Workers.
Install
pnpm add @boomevents/sdk
# or: npm install @boomevents/sdk · yarn add @boomevents/sdkRequires Node 18+ (or any runtime with a global fetch). No axios, no Node built-ins, no framework dependency.
60 seconds in
import { createStorefrontClient } from '@boomevents/sdk/storefront'
const boom = createStorefrontClient({
apiKey: process.env.BOOM_API_KEY!,
})
const result = await boom.events.listEventsV1({
currency: 'CZK', // required — see below
lang: 'CS',
pageSize: 20,
})
result.page // the events, each with its products
result.actual // current page index — 0-based
result.pagesYou never call /auth/token yourself: the SDK exchanges your API key for a bearer token on the first request, keeps it, and swaps it for a fresh one if the platform answers 401. The organizer comes from the token too, so no call takes a tenant parameter.
boom.tenantId() // organizer the key belongs to, once a token has been issuedManagement works the same way, against its own token endpoint:
import { createManagementClient } from '@boomevents/sdk/management'
const boom = createManagementClient({
apiKey: process.env.BOOM_API_KEY!,
})
await boom.events.publishEventV1({
eventId,
publishRequest: { published: true },
})Every request goes to https://platform.boomevents.org unless you say otherwise. baseUrl switches gateways, and it takes one of two known hosts rather than a free-form URL — the client sends your API key to whatever it names, so a typo must not be able to hand that key to somebody else:
import { BOOM_DEV_BASE_URL } from '@boomevents/sdk'
const boom = createStorefrontClient({ apiKey, baseUrl: BOOM_DEV_BASE_URL })Reading it from the environment? asBoomBaseUrl narrows a string to a known gateway and returns null for anything else.
Always send currency
The storefront prices everything it returns, so the event endpoints need a currency even though the OpenAPI document lists the parameter among the optional ones. A call without it answers:
400 Bad Request
{"InvalidCurrency": ["Currency is not valid."]}So pass it on every call that takes it — and mind the naming, which differs between list and detail:
await boom.events.listEventsV1({ currency: 'CZK', lang: 'CS' })
await boom.events.getEventByIdV1({ id, currencyCode: 'CZK', langCode: 'CS' })The simplest approach is to bake it into your own data layer once, rather than remembering it at every call site:
const CURRENCY = 'CZK'
const LANG = 'CS'
export const listEvents = (params = {}) =>
boom.events.listEventsV1({ currency: CURRENCY, lang: LANG, ...params })Pagination is 0-based
The first page is pageNumber: 0, and actual reports the 0-based index of the page you got back. URLs are usually nicer 1-based, so convert at the boundary:
const result = await boom.events.listEventsV1({
currency: 'CZK',
pageNumber: uiPage - 1,
pageSize: 20,
})
result.hasNextPage
result.hasPreviousPageThe envelope answers both questions itself, so there is no arithmetic to get wrong.
What's in the box
| Surface | Import | Covers |
| -------------- | ---------------------------- | ------------------------------------------------------------------------------- |
| Storefront | @boomevents/sdk/storefront | events (list, detail with products) · orders (direct order) · auth |
| Management | @boomevents/sdk/management | events (CRUD, publish, visibility) · products (CRUD) · customers · auth |
| Core | @boomevents/sdk | client options, error types, createBoomConfig, BOOM_BASE_URL |
Two OpenAPI documents, 7 API classes, 16 operations — see docs/api-surface.md.
Both clients are flat: one document per surface means boom.events.listEventsV1(), not a service-then-class hierarchy.
Why it feels different from raw generated code
The generated classes are exposed as-is — no domain facade to drift out of sync with the API. What sits around them is the thin layer you'd otherwise write in every project:
- API key handling. Exchanged for a token once, reused, re-exchanged on 401. Concurrent calls share one exchange rather than stampeding the auth endpoint.
- Real errors. Non-2xx throws
BoomApiErrorwith the body already parsed, plusvalidationErrors,statusandisNotFound-style guards. - Retries with backoff. Idempotent methods only, honouring
Retry-After, with full jitter. - Timeouts. Per attempt, composed with any
AbortSignalyou pass in. - Your
fetch. Swap it out to add framework caching, tracing or mocks.
Keep the API key on a server
The key is long-lived and grants full access to the organizer's data — including placing orders. It belongs in an environment variable on a server, never in client-side JavaScript.
The bearer token it produces is short-lived, but it can place orders too, so it should not reach the browser either. Both framework guides show the shape that works: your own endpoint holds the key, the browser gets JSON. See docs/authentication.md.
For a storefront already built this way, see boom-frontend-sdk — a layer over this package with Next.js and Astro reference apps, the SDK confined to a single server-only workspace package, and a verify:no-leak step that scans the built client bundles for the key, the platform hostname and SDK internals.
Documentation
| | |
| -------------------------------------------------- | ---------------------------------------------------- |
| Getting started | Install, first call, client options in full |
| Authentication | API keys, token exchange, where they may live |
| Error handling | BoomApiError, validation errors, retries, timeouts |
| Recipes | Listing, ordering, publishing, testing |
| Next.js / Astro | Framework wiring |
| API surface | Every API class and operation |
| Regenerating | Pulling new endpoints from the platform specs |
| Architecture | How the layers fit together, and why |
Working on the SDK
pnpm install
pnpm run generate # pull both OpenAPI documents -> src/generated
pnpm run barrels # rebuild the hand-facing surface over them
pnpm run typecheck
pnpm run test
pnpm run buildBoth documents come from platform.boomevents.org/custom-openapi.
Tests
pnpm run test # 95 tests, no network
pnpm run test:coverage
pnpm run smoke # hits the platform — needs BOOM_API_KEYThe suite covers src/core, the hand-written layer where bugs would live, at ~98% statement coverage. Everything runs against an injected fetch, so it needs no credentials and no network.
| File | What it pins down |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| api-key.test.ts | Token exchanged once, shared between concurrent callers, key sent as a header only, failed exchange not cached |
| fetcher.test.ts | Retries only idempotent methods, honours Retry-After, never replays a stream body, timeouts, signal composition, a caller's cancellation left intact |
| errors.test.ts | BoomApiError fields and guards, body parsing that never throws |
| middleware.test.ts | Auth header, 401 re-exchange (and when it must not fire), non-2xx becoming BoomApiError |
| config.test.ts | Option defaults, middleware ordering, refusing a client with no way to authenticate |
| client.integration.test.ts | The whole chain against a stubbed fetch, through the real generated clients |
| version.test.ts | SDK_VERSION matches package.json, so the User-Agent never misreports a release |
License
MIT © BOOM Events s.r.o.
