Integration library for Effect with SvelteKit - seamless effect handling in SvelteKit remote functions
npm install @kuroski/effect-svelteIntegration library for Effect with SvelteKit. Run Effect programs in SvelteKit load functions and remote functions with automatic error handling, redirects, and form validation.
``sh`
npm add effect-svelteor
bun add effect-svelte
Peer dependencies: effect >=3.19.15, svelte >=5, @sveltejs/kit >=2
`ts
// src/lib/server/runtime.ts
import { Effect, Layer, ManagedRuntime } from "effect";
import { createRunner } from "effect-svelte";
const AppLayer = Layer.mergeAll(
// your service layers
);
const RuntimeServer = ManagedRuntime.make(AppLayer);
export const remoteRunner = createRunner({
runtime: RuntimeServer,
before: () => Effect.log("Starting operation"),
after: () => Effect.log("Operation completed"),
onError: (err, isUnexpectedError) =>
isUnexpectedError
? Effect.logError("Operation failed", err)
: Effect.void,
});
`
The runner works directly inside SvelteKit load functions. Call it with an operation name, your Effect program, and an optional error-mapping pipeline:
`ts
// src/routes/posts/+page.server.ts
import { Effect } from "effect";
import { httpErrorEffect } from "effect-svelte";
import { remoteRunner } from "$lib/server/runtime";
export async function load({ params }) {
return remoteRunner(
"load-posts",
Effect.gen(function* () {
const posts = yield* fetchPosts();
if (posts.length === 0) {
yield* httpErrorEffect(404, "NOT_FOUND", "No posts found");
}
return { posts };
}),
);
}
`
`svelte
$3
The same runner works with SvelteKit's experimental remote functions:
`ts
// src/routes/posts.remote.ts
import { Effect } from "effect";
import { query } from "$app/server";
import { remoteRunner } from "$lib/server/runtime";export const getPosts = query(() =>
remoteRunner(
"get-posts",
Effect.gen(function* () {
yield* Effect.logInfo("Fetching posts");
return { posts: [{ id: 1, title: "Hello" }] };
}),
),
);
``svelte
{#snippet pending()}
loading...
{/snippet}
{#snippet failed(error)}
Error: {error}
{/snippet}
{@const { posts } = await getPosts()}
{#each posts as post (post.id)}
- {post.title}
{/each}
`Error Handling
The library provides three tagged error types that map to SvelteKit's control flow:
`ts
import { Effect } from "effect";
import {
SvelteKitRedirect,
SvelteKitHttpError,
SvelteKitInvalidError,
redirectEffect,
httpErrorEffect,
invalidEffect,
} from "effect-svelte";Effect.gen(function* () {
// Redirect (throws SvelteKit redirect())
yield* redirectEffect(303, "/login");
// or: yield* Effect.fail(SvelteKitRedirect.make(303, "/login"));
// HTTP error (throws SvelteKit error())
yield* httpErrorEffect(404, "NOT_FOUND", "Resource not found");
// or: yield* Effect.fail(SvelteKitHttpError.make(404, "NOT_FOUND", "Not found"));
// Form validation error (throws SvelteKit invalid())
yield* invalidEffect("email", "Invalid email format");
// or: yield* Effect.fail(SvelteKitInvalidError.make({ email: "Invalid" }));
});
`$3
Use the third argument of the runner to map domain errors to SvelteKit errors:
`ts
export const listProjects = remoteRunner(
"listProjects",
Effect.gen(function* () {
const service = yield* ProjectService;
return yield* service.all();
}),
(effect) =>
effect.pipe(
Effect.catchTags({
ParseError: () =>
httpErrorEffect(500, "PARSE_ERROR", "Unexpected data from the server"),
ResponseError: () =>
httpErrorEffect(500, "GENERIC_ERROR", "Unexpected server response"),
}),
),
);
`API Reference
$3
Creates a runner function that executes Effect programs in a SvelteKit context.
Options:
| Option | Type | Description |
|--------|------|-------------|
|
runtime | ManagedRuntime | The Effect runtime to use |
| before | () => Effect | Runs before the effect |
| after | (result: A) => Effect | Runs after successful execution |
| onError | (err, isUnexpectedError) => Effect | Error handler. isUnexpectedError is true for 500+ errors and unrecognized failures, false for redirects, validation errors, and <500 HTTP errors |Returns:
Runner - a function with signature:`ts
(operationName: string, effect: Effect, pipeline?: PipelineFn) => Promise
`Execution order:
before → effect → pipeline → onError (on failure) → after (on success) → withSpan(operationName)$3
| Class | Converts to | Factory |
|-------|-------------|---------|
|
SvelteKitRedirect | redirect(status, location) | SvelteKitRedirect.make(status, location) |
| SvelteKitHttpError | error(status, body) | SvelteKitHttpError.make(status, code, message, details?) |
| SvelteKitInvalidError | invalid(issues) | SvelteKitInvalidError.make(issues) |$3
| Function | Description |
|----------|-------------|
|
redirectEffect(status, location) | Returns Effect.fail(SvelteKitRedirect.make(...)) |
| httpErrorEffect(status, code, message, details?) | Returns Effect.fail(SvelteKitHttpError.make(...)) |
| invalidEffect(issues) | Returns Effect.fail(SvelteKitInvalidError.make(...)) |$3
Error code type:
"GENERIC_ERROR" | "PARSE_ERROR" | "NOT_FOUND" | "UNAUTHORIZED"`MIT