@kuroski/effect-svelte
v1.0.1
Published
Integration library for Effect with SvelteKit - seamless effect handling in SvelteKit remote functions
Downloads
11
Maintainers
Readme
effect-svelte
Integration library for Effect with SvelteKit. Run Effect programs in SvelteKit load functions and remote functions with automatic error handling, redirects, and form validation.
Installation
npm add effect-svelte
# or
bun add effect-sveltePeer dependencies: effect >=3.19.15, svelte >=5, @sveltejs/kit >=2
Quick Start
1. Create a Runner
// 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,
});2. Use in Load Functions
The runner works directly inside SvelteKit load functions. Call it with an operation name, your Effect program, and an optional error-mapping pipeline:
// 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 };
}),
);
}<!-- src/routes/posts/+page.svelte -->
<script lang="ts">
let { data } = $props();
</script>
<ul>
{#each data.posts as post (post.id)}
<li>{post.title}</li>
{/each}
</ul>3. Use in Remote Functions
The same runner works with SvelteKit's experimental remote functions:
// 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" }] };
}),
),
);<!-- src/routes/+page.svelte -->
<script lang="ts">
import { getPosts } from "./posts.remote.ts";
</script>
<svelte:boundary>
{#snippet pending()}
<p>loading...</p>
{/snippet}
{#snippet failed(error)}
<span>Error: {error}</span>
{/snippet}
{@const { posts } = await getPosts()}
<ul>
{#each posts as post (post.id)}
<li>{post.title}</li>
{/each}
</ul>
</svelte:boundary>Error Handling
The library provides three tagged error types that map to SvelteKit's control flow:
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" }));
});Pipeline for Error Mapping
Use the third argument of the runner to map domain errors to SvelteKit errors:
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
createRunner(options)
Creates a runner function that executes Effect programs in a SvelteKit context.
Options:
| Option | Type | Description |
|--------|------|-------------|
| runtime | ManagedRuntime<R, never> | The Effect runtime to use |
| before | () => Effect<void> | Runs before the effect |
| after | (result: A) => Effect<void> | Runs after successful execution |
| onError | (err, isUnexpectedError) => Effect<void> | Error handler. isUnexpectedError is true for 500+ errors and unrecognized failures, false for redirects, validation errors, and <500 HTTP errors |
Returns: Runner<R> - a function with signature:
(operationName: string, effect: Effect<A, E, R>, pipeline?: PipelineFn<R>) => Promise<A>Execution order: before → effect → pipeline → onError (on failure) → after (on success) → withSpan(operationName)
Error Types
| 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) |
Convenience Functions
| 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(...)) |
SvelteEffect.Code
Error code type: "GENERIC_ERROR" | "PARSE_ERROR" | "NOT_FOUND" | "UNAUTHORIZED"
License
MIT
