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 ​

See Installation - SDKs.


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. fetch and storage can be supplied for tests, non-browser runtimes, or custom token persistence.

createSkyStateClient(options) ​

ts
function createSkyStateClient(options: SkyStateClientOptions): SkyStateClient
OptionTypeRequiredDescription
accountstringYesAccount route identifier, for example acc_example.
projectstringYesProject identifier.
environmentstringYesEnvironment slug (full name only, no aliases). See Environments.
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. See what public state is for.

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.
fallbackReturned when public state is not ready or the key is absent; see Fallback semantics.

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. A write the server accepts is persisted; a write the server rejects is rolled back and reported through the user-state status surface, and temporary network, sign-in renewal, and quota issues are retried in the background. See Error Handling for the per-status contract.

Keys are top-level state keys such as theme, profile, or preferences. Keys must be non-empty and cannot contain / or ~; these rules apply to every keyed accessor and hook.

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.
fallbackReturned when user state is not ready or the key is absent; see Fallback semantics.

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>; see Fallback semantics for why a fallback does not narrow the type. 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.

Fallback Semantics ​

Every keyed read takes an optional fallback: client.publicState.get(key, fallback?), client.userState.get(key, fallback?), the get() and committed() methods of the handle returned by client.userState.draft(key, fallback?), and the React hooks built on them (usePublicState, useUserState). The rule is the same everywhere:

  • When the state is not ready or the key is absent, the read returns fallback, or null when no fallback is given.
  • A stored null is a value, not an absence. It is returned as-is and is never replaced by the fallback.
  • Because of that, a fallback never narrows the type: keyed reads, hooks, and draft handles are typed T | null even when a fallback is passed.

The exported applyFallback(raw, fallback) helper implements this rule: it returns fallback when raw is undefined, null when raw is undefined and no fallback is given, and any other value, including null, unchanged.


Cross-Tab Sync ​

When a write commits in one tab, other same-origin tabs on the same account, project, and environment refetch user state to pick up the change. Sync is active only once the tab is authenticated. Where the runtime offers no cross-tab messaging (server-side rendering, older browsers, sandboxed frames), the client behaves as a single tab.


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.


Failure Handling ​

Transient failures (network, 5xx, rate limits) retry with capped backoff and quota failures on a slower pace; content rejections (patch_* validation, 415) are dropped and rolled back; a version conflict (412) is resolved by transparent refetch-and-replay; an authentication rejection resets the session unless the rejected credential was already superseded. See Error Handling for the full per-status contract. Auth failures surface on the auth snapshot, operational failures share one set of health codes (isHealthError(error) narrows to them), and write rejections surface on the user-state snapshot with the failing key in error.path.


Errors ​

SkyStateError is a discriminated union: narrow on error.code before reading variant fields. The full code list, HTTP-status mapping, and per-variant fields live in Error Handling.


Value-Only Metadata Contract ​

The SDK 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 and its code set; see Error Handling.
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; implements Fallback semantics.
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.