The session lifecycle and request pipeline shared by the SDK API clients: persisted credentials, the login-backoff gate, the logOut-epoch protocol, single-flight session refresh, the resilience pipeline around every request, and the sync-cycle template that keeps the registry and the auto-sync timer honest.
Thin fetch-based HTTP client used internally by the consuming SDKs.
Thrown by HttpClient whenever an upstream response has a non-2xx
status. The shape mirrors what downstream code needs: response.status,
response.headers, and response.data.
Construction options for HttpClient.
Snapshot of the request that triggered an HttpError. Any
value naming a secret — header, body field or query parameter —
reads as ******.
Configuration accepted by HttpClient.request: body (data),
query (params), per-request headers, method, abort signal and URL.
Minimal response shape surfaced to callers.
Type guard for HTTP errors thrown by the internal HTTP client.
Reads a fetch Headers object into a plain record.
Callback bundle invoked around SDK lifecycle moments. All callbacks are optional and non-throwing — the SDK ignores any exceptions they raise so a buggy observer cannot break the request flow.
Logger interface for API call tracing.
Emitted when a request (possibly after retries) completes successfully.
Emitted when a request ultimately fails after exhausting its retries.
Identifies a single logical request across its lifecycle events.
Generated client-side via crypto.randomUUID() when each request
starts, so consumers can correlate a onRequestStart with its
eventual onRequestComplete or onRequestError — including across
retry attempts, which share the same correlationId.
Emitted each time a retry attempt is scheduled.
User-facing configuration every session-bearing SDK client accepts. A consuming SDK extends it with its own surface (credentials, locale, timezone, transport options, protocol switches).
Subclass-internal options injected into the SessionAPI constructor. Distinct from SessionAPIConfig (the user-facing surface) — these capture what the subclass knows that the user doesn't pick: the built transport, the sync cadence default, the sync runner closure, and the protocol's rate-limit / auth-failure vocabulary.
External storage adapter for persisting API session settings.
Emitted at the start of a request, before any retry attempts.
Callback invoked after sync operations, with consumer-defined scoping parameters (device ids, type filters…).
Base class for all errors thrown by the SDK family. Each consuming SDK's protocol errors extend this one class, so a host can catch the whole hierarchy through isAPIError whatever wire it talks to.
The server rejected the credentials, the login response could not be parsed, or the reactive re-authentication that followed an auth-failure status failed in turn.
The upstream is temporarily refusing SIGN-INS — a login throttle, not a rejected password. Retrying a login keeps the lockout alive, so the automatic re-login backoff widens when this error arms it; sessions established BEFORE the throttle keep working.
Upstream returned HTTP 429 (Too Many Requests), or the local rate-limit gate is still holding a pause window from a previous 429.
The sign-in round-trip was ACCEPTED but the enforced post-auth
registry sync failed: the session is established and the credentials
persisted, yet the registry could not be verified against the
server. Thrown by authenticate() with the sync's own failure (a
validation rejection, a transport error, any registry error)
preserved as cause, so consumers can tell "signed in, stale list"
from a refused credential BY TYPE instead of re-deriving the verdict
from isAuthenticated() — a discriminator with a real false
positive: a transport failure during a sign-in over a PRE-EXISTING
live session (a user switching accounts) reads "signed in" while the
new pair was never accepted. A refused credential is never wrapped
in this type: it stays AuthenticationError.
Thrown when a runtime validator rejects an upstream payload. The
consuming SDKs construct it at their zod boundaries (parseOrThrow,
which stays in each SDK); the class itself names no validator — the
validator's own error, typically a ZodError, rides the standard
cause chain as unknown. The context field surfaces which
boundary the payload came from (an endpoint or flow label such as
'login' or 'BFF /context') so consumer dashboards can group drift
alerts without parsing the message string.
A failure and its causes on one line, outermost first —
User-defined type guard for APIError and its subclasses.
Whether a failure is one this package has a class for — an
HttpError, one of the APIError family, or a transport
failure (undici's fetch failed, the client's TimeoutError, a
caller's AbortError) — anywhere in its cause chain. Such a failure
is an expected condition of the wire, and its stack names only the
transport and this package; anything else is a programming error,
whose stack is the one thing that locates it. A wrapper is judged by
what it wraps: a RegistrySyncError carries whatever the
enforced sync threw, and an HttpError with a cause carries its
reader's own failure, so neither counts on its own.
Write a failure to the log the way a diagnostic report can read it: a known failure as ONE line — the label, then its failureCauseChain — with no stack, since a transient's ten transport frames read as a crash in a report; anything else as the label followed by the error object, so a programming error keeps the stack that locates it.
Accessor decorator that delegates storage to an external SettingManager
(e.g. persistent settings), falling back to the in-memory field when none
is configured. The setting key is resolved once at decoration time rather
than on every get/set.
Method decorator factory that invokes the host's sync notification
after the decorated method resolves, forwarding params verbatim.
Generic over the consumer's sync-params shape (device ids, a type
filter…) like SessionAPI itself: @syncDevices({ type }) forwards
a payload, @syncDevices() notifies without one. The host contract
is structural: the returned method's this names it, which types the
body and any .call(host) use, but a TC39 application site does not
check it — the method's own type carries no this — so it is
documented, not enforced, as it was in the SDKs' own copies. No
action is taken when the host exposes no hook.
The username/password pair a session mechanism signs in with. Every consuming SDK's login takes exactly this shape; a protocol that also posts it verbatim as its login body (Gizwits does) keeps that fact — and any extra wire field — in its own types.
Fully-resolved counterpart of an undefined-tolerant input shape:
every property present and defined (defaults applied). Under
exactOptionalPropertyTypes, Required<T> removes ? but keeps an
explicit | undefined in the property type; this also strips it.
null is preserved — in this domain it is a sentinel, not an
absence marker.
Optional form of T whose properties may also be explicitly
undefined — the input-side counterpart of Partial<T> under
exactOptionalPropertyTypes (whose mapped ? does not admit a
present-undefined key). For inputs whose runtime treats a
present-undefined key exactly like an absent one.
Abstract base for API call logging data, serializable to JSON with a
fixed set of log keys. Serialization redacts through the injected
Redaction — the same vocabulary the HttpError snapshot
uses, so a secret cannot reach a log through either route.
Structured log data for an outgoing API request.
Structured log data for an API response.
Unit of cross-cutting resilience logic around a request attempt.
Compose N policies into a single pipeline. The first policy in the array is the outermost wrapper — it sees the request before any inner policy gets to decorate it, and sees the result last.
Disposable wrapper around setTimeout for internal background
bookkeeping (e.g. the auto-sync cadence). Auto-clears the previous
timeout when rescheduled and unrefs the underlying handle so a
scheduled callback never keeps the Node event loop alive on its
own — callers are still notified on the regular loop, but a script
that has nothing left to do can exit immediately.
Failure streaks per SUBJECT — an endpoint, the registry cycle — so a repeated failure is one event in the log rather than one entry per attempt.
Thin wrapper around a LifecycleEvents bundle that swallows any exceptions raised by consumer callbacks and logs them at error level. A misbehaving observer must never be able to break the request or sync flow — observability is a side concern, never a blocker.
Tracks an upstream rate-limit window and lets callers check whether the gate is currently closed.
Rate-limit circuit breaker. Two responsibilities:
Retry-After header and surface a diagnostic
log before re-raising. Subsequent callers see the paused state
and refuse immediately (see point 1).One-shot retry budget limiter.
Manages periodic auto-sync with a configurable interval. Drives the consuming clients' periodic registry refresh.
Exponential-backoff retry for transient failures — a 500, 502, 503 or 504, or a network failure (see isTransientFailure). Wraps the attempt with withRetryBackoff using the caller-provided telemetry hook for every retry tick.
Log data extended with the error message from a failed API call.
What a policy owns as an auth failure: the statuses, optionally narrowed by the response itself.
Minimal structural shape required by the API call loggers.
Subset of Temporal.Duration field values accepted by
RateLimitGate's fallback configuration. Matches what callers
actually need to express a rate-limit pause window.
The redaction engine bound to one sensitive-key vocabulary — the ONE
surface shared by the call loggers and the HttpError snapshot, so a
secret cannot reach a log through either route. Built by
createRedaction; each consumer builds exactly one, seeded
with its wire's credential keys.
The second vocabulary tier createRedaction accepts, declared APART from the credential keys because it answers a different rule: a credential must never reach a log because it opens an account; a personal-data field must never reach one because the user typed it, and a diagnostic report pasted into a public issue reproduces it.
Unit of cross-cutting resilience logic around a request attempt.
Options for withRetryBackoff.
Callback surface for per-retry instrumentation.
The credential keys every consumer redacts, whatever wire it speaks: the generic HTTP carriers plus the account pair every login flow posts. This is the BASE a protocol vocabulary extends through createRedaction — it is deliberately the intersection of the consuming SDKs' vocabularies, so adopting the core can only ever redact MORE, never less.
The engine bound to BASE_SENSITIVE_KEYS alone — the default every redaction seat falls back to when no vocabulary is injected. A consumer SDK should build its own via createRedaction and thread it through every seat; this default guarantees the generic carriers are covered even where it forgets to.
Default transient-retry budget shared by every consuming client. Keeping these in one place prevents drift: if we decide to tune the upper bound or jitter ratio, we update a single constant instead of one per consumer.
How long an unchanged failure stays silent before the log repeats it. Five minutes is the cadence at which one line per failed cycle was once accepted, and short enough that a diagnostic report's tail still carries a running failure.
HTTP status codes used across the SDK family. Single source so
callers don't redefine them per file (HTTP_STATUS_UNAUTHORIZED was
declared in three places before this module existed).
The largest serialised body a log record carries. A body above it is OMITTED whole — never cut — and the record names its size, so a reader knows what was there and how big it was.
Number of milliseconds in one day.
Number of milliseconds in one minute.
Number of milliseconds in one second.
Placeholder written over any value whose key names a secret.
Forward window applied by the consumers' session-refresh hooks: trigger the session refresh when the persisted token is within this many ms of its real expiry, so no request pays the full re-auth round-trip on its critical path.
Create structured error log data from a failed HTTP request. Uses response data when the error carries one, otherwise falls back to request-only data.
Builds the Redaction engine for one protocol vocabulary. The mechanism is owned here; the vocabulary is the caller's — the consuming SDK passes every key that names a credential on ITS wire, and the engine unions them with BASE_SENSITIVE_KEYS. A second, separately declared tier names the user-entered fields the same wire carries (RedactionOptions.personalDataKeys); both tiers blank the same way, through every seat the engine reaches.
A failure's identity for a streak, stable across repeats: an Error
by its name and message — an HttpError's message is its status
line, so a persistent refusal keeps one streak — a PRIMITIVE by its
own value, so two distinct thrown values stay two streaks, and any
other object by its type alone, since its default stringification
says nothing and a walk over it could print a credential. A dialect
whose message carries a value that changes between attempts (a
schema refusal naming what it received) derives its own reason from
this one.
The one sanctioned fire-and-forget seam: detach already-started work from the caller's critical path, logging a rejection instead of propagating it.
Render a Temporal.Duration in English diagnostic form, with
adaptive units: sub-minute windows render as seconds (e.g.
"20 seconds") so a short Retry-After never rounds to a misleading
"0 minutes"; longer windows render as "M minutes, S seconds".
Check whether an ISO 8601 expiry timestamp has passed or is malformed.
The transient set a read is retried on: a transient HTTP status (isTransientServerError) or a network failure (isTransientNetworkFailure). Only ever applied to idempotent reads, where re-sending is safe whatever stage the exchange died at.
Predicate suitable for RetryBackoffOptions.isRetryable: returns
true when the transport could not complete the exchange — Node's
TypeError: fetch failed (DNS, connect timeout, reset, TLS) or the
client's own timeout (DOMException named TimeoutError) — including
errors wrapped via Error.cause. A caller's abort (AbortError) and
every HTTP response return false. Measured on a Homey (2026-10-22,
01:05): a burst of 23 report reads drew 14 connect timeouts and 9
request timeouts while the same burst from a second host passed
thirty seconds later — exactly what a retry with backoff recovers.
Predicate suitable for RetryBackoffOptions.isRetryable: returns
true for transient HTTP 5xx status codes (500 / 502 / 503 / 504) including
errors wrapped via Error.cause. All other inputs return false.
Run operation, retrying on errors accepted by options.isRetryable.
The mechanism the SDK family shares: the session lifecycle and request pipeline, the redaction-seated transport, the observability shells, the resilience primitives, the error hierarchy and the two decorators — everything a consuming SDK's PRODUCTION code reaches. The vitest-backed test helpers live on their own
./testingsubpath and are never re-exported from here.