@status-kit/manager-svelte
v0.1.0
Published
A tiny, pluggable status/state primitive for Svelte 5 — compose any number of named entries into one reactive status.
Maintainers
Readme
@status-kit/manager-svelte
A tiny, pluggable status/state primitive for Svelte 5. StatusManager holds
a bag of arbitrarily-named entries and compiles them into one status, using
hooks you supply — the manager itself has no opinion about what a "status"
is.
- Primitive — one
SvelteMapand four memoized$derivedreads. No$effect. - Pluggable —
compile,onPending,onError,onSuccessare all overridable; sensible defaults are provided so it works out of the box. - SSR-safe — as long as you create one instance per request (see below).
- Zero dependencies other than Svelte itself.
Install
pnpm add @status-kit/manager-svelteQuick start
Single-slot usage — track one thing:
import { StatusManager } from "@status-kit/manager-svelte";
const uploadStatus = new StatusManager({ initialStatus: "idle" });
async function upload(file: File) {
uploadStatus.update("pending");
try {
await api.upload(file);
uploadStatus.update("success");
} catch {
uploadStatus.update("failed");
}
}<button disabled={uploadStatus.isPending}>
{uploadStatus.isPending ? "Uploading…" : "Upload"}
</button>Typed entries — pass a TEntries map (the same pattern Node's
EventEmitter<TEvents> uses) to get autocomplete and compile-time
validation on every get / set / has / delete call:
import { StatusManager, type StatusManagerStatus } from "@status-kit/manager-svelte";
interface PageEntries {
profile: StatusManagerStatus;
settings: StatusManagerStatus;
}
const pageStatus = new StatusManager<PageEntries>();
pageStatus.set("profile", "success"); // ✓ key and value both checked
pageStatus.set("profile", "nope"); // ✗ type error — not a StatusManagerStatus
pageStatus.set("billing", "success"); // ✗ type error — "billing" isn't a key of PageEntries
pageStatus.get("profile"); // StatusManagerStatus | undefined, with autocomplete on the keyTEntries defaults to Record<string, StatusManagerStatus> (any string key,
StatusManagerStatus values) so untyped, freeform usage — as in the
examples above — keeps working with no type arguments at all.
Multi-slot usage — compile several independent statuses into one:
const pageStatus = new StatusManager();
pageStatus.set("profile", "success");
pageStatus.set("settings", "pending");
pageStatus.status; // null — entries disagree, no consensus
pageStatus.set("settings", "success");
pageStatus.status; // "success" — every entry now agreesAPI
new StatusManager<TEntries, TStatus>(options?)
Both type parameters are optional:
TEntries extends Record<string, any>— maps entry keys to their value types, e.g.{ profile: StatusManagerStatus; settings: StatusManagerStatus }. Constrained toRecord<string, any>rather thanRecord<string, unknown>on purpose: a plain interface without an index signature doesn't structurally satisfy aRecord<string, unknown>generic constraint in TypeScript, so requiringunknownwould force every consumer to add a redundant index signature to their entries type. Defaults toRecord<string, StatusManagerStatus>(any string key,StatusManagerStatusvalues), which is why untyped usage needs no type arguments.TStatus extends string— the compiled status vocabulary. Defaults toStatusManagerStatus.
get, set, has, and delete all key off TEntries: with a custom
TEntries, the key parameter is restricted to keyof TEntries (with
editor autocomplete) and get/set's value is inferred as TEntries[key].
With the default TEntries, any string key is accepted, matching the
original untyped "bag of entries" behavior.
| Option | Type | Description |
| ------------------ | ---------------------------------------- | ----------------------------------------------------------------------------- |
| initialStatus | TStatus | Shorthand: seeds a single 'default' entry. |
| initialStatuses | SvelteMap<string, unknown> | Seeds arbitrary entries verbatim. Takes precedence over initialStatus. |
| compile | (manager) => TStatus \| null | Derives the compiled status from current entries. Defaults to "consensus". |
| onPending | (manager) => boolean | Derives isPending. Defaults to status === 'pending'. |
| onError | (manager) => boolean | Derives isError. Defaults to status === 'failed'. |
| onSuccess | (manager) => boolean | Derives isSuccess. Defaults to status === 'success'. |
Instance members
statuses: SvelteMap<string, unknown>— the raw entries, reactive.status: TStatus | null— compiled status ($derived).isPending/isError/isSuccess: boolean— hook-derived flags ($derived).get<T>(key)/set<T>(key, value)— type-safe reads/writes;setreturnsthis.has(key)/delete(key)— standard map-like helpers.update(value)— shorthand forset('default', value); returnsthis.clear()— removes every entry.
defaultStatusManagerOptions
The default compile / onPending / onError / onSuccess hooks, exported
standalone in case you want to reuse or partially extend them:
import { StatusManager, defaultStatusManagerOptions } from "@status-kit/manager-svelte";
const manager = new StatusManager({
...defaultStatusManagerOptions,
onError: (m) => m.status === "failed" || m.has("fatal")
});SSR
Never create a StatusManager at module scope and share it across requests
— with SSR enabled, module-level state is shared by every concurrent
request on the server and will leak between users. Always create one
instance per component or per request (inside a component's <script>, a
Svelte context, or a per-request factory).
License
MIT
