@ctx-kit/svelte
v0.1.1
Published
SSR-safe, framework-agnostic Svelte 5 context manager factory with an optional server singleton escape hatch.
Maintainers
Readme
@ctx-kit/svelte
SSR-safe context manager factory for Svelte 5. Wraps getContext/setContext with a typed,
key-based API, and adds one deliberate escape hatch — a process-scoped server singleton — for
the rare case where state is genuinely process-wide (a manifest, a feature-flag set, a search
index) rather than per-request.
Framework-agnostic: works in SvelteKit and in plain Vite+Svelte. No $app/environment or other
SvelteKit-only imports.
Install
npm install @ctx-kit/sveltesvelte (^5.0.0) is a peer dependency — install it in your app if it isn't already.
Why not just getContext/setContext?
You can, and this package is a thin layer on top, not a replacement. It exists for three things
raw getContext/setContext don't give you:
- A typed
use()that throws a useful error instead of returningundefinedwhen context was never initialized — so a missing provider fails loudly, near the mistake, not as a mysteriousundefinedthree components downstream. - A safe way to share truly process-scoped state (
serverSingleton: true) without accidentally leaking one request's data into another's — including deep-freezing plain object/array singletons by default, so a stray mutation can't cross requests. - Duplicate-init detection in dev (
trackContextInit) for the "why is this initialized twice" class of bug that's otherwise invisible until it causes a real one.
Basic usage
// lib/context/theme.ts
import { createContextManager } from '@ctx-kit/svelte';
export const themeManager = createContextManager({
key: 'app:theme',
create: (config?: { initial?: 'light' | 'dark' }) => ({
mode: config?.initial ?? 'light'
})
});<!-- +layout.svelte -->
<script lang="ts">
import { themeManager } from '$lib/context/theme';
themeManager.init();
</script><!-- some deeply nested component -->
<script lang="ts">
import { themeManager } from '$lib/context/theme';
const theme = themeManager.use(); // throws a clear error if no ancestor called init()
</script>Required configuration
If a value can't be created without some input (an auth token, a request-scoped value), use
createConfiguredContextManager instead — create/init both require config, enforced at
compile time:
import { createConfiguredContextManager } from '@ctx-kit/svelte';
export const authManager = createConfiguredContextManager<Auth, { token: string }>({
key: 'app:auth',
create: (config) => loadAuth(config.token)
});
authManager.init({ token }); // omitting the config is a compile errorServer singletons
⚠️ Only for read-only, non-user, process-scoped state. Never for auth, preferences, sessions, or anything user-specific — a server singleton is shared by every concurrent request on that server process.
export const manifestManager = createContextManager({
key: 'app:manifest',
create: () => loadManifest(),
serverSingleton: true // one instance per process, not per request
});Plain object/array singletons are deep-frozen immediately after creation by default (set
freeze: false to opt out, e.g. for a cache that's intentionally updated in place). Class
instances are never auto-frozen, since they may rely on internal mutable state.
clearServerSingleton(key) / clearServerSingletons() are exported for tests and HMR.
Sharing one context slot across packages
By default each manager mints its own private Symbol(key), so two managers can safely use the
same string key without colliding. If two independent packages genuinely need to read/write the
same context (e.g. a Guard package reading the Auth context an Auth package sets), export one
Symbol from a shared module and pass it as contextSymbol on both sides:
// shared-symbols.ts
export const authSymbol = Symbol('app:auth');
// auth-package.ts
createContextManager({ key: 'app:auth', create: loadAuth, contextSymbol: authSymbol });
// guard-package.ts
createContextManager({ key: 'app:auth', create: loadAuth, contextSymbol: authSymbol });Dev-only duplicate-init detection
import { trackContextInit } from '@ctx-kit/svelte';
function initAuthContext() {
trackContextInit('Auth'); // warns if called more than once in the same tree
setContext('auth', auth);
}No-op (and tree-scan-free) in production builds.
API
| Export | Purpose |
| ----------------------------------- | ------------------------------------------------------------ |
| createContextManager | Manager for state whose factory takes optional config. |
| createConfiguredContextManager | Manager for state whose factory requires config. |
| trackContextInit | Dev-only duplicate-initialization warning. |
| clearServerSingleton(key) | Evict one cached server singleton (tests/HMR). |
| clearServerSingletons() | Evict every cached server singleton (tests/HMR). |
Each manager exposes: key, singleton, init(config?), use(), peek(), useOrFallback(opts?).
See docs/audit.md in the repository for the design rationale and the adversarial-audit history
behind these APIs.
License
MIT
