tailbits-js
v0.0.4
Published
A lightweight, framework-agnostic SDK for Tailbits experiences. Components are implemented as Web Components using Lit, with real-time updates over a resilient reconnecting WebSocket (via `reconnecting-websocket`) and background data fetching powered by T
Readme
tailbits-js
A lightweight, framework-agnostic SDK for Tailbits experiences. Components are implemented as Web Components using Lit, with real-time updates over a resilient reconnecting WebSocket (via reconnecting-websocket) and background data fetching powered by TanStack Query. React wrappers are generated for convenient use in React and a CDN build is available for drop‑in usage.
Features
- Web Components for framework agnosticism
- Background refetch + interval polling via
@tanstack/query-core - Real-time invalidation via
reconnecting-websocket - Typed API client generated from OpenAPI via
orval, usingkyas fetcher - React wrappers generated from the Custom Elements Manifest
- CDN and ESM builds
Getting Started (CDN)
Use the CDN as the primary integration path. Import the loader, and call init(...) once.
<script type="module">
import { init } from 'https://unpkg.com/tailbits-js';
init({
projectId: '{{ your-tailbits-project-id }}',
environmentId: '{{ your-env-id }}',
});
</script>Notes
- You can pin the CDN to a version:
https://unpkg.com/[email protected]. - Use
preload: ['tb-journey']ininit(...)if you want to eagerly load known tags. The default behavior is to load features lazily, as they're detected on the page.
Config Options
Call init(options) once per page load. Subsequent calls throw.
| Option | Type | Default | Required | Description |
|-----------------|------------------------------|--------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------|
| projectId | string | — | Yes | Your Tailbits project ID. |
| environmentId | string | — | Yes | The environment identifier for the page. |
| preload | string[] | [] | No | Feature tags to eagerly load (e.g., ['tb-journey']). Otherwise features load lazily when first seen on the page. |
| onError | (error: unknown) => void | undefined | No | Global error handler for SDK-level errors. Components also emit tb-error events. |
Error Handling
Handle errors globally via onError, and at the component level via the tb-error event. In most cases, it's enough to use one of these hooks.
<tb-journey journey-id="..." step="install_cli"></tb-journey>
<script type="module">
import { init } from 'https://unpkg.com/tailbits-js';
init({
projectId: '...',
environmentId: '...',
onError(error) {
// Global SDK errors (e.g., network/socket)
console.error('[tailbits onError]', error);
},
});
const el = document.querySelector('tb-journey');
el?.addEventListener('tb-error', (e) => {
// Component-level errors include a message and original error
console.error('[tb-error]', e.detail);
});
</script>Components
tb-journey
Render tb-journey anywhere on your page, and the loader will dynamically fetch the required assets to wire it up.
<tb-journey journey-id="{{ your-journey-id }}" step="{{ your-journey-step }}">
<h3>Install the CLI</h3>
<!-- Optional slotted content -->
</tb-journey>The above is all it takes to wire up a Tailbits Journey. Add more elements with different steps to wire up the complete journey. Note that every tb-journey element must have a journey-id and step assigned.
Events
It’s possible to attach event listeners to tb-journey elements using the native addEventListener API.
const el = document.querySelector('tb-journey');
el.addEventListener('tb-step-update', (event) => {
console.log('step status:', event.detail.status);
});
el.addEventListener('tb-error', (event) => {
console.error('component error:', event.detail);
})React (npm)
If using the CDN route is not an option for you, install the package and the peer dependencies, initialize via the loader export, and render the generated React wrappers.
npm i tailbits-js react react-domimport { init } from 'tailbits-js/loader';
import Journey from 'tailbits-js/react/tb-journey';
init({
projectId: '...',
environmentId: '...',
});
export function App() {
return (
<Journey
journeyId="..."
step="install_cli"
onStepUpdate={(e) => console.log('step status:', e.detail.status)}
>
<h3>Install the CLI</h3>
</Journey>
);
}Architecture
Components (Lit)
- Web Components live in
src/features/*and extend a small base insrc/internal/core/base-element.ts. - Elements emit typed custom events (e.g.,
tb-step-update,tb-error) declared insrc/events/*.
Loader + Registry
- Entry point
src/loader.tssets global config and registers a lazy element loader usingimport.meta.glob. - The registry in
src/internal/core/registry.tsobserves the DOM and dynamically imports elements on first sighting;preloadlets you eagerly load known tags.
Data Layer (TanStack Query Core)
- Central client in
src/internal/api/query.tsconfigures sensible defaults:staleTime: 60_000,gcTime: 5 * 60_000,retry: 3refetchOnWindowFocus: truefor background freshness- Per-query
refetchInterval(e.g., 30s for journey progress)
- Components call
this.query(...)to subscribe to query results and lifecycle; errors are surfaced via atb-errorevent.
Real-time (WebSocket)
src/internal/core/socket.tsusesreconnecting-websocketto create a resilient connection.- Incoming messages trigger targeted invalidations (e.g.,
step_updateinvalidates['journey', id]), seamlessly syncing server pushes with cached data.
API SDK (HTTP)
orval.config.tsgeneratessrc/internal/api/client.tsfrom the Tailbits OpenAPI spec.- HTTP is performed by
kyvia a small wrapper insrc/internal/api/fetcher.ts(with centralized error forwarding viasrc/internal/core/errors.ts).
React Wrappers
- Thin React adapters for each element are generated with
@lit/reactand live undersrc/react/*anddist/react/*. - Codegen is driven by the Custom Elements Manifest using a Vite plugin:
scripts/vite-plugin-cem-react.ts. - Event props map to element events (e.g.,
onStepUpdate→tb-step-update).
Agnostic by Design
- The primary deliverable is a set of standard Web Components (Lit) that run without any framework.
- React wrappers are optional and add zero business logic—just ergonomic bindings.
Background Fetching & Intervals
- Queries are configured for background freshness (
refetchOnWindowFocus: true). - Interval polling is used where appropriate (e.g.,
getJourneyQueryOptionssetsrefetchInterval: 30_000). - WebSocket messages act as server‑driven invalidation signals for immediate consistency.
Builds
Library (ESM)
npm run build:libproduces ESM modules indistwith type declarations.- Exports map:
tailbits-js→dist/cdn/loader.js(CDN/loader)tailbits-js/loader→dist/loader.jstailbits-js/react/*→dist/react/*.jstailbits-js/features/*→dist/features/*.jstailbits-js/events/*→dist/events/*.js
CDN
npm run build:cdncreates a CDN‑friendly bundle indist/cdnfor<script type="module">usage.
React Codegen
- React wrappers are generated during Vite builds by
scripts/vite-plugin-cem-react.tsand written tosrc/react/*(sources) anddist/react/*(output).
Events
tb-step-update–{ journeyId, stepId, status: 'incomplete' | 'complete' | 'pending' }tb-error–{ message, error }
React wrappers expose event props mirroring these (e.g., onStepUpdate). Types are exported from tailbits-js/events/* and tailbits-js/react/*.
Demos
- Vanilla demo:
yarn dev:vanilla, sources exist indemo/vanilla/index.html - React demo:
yarn dev:react, sources exist indemo/react/main.tsx - Mock server for demos is powered by
msw(seemocks/*).
