npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rikalabs/effect-react

v0.0.2

Published

Effect-native full-stack React meta-framework

Downloads

10

Readme

@rikalabs/effect-react

Effect-native full-stack React framework.

React is the view layer. Effect is the execution layer.

bun add @rikalabs/effect-react effect react react-dom

What it looks like

Define typed errors and services

import { Context, Effect, Layer, Schema } from "effect";

// Errors are values — declared as schemas, not thrown exceptions
class RateLimited extends Schema.TaggedError<RateLimited>()("RateLimited", {
  retryAfter: Schema.Number,
}) {}

class Unauthorized extends Schema.TaggedError<Unauthorized>()("Unauthorized", {
  reason: Schema.String,
}) {}

class PostsApi extends Context.Tag("PostsApi")<
  PostsApi,
  {
    readonly list: Effect.Effect<readonly Post[], Unauthorized>;
    readonly create: (input: {
      title: string;
      body: string;
    }) => Effect.Effect<Post, Unauthorized | RateLimited>;
  }
>() {}

Every failure is in the type signature. The compiler knows create can fail with Unauthorized | RateLimited — not unknown, not Error, not a string.

Wire it into the runtime

import { ManagedRuntime, Layer } from "effect";
import { EffectProvider } from "@rikalabs/effect-react";

const AppLayer = Layer.mergeAll(PostsApiLive, AuthServiceLive, AnalyticsLive);
const AppRuntime = ManagedRuntime.make(AppLayer);

const App = () => (
  <EffectProvider runtime={AppRuntime}>
    <PostsPage />
  </EffectProvider>
);

Query with full Effect power

import { Effect, Schema } from "effect";
import { defineQuery } from "@rikalabs/effect-react/query";

const Post = Schema.Struct({
  id: Schema.Number,
  title: Schema.String,
  body: Schema.String,
  authorId: Schema.Number,
});
type Post = typeof Post.Type;

export const postsQuery = defineQuery({
  name: "posts",
  input: Schema.Void,
  output: Schema.Array(Post),
  run: () =>
    Effect.gen(function* () {
      const api = yield* PostsApi;
      const analytics = yield* Analytics;
      const posts = yield* api.list;
      yield* analytics.track("posts_viewed", { count: posts.length });
      return posts;
    }),
});

The query run is a full Effect pipeline — it pulls services from the runtime, composes operations, and the framework handles caching, deduplication, and cancellation automatically.

Mutate with typed error channels

import { defineAction } from "@rikalabs/effect-react/framework";
import { invalidateQuery } from "@rikalabs/effect-react/query";

export const createPost = defineAction({
  name: "createPost",
  input: Schema.Struct({ title: Schema.String, body: Schema.String }),
  output: Post,
  error: Schema.Union(Unauthorized, RateLimited), // errors are part of the contract
  handler: (input) =>
    Effect.gen(function* () {
      const api = yield* PostsApi;
      const post = yield* api.create(input); // Unauthorized | RateLimited flows through
      yield* invalidateQuery(postsQuery, undefined);
      return post;
    }),
});

The error schema is part of the action contract. The framework validates it at the boundary — if the handler fails with RateLimited, the client gets a decoded RateLimited instance, not a 500.

Render by error type — not by guessing

import { Suspense } from "react";
import { useQuery } from "@rikalabs/effect-react/query";
import { useAction } from "@rikalabs/effect-react";
import { useForm } from "@rikalabs/effect-react/form";
import { useStoreSelector } from "@rikalabs/effect-react/state";

const PostList = () => {
  const posts = useQuery(postsQuery, undefined);

  if (posts.phase === "failure") {
    const error = posts.error;
    // error is Unauthorized | RateLimited | BoundaryDecodeError | ...
    // exhaustive — the compiler tells you every case
    switch (error._tag) {
      case "Unauthorized":
        return <p>Please sign in to view posts.</p>;
      case "RateLimited":
        return <p>Too many requests. Retry in {error.retryAfter}s.</p>;
      case "BoundaryDecodeError":
        return <p>The server returned unexpected data.</p>;
      default:
        return <p>Something went wrong.</p>;
    }
  }

  if (posts.phase !== "success") return <p>Loading...</p>;

  return (
    <ul>
      {posts.data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

No catch (e: unknown). No instanceof chains. No string matching. The error is a decoded value with typed fields — error.retryAfter is a number, not something you fish out of a response body.

Forms, actions, state — all in one page

const CreatePostForm = () => {
  const form = useForm(postForm);
  const action = useAction(createPost);

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        form.submit((values) =>
          Effect.gen(function* () {
            yield* runAction(createPost, values);
          }),
        );
      }}
    >
      <input
        value={form.values.title}
        onChange={(e) => form.setField("title", e.target.value)}
        placeholder="Title"
      />
      <textarea
        value={form.values.body}
        onChange={(e) => form.setField("body", e.target.value)}
        placeholder="Body"
      />
      {form.errors.title && <p>{form.errors.title}</p>}
      <button disabled={action.pending}>
        {action.pending ? "Creating..." : "Create Post"}
      </button>
      {action.error?._tag === "RateLimited" && (
        <p>Slow down — retry in {action.error.retryAfter}s</p>
      )}
      {action.error?._tag === "Unauthorized" && (
        <p>Session expired. Please sign in again.</p>
      )}
    </form>
  );
};

const PostsPage = () => {
  const currentUser = useStoreSelector(authStore, (s) => s.user);

  return (
    <main>
      <h1>Posts</h1>
      {currentUser && <CreatePostForm />}
      <PostList />
    </main>
  );
};

One runtime. Errors as values. Automatic cancellation. Schema-validated boundaries. No glue code.

What you get

  • Query — cached data fetching with deduplication, stale-while-revalidate, Suspense, and window-focus refetch
  • State — reactive stores with selectors, derived stores, and change streams
  • Form — schema-validated forms with typed errors and submit handlers as Effects
  • Actions — typed mutations with schema validation and HTTP transport
  • Router — type-safe routes, loaders, navigation, and <Link> component
  • Realtime — typed channels, pub/sub, and presence tracking
  • Framework — SSR, hydration, file-routing via Vite plugin, layouts, middleware
  • DI — Effect Layer for dependency injection — swap any service for tests without mocks

Modules

| Module | Import | |---|---| | Framework | @rikalabs/effect-react/framework | | Vite Plugin | @rikalabs/effect-react/framework/vite | | Config | @rikalabs/effect-react/config | | Server | @rikalabs/effect-react/server | | Client | @rikalabs/effect-react/client | | Query | @rikalabs/effect-react/query | | State | @rikalabs/effect-react/state | | Form | @rikalabs/effect-react/form | | Router | @rikalabs/effect-react/router | | Grid | @rikalabs/effect-react/grid | | Virtual | @rikalabs/effect-react/virtual | | Realtime | @rikalabs/effect-react/realtime | | Devtools | @rikalabs/effect-react/devtools | | Testing | @rikalabs/effect-react/testing |

Documentation

License

MIT