@colixsystems/notifications-client
v0.1.0
Published
Scoped client for the AppStudio widget send-notification API. Used by widgets through the injected WidgetContext.notifications.
Readme
@colixsystems/notifications-client
Scoped client for the AppStudio widget send-notification API (sc-890). It is a communication-plane sibling of @colixsystems/payments-client, @colixsystems/datastore-client, @colixsystems/assets-client, and @colixsystems/directory-client. It is a standalone fetch-based client you instantiate with createNotificationsClient({ baseUrl, getToken, getTenantId }).
Two surfaces, one package. This client serves two callers:
- External / server-side integrations instantiate it directly with an API key and call
sendbelow.- The widget runtime — the Player and exported Expo app instantiate the same package and inject it into
WidgetContext.notifications(a flat{ send }namespace). Widgets never import this package; they call the SDK hookuseSendNotification()from@colixsystems/widget-sdk, which readsctx.notifications. Both surfaces speak the identical snake_case REST contract.
Status
v0.1.0 — pre-publish. Not yet published to npm.
Wire format — snake_case, no transform
The wire format is snake_case in both directions (REQ-GEN-09) and that is the client contract. The SDK does no camelCase↔snake_case transform. Request bodies are sent snake_case verbatim ({ recipient_user_id, title, body, link?, payload? }); response objects are returned snake_case verbatim ({ id, tenant_id, user_id, title, body, link, payload, read_at, created_at }). The only caller-facing camelCase is the JS method name (send) and the factory option names.
Public API
import {
createNotificationsClient,
NotificationError,
NotFoundError,
ForbiddenError,
ValidationError,
RateLimitedError,
ServerError,
} from "@colixsystems/notifications-client";
const client = createNotificationsClient({
baseUrl: "https://api.appstudio.io/api/v1",
getToken: () => "Bearer ...",
getTenantId: () => "tenant_abc",
// Optional: per-request extra headers (e.g. a per-widget scope token). Called
// with { namespace: "notifications", operation } and merged into the request.
getRequestHeaders: ({ namespace, operation }) => ({ "X-Widget-Scopes": "..." }),
// fetchImpl defaults to globalThis.fetch
});
// Send an in-app notification to one app user in the tenant. Rejects with a
// NotificationError subclass on a 4xx/5xx (INVALID_TITLE / INVALID_BODY /
// INVALID_RECIPIENT / INVALID_PAYLOAD → ValidationError; RECIPIENT_NOT_FOUND →
// NotFoundError; RATE_LIMITED → RateLimitedError; 401/403 → ForbiddenError).
const notification = await client.send({
recipient_user_id: "u_123",
title: "New comment",
body: "Alex replied to your post.",
link: "/posts/42",
payload: { post_id: "42" },
});
// → { id, tenant_id, user_id, title, body, link, payload, read_at, created_at }Surface
| Method | HTTP | Returns |
| --- | --- | --- |
| send(body) | POST /notifications/send | Notification (snake_case row) |
send body (snake_case verbatim): { recipient_user_id, title, body, link?, payload? }.
Factory options
| Option | Type | Notes |
| --- | --- | --- |
| baseUrl | string | Required. API root, e.g. https://api.appstudio.io/api/v1. |
| getToken | () => string \| Promise<string> | Required. The Authorization header value; "Bearer " prefix added if missing. |
| getTenantId | () => string \| Promise<string> | Required. The x-tenant-id header value. |
| getRequestHeaders | ({ namespace, operation }) => object \| Promise<object> | Optional. Extra headers merged per request. |
| fetchImpl | typeof fetch | Optional. Defaults to globalThis.fetch. |
Transport
- Idempotent GETs retried 3× with exponential backoff (200/400/800 ms); non-idempotent verbs are not retried.
sendis a POST, so it is not auto-retried on a network failure — a retried send could deliver a duplicate notification. - 10 s default per-call timeout via
AbortController. - Typed
NotificationErrorhierarchy (NotFoundError,ForbiddenError,ValidationError,RateLimitedError,ServerError) pluserrorFromResponse.
Dependencies
None. The client uses only platform fetch and AbortController, available in modern browsers, Node 18+, and React Native.
