@skystate/core
@skystate/core is the framework-agnostic SkyState client. It loads public state, stores SkyState auth tokens through a pluggable storage adapter, and reads or writes user state after login.
Basic Example
ts
import { createSkyStateClient } from '@skystate/core';
const client = createSkyStateClient({
account: 'acc_example',
project: 'my-app',
environment: 'production',
});
await client.init();
const banner = client.publicState.get('banner', { enabled: false });
client.setAuthTokens({ idToken, refreshToken });
const theme = client.userState.get('theme', 'dark');
client.userState.set('theme', 'light');Installation
bash
npm install @skystate/coreCreating A Client
ts
import { createSkyStateClient } from '@skystate/core';
const client = createSkyStateClient({
apiUrl: 'https://api.skystate.io',
account: 'acc_example',
project: 'my-app',
environment: 'production',
});
await client.init();account, project, and environment are required. apiUrl defaults to https://api.skystate.io.
createSkyStateClient(options)
ts
function createSkyStateClient(options: SkyStateClientOptions): SkyStateClient| Option | Type | Required | Description |
|---|---|---|---|
account | string | Yes | Account route identifier, for example acc_example. |
project | string | Yes | Project identifier. |
environment | string | Yes | development, staging, or production. |
apiUrl | string | No | SkyState API base URL. Defaults to https://api.skystate.io. |
fetch | typeof globalThis.fetch | No | Custom fetch implementation. |
storage | AuthTokenStorage | No | Custom token storage adapter. |
Every request the client sends (public-state loads, user-state reads and writes, and auth token refresh) runs under a hard 10-second deadline. A request that stalls past it is aborted and classified as a retryable no_response network failure. The deadline is fixed; there is no option to override it.
Public State
Public state is read during init() and is available without end-user auth. Use it for application config, feature flags, settings, and catalog or inventory values.
ts
const banner = client.publicState.get('banner', { enabled: false });Keys are top-level state keys. They are not nested paths.
client.publicState.get(key, fallback?)
ts
get(key: string, fallback?: unknown): unknown| Parameter | Description |
|---|---|
key | Top-level public-state key. |
fallback | Value returned when public state is not ready or the key is absent. A stored null is returned as-is, never replaced by the fallback. |
Returns the current value for key. When public state is not ready or the key is absent, returns fallback, or null when no fallback is given.
Auth
The core client does not open browser windows. Pass tokens from your hosted auth flow into the client:
ts
client.setAuthTokens({
idToken,
refreshToken,
});clearAuthTokens() clears local auth and user state without contacting SkyState servers. logout() signs the user out through SkyState and also clears local auth and user state.
Auth methods
| Method | Signature | Description |
|---|---|---|
setAuthTokens | (tokens: { idToken: string; refreshToken: string }) => void | Attaches hosted-auth tokens to the client. |
clearAuthTokens | () => void | Clears local auth and user state. |
logout | () => Promise<void> | Signs out through SkyState and clears local auth state. |
beginAuthenticate | () => void | Marks the client as authenticating while a hosted login flow is in progress. |
User State
User state requires an authenticated end-user bearer scoped to the same account, project, and environment.
ts
const theme = client.userState.get('theme', 'dark');
client.userState.set('theme', 'light');
const unsubscribe = client.userState.subscribe('theme', () => {
console.log(client.userState.get('theme'));
});Writes update local subscribers immediately and are persisted by the SDK. Temporary network, sign-in renewal, concurrent update, validation, and permission issues are reported through the user-state status surface.
Keys are top-level state keys such as theme, profile, or preferences. Keys must be non-empty and cannot contain / or ~.
API
| Method | Signature | Description |
|---|---|---|
get | (key: string, fallback?: unknown) => unknown | Reads the current user-state value for a key. |
set | (key: string, value: NonNullable<unknown> | null) => void | Writes a user-state value or updater function. undefined is a type error: removal goes through clear. |
clear | (key: string) => void | Removes the key for the signed-in user. |
subscribe | (key: string, listener: () => void) => () => void | Subscribes to changes for a key. Returns an unsubscribe function. |
isKeyWriting | (key: string) => boolean | Whether a write for the key is still awaiting server confirmation. |
syncStatus | (key: string) => UserStateSyncStatus | Sync state of the key's stored value: 'unset', 'syncing', or 'synced'. |
subscribeWriting | (key: string, listener: () => void) => () => void | Subscribes to changes in the key's writing status. Returns an unsubscribe function. |
draft | <T>(key: string, fallback?: T) => UserStateDraftHandle<T | null> | Creates a draft handle for edit-then-save UI. Both overloads return UserStateDraftHandle<T | null>. |
client.userState.get(key, fallback?)
ts
const theme = client.userState.get('theme', 'dark');| Parameter | Description |
|---|---|
key | Top-level user-state key. |
fallback | Value returned when user state is not ready or the key is absent. A stored null is returned as-is, never replaced by the fallback. |
Returns the current value for key. When user state is not ready or the key is absent, returns fallback, or null when no fallback is given.
client.userState.set(key, value)
ts
client.userState.set('theme', 'light');| Parameter | Description |
|---|---|
key | Top-level user-state key. |
value | JSON-serializable value to save for the signed-in user. undefined is a type error: removing a key goes through clear(key). null is a deliberate stored-null write and stays allowed. |
Returns void. The local value updates immediately; save errors are reported through user-state status and client subscribers. A value-form write whose value equals the current server-confirmed value is dropped before sending, so no new version is created and no usage is counted for it; an updater-function write always sends and lets the server decide whether it is a no-op.
client.userState.subscribe(key, listener)
ts
const unsubscribe = client.userState.subscribe('theme', () => {
console.log(client.userState.get('theme', 'dark'));
});
unsubscribe();| Parameter | Description |
|---|---|
key | Top-level user-state key. |
listener | Callback invoked when the key's visible value changes. |
Returns a function that unsubscribes the listener.
client.userState.draft(key, fallback?)
Creates a local draft handle for staged edits:
ts
const profile = client.userState.draft('profile', { displayName: '' });
profile.set((current) => ({ ...current, displayName: 'Ada' }));
profile.save();Use profile.save() to save the drafted value, or profile.discard() to drop local form edits.
Both overloads return UserStateDraftHandle<T | null>: even with a fallback, a stored null surfaces as null. The rows below write the handle's value type as V = T | null.
| Draft member | Type | Description |
|---|---|---|
get() | () => V | Drafted value if present, otherwise committed value or fallback. |
committed() | () => V | Committed value, ignoring local draft edits. |
hasDraft() | () => boolean | Whether a local draft value exists. |
set(value) | (value: V | ((displayValue: V) => V)) => void | Updates the local draft only. |
save() | () => void | Saves the current draft value. |
discard() | () => void | Drops the local draft value. |
subscribe(listener) | (listener: () => void) => () => void | Subscribes to draft or committed-value changes. |
dispose() | () => void | Releases handle-local subscriptions. |
Cross-Tab Sync
When a write commits in one tab, peer tabs on the same account, project, and environment refetch user state to pick up the change. Sync runs over a BroadcastChannel shared by same-origin tabs and is active only once the tab is authenticated. When BroadcastChannel is unavailable (server-side rendering, older browsers, sandboxed frames), the client degrades to single-tab behavior.
Snapshot And Lifecycle
Use client.getSnapshot() and client.subscribe() when building an integration layer:
ts
const unsubscribe = client.subscribe(() => {
const snapshot = client.getSnapshot();
console.log(snapshot.publicState.status, snapshot.auth.status, snapshot.userState.status);
});The snapshot contains lifecycle, publicState, auth, and userState subtrees.
When auth.status is 'authenticated', the snapshot also carries the signed-in user's name and email (each string | null). A userState snapshot in the 'error' state always includes data alongside error: the values visible when the error occurred, typed Readonly<Record<string, unknown>> | null.
Errors
SkyStateError is a discriminated union: narrow on error.code before reading variant fields. Codes cover authentication, server responses (quota, configuration, server_unavailable, protocol), rate_limited, write rejections (the patch_* codes), no_response (no HTTP response came back, including timeouts), and client misuse (missing_config, missing_provider, disposed, invalid_path).
| Field | Description |
|---|---|
httpStatus | HTTP status of the failing response. Always a number on response, rate-limit, and write-rejection errors; number | null on authentication, which can fail without an HTTP response. |
path | Failing user-state key on a keyed write, null on a non-keyed load. Always a string on write rejections and invalid_path. |
retryAfter | rate_limited only: the server's Retry-After delay in seconds, or null when absent. |
Value-Only Metadata Contract
SDK v1 intentionally returns application values and subsystem status only. Use the console, CLI, or REST API when you need inspection, audit, or history metadata.
Exports
| Export | Description |
|---|---|
createSkyStateClient(options) | Create a framework-agnostic client. |
SkyStateClient, SkyStateClientOptions | Client interface and construction options. |
ClientSnapshot, ClientLifecycle | Client lifecycle and combined snapshot types. |
PublicStateSnapshot, AuthSnapshot, UserStateSnapshot | Public, auth, and user-state snapshot types. |
PublicStateAccessor, UserStateAccessor, UserStateDraftHandle, UserStateSyncStatus | Accessor, draft-handle, and sync-status types. |
SkyStateError, SkyStateErrorCode | Discriminated error union (see Errors) and its code set. For the full error-code reference and HTTP-status mapping, see the errors reference. The canonical code-to-HTTP-status mapping (Section 8.5) lives in the repository specification document (docs/3_specification.md). |
AuthenticationError, ResponseError, RateLimitError, WriteError, TransportError, UsageError, AsyncMisuseError | Member types of the SkyStateError union. |
isHealthError(), SkyStateHealthError, SkyStateAuthError | Operational-health and auth facets of the error union. |
applyFallback(raw, fallback) | Fallback helper used by the state accessors: returns fallback when raw is undefined (absent key), or null when no fallback is given. Any other value, including a stored null, is returned as-is. |
decodeJwtPayload() | Decode JWT payloads for inspection. |
createMemoryStorage() / createLocalStorage() / AuthTokenStorage | Auth token storage adapters and interface. |
buildStorageKey() | Build the SDK auth storage key for an account/project/environment. |
toJsonPointer() / resolvePointer() | Lower-level JSON Pointer helpers. Normal state accessors take top-level keys. |
INITIAL_SNAPSHOT | The initial unauthenticated client snapshot. |
assertNever() | Exhaustiveness helper for TypeScript discriminated unions. |
RefreshOutcome | Token-refresh outcome type used by advanced integrations. |