Skip to main content
Services are the data layer for blocks. Each service abstracts a domain — subscriptions, customers, orders, products, schedules, auth, routing, workflows — behind a stable interface so blocks stay decoupled from any particular backend.

Pattern

Every service follows the same shape:
const service = create<Domain>Service(adapter);
The adapter is the I/O layer. Real adapters call the application’s API; mock adapters return static data for development. The service wraps the adapter, manages reactive state, and exposes a stable interface to blocks. Provide each service once on the <juo-context-root> element:
import {
  provideContext,
  createSubscriptionService,
  createApiSubscriptionAdapter,
  SubscriptionServiceContext,
} from "@juo/blocks";

const root = document.querySelector("juo-context-root") as HTMLElement;

provideContext(
  root,
  SubscriptionServiceContext,
  createSubscriptionService(createApiSubscriptionAdapter({ baseUrl: "/api/v1" })),
);
Blocks then call injectContext(element, SubscriptionServiceContext) to retrieve it. See Contexts for how that flows through the DOM.

Result type

All async service methods return a discriminated union — check _tag before reading data:
const result = await subscriptionService.getById(id);

if (result._tag === "Success") {
  console.log(result.data);
} else {
  console.error(result.error);
}

Reactive state

Where it matters, services expose their state as signals — subscriptionService.current, customerService.current, workflowService.isLoading. Blocks read these signals and re-render automatically. See Reactivity.

Adapters: real vs mock

Every service ships with a mock adapter for development and Storybook:
import {
  createSubscriptionService,
  createMockSubscriptionAdapter,
} from "@juo/blocks";

provideContext(
  root,
  SubscriptionServiceContext,
  createSubscriptionService(createMockSubscriptionAdapter()),
);

Writing a custom adapter

If the backend doesn’t match the default adapter, implement the adapter interface and pass it to the service factory:
import type { SubscriptionAdapter } from "@juo/blocks";
import {
  createSubscriptionService,
  SubscriptionServiceContext,
  provideContext,
} from "@juo/blocks";

const myAdapter: SubscriptionAdapter = {
  async search() { /* ... */ },
  async getById(id) { /* ... */ },
  async pause(id) { /* ... */ },
  // ...
};

provideContext(root, SubscriptionServiceContext, createSubscriptionService(myAdapter));

Available services

Subscription

Read, mutate, pause, resume, cancel subscriptions; manage items, discounts, upsells.

Customer

Current customer profile and reactive customer state.

Order

Order history and details.

Product

Product catalogue lookup and search with variants.

Schedules

Upcoming orders, skip / reschedule.

Login

Passwordless and social login, token management.

Router

In-page navigation, decoupled from a specific router.

Workflow

Interactive flow execution — start, respond, observe.