Skip to content

@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/core

Creating 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
OptionTypeRequiredDescription
accountstringYesAccount route identifier, for example acc_example.
projectstringYesProject identifier.
environmentstringYesdevelopment, staging, or production.
apiUrlstringNoSkyState API base URL. Defaults to https://api.skystate.io.
fetchtypeof globalThis.fetchNoCustom fetch implementation.
storageAuthTokenStorageNoCustom 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
ParameterDescription
keyTop-level public-state key.
fallbackValue 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

MethodSignatureDescription
setAuthTokens(tokens: { idToken: string; refreshToken: string }) => voidAttaches hosted-auth tokens to the client.
clearAuthTokens() => voidClears local auth and user state.
logout() => Promise<void>Signs out through SkyState and clears local auth state.
beginAuthenticate() => voidMarks 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

MethodSignatureDescription
get(key: string, fallback?: unknown) => unknownReads the current user-state value for a key.
set(key: string, value: NonNullable<unknown> | null) => voidWrites a user-state value or updater function. undefined is a type error: removal goes through clear.
clear(key: string) => voidRemoves the key for the signed-in user.
subscribe(key: string, listener: () => void) => () => voidSubscribes to changes for a key. Returns an unsubscribe function.
isKeyWriting(key: string) => booleanWhether a write for the key is still awaiting server confirmation.
syncStatus(key: string) => UserStateSyncStatusSync state of the key's stored value: 'unset', 'syncing', or 'synced'.
subscribeWriting(key: string, listener: () => void) => () => voidSubscribes 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');
ParameterDescription
keyTop-level user-state key.
fallbackValue 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');
ParameterDescription
keyTop-level user-state key.
valueJSON-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();
ParameterDescription
keyTop-level user-state key.
listenerCallback 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 memberTypeDescription
get()() => VDrafted value if present, otherwise committed value or fallback.
committed()() => VCommitted value, ignoring local draft edits.
hasDraft()() => booleanWhether a local draft value exists.
set(value)(value: V | ((displayValue: V) => V)) => voidUpdates the local draft only.
save()() => voidSaves the current draft value.
discard()() => voidDrops the local draft value.
subscribe(listener)(listener: () => void) => () => voidSubscribes to draft or committed-value changes.
dispose()() => voidReleases 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).

FieldDescription
httpStatusHTTP 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.
pathFailing user-state key on a keyed write, null on a non-keyed load. Always a string on write rejections and invalid_path.
retryAfterrate_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

ExportDescription
createSkyStateClient(options)Create a framework-agnostic client.
SkyStateClient, SkyStateClientOptionsClient interface and construction options.
ClientSnapshot, ClientLifecycleClient lifecycle and combined snapshot types.
PublicStateSnapshot, AuthSnapshot, UserStateSnapshotPublic, auth, and user-state snapshot types.
PublicStateAccessor, UserStateAccessor, UserStateDraftHandle, UserStateSyncStatusAccessor, draft-handle, and sync-status types.
SkyStateError, SkyStateErrorCodeDiscriminated 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, AsyncMisuseErrorMember types of the SkyStateError union.
isHealthError(), SkyStateHealthError, SkyStateAuthErrorOperational-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() / AuthTokenStorageAuth 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_SNAPSHOTThe initial unauthenticated client snapshot.
assertNever()Exhaustiveness helper for TypeScript discriminated unions.
RefreshOutcomeToken-refresh outcome type used by advanced integrations.