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

@typesugar/effect

v0.1.1

Published

🧊 Effect-TS adapter for typesugar - do-notation and comprehension syntax

Readme

@typesugar/effect

Zero-boilerplate services, automatic layer wiring, and compile-time optimization for Effect-TS.

Why @typesugar/effect?

Effect-TS gives you structured concurrency, typed errors, and dependency injection. @typesugar/effect makes it easier to use and faster at runtime:

  • @service β€” Define services without Context.Tag boilerplate
  • @layer β€” Declare dependencies; the compiler wires them
  • layerMake / resolveLayer β€” ZIO-style layer composition with clear errors when something's missing
  • @compiled β€” Eliminate generator overhead in Effect.gen at compile time

What You Get

// @service β€” one decorator, you get the Tag and accessors
@service
interface UserRepo {
  findById(id: string): Effect.Effect<User, NotFound>;
}

// @layer β€” declare what you need, compiler figures out the wiring
@layer(UserRepo, { requires: [Database] })
const userRepoLive = let: { db << Database } yield: ({
  findById: (id) => db.query(sql`SELECT * FROM users WHERE id = ${id}`),
});

// layerMake β€” you list layers, typesugar composes them
const appLayer = layerMake<UserRepo | Database>(userRepoLive, databaseLive);
// @compiled β€” bonus: Effect.gen without generator overhead
class UserService {
  @compiled
  getWithPosts(id: string) {
    return Effect.gen(function* () {
      const user = yield* getUser(id);
      const posts = yield* getPosts(user.id);
      return { user, posts };
    });
  }
}
// Compiles to direct flatMap chain, no iterator objects

When layers are missing, you get clear errors:

error[EFFECT001]: No layer provides `UserRepo`
  --> src/app.ts:15:5
   |
15 |   const result = program.pipe(Effect.provide(appLayer))
   |                  ^^^^^^^ requires UserRepo
   |
   = note: Effect<void, Error, UserRepo | Database> needs layers for:
           - UserRepo (no layer found)
           - Database (provided by `databaseLive` at src/layers.ts:8)
   = help: Add a layer:
           @layer(UserRepo) const userRepoLive = { ... }

Installation

npm install @typesugar/effect effect
# or
pnpm add @typesugar/effect effect

Build tooling required: @typesugar/effect runs as a TypeScript compiler plugin. You'll need to configure the transformer in your build tool (Vite, esbuild, webpack) and the language service plugin for IDE support. For teams with existing build systems this is straightforward, but it's not zero-configuration.


Service & Layer System

The main value: define services and layers with minimal boilerplate, let the compiler handle wiring.

@service β€” Zero-Boilerplate Services

import { service } from "@typesugar/effect";

@service
interface HttpClient {
  get(url: string): Effect.Effect<Response, HttpError>;
  post(url: string, body: unknown): Effect.Effect<Response, HttpError>;
}

// Generates:
// - HttpClientTag (Context.Tag)
// - HttpClient namespace with accessor functions

@layer β€” Declarative Dependencies

import { layer } from "@typesugar/effect";

@layer(HttpClient)
const httpClientLive = {
  get: (url) => Effect.tryPromise(() => fetch(url)),
  post: (url, body) => Effect.tryPromise(() =>
    fetch(url, { method: "POST", body: JSON.stringify(body) })
  ),
};

@layer(UserRepo, { requires: [Database] })
const userRepoLive =
let: {
  db << Database;
}
yield: ({
  findById: (id) => db.query(sql`SELECT * FROM users WHERE id = ${id}`)
});

Layer Wiring β€” Two Approaches

Inspired by ZIO's ZLayer.make, @typesugar/effect offers two ways to compose layers:

layerMake<R>(...) β€” Explicit (ZIO-style)

List the layer values explicitly. The compiler resolves the dependency graph:

import { layerMake } from "@typesugar/effect";

// You provide the ingredients, typesugar figures out the wiring
const appLayer = layerMake<UserRepo | HttpClient>(
  userRepoLive, // requires Database
  databaseLive, // no requirements
  httpClientLive // no requirements
);

// Compiles to:
// Layer.merge(
//   userRepoLive.pipe(Layer.provide(databaseLive)),
//   httpClientLive
// )

Missing dependencies produce clear errors:

error: Missing layers for:
  - Database (required by userRepoLive)
Add the missing layers to layerMake<R>() arguments.

resolveLayer<R>() β€” Implicit (from registered layers)

The compiler resolves layers automatically from @layer registrations visible in your import scope:

import { resolveLayer } from "@typesugar/effect";

const program: Effect<void, Error, UserRepo | HttpClient> = ...;

// Automatically finds and composes all required layers:
const runnable = program.pipe(
  Effect.provide(resolveLayer<UserRepo | HttpClient>())
);

Only layers from files in your import graph are considered β€” no global action-at-a-distance.

Layer Dependency Resolution

Layer wiring is built on a dependency graph. The layer-graph module defines a layerGraphLike β€” a GraphLike instance for the layer dependency graph β€” and uses topoSortG from @typesugar/graph to resolve the correct composition order. Cycles are detected via detectCycles before composition. Both layerMake and resolveLayer use this shared graph logic.

Debug Tree

Both approaches support { debug: true } to emit the resolved wiring graph at compile time (like ZIO's ZLayer.Debug.tree):

// See what the compiler resolved
const appLayer = layerMake<UserRepo | HttpClient>(userRepoLive, databaseLive, httpClientLive, {
  debug: true,
});

// Emits at compile time:
// Layer Wiring Graph
//
// β—‘ userRepoLive
// ╰─◉ databaseLive
// β—‰ httpClientLive

When to Use Which

| Approach | Best for | | ------------------- | ------------------------------------------------------------------ | | layerMake<R>(...) | Tests, app entry points, when you want to see exactly what's wired | | resolveLayer<R>() | Large apps, rapid prototyping, when listing every layer is tedious |


Rich Diagnostics

Service Resolution Errors

When a program requires services that aren't provided:

error[EFFECT001]: No layer provides `UserRepo`
  --> src/app.ts:15:5
   |
15 |   Effect.provide(program, appLayer)
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires UserRepo
   |
   = note: Effect<void, Error, UserRepo | Database> needs:
           - UserRepo (no layer found)
           - Database (provided by databaseLive at src/layers.ts:8)
   = help: Add a layer with @layer(UserRepo)

Layer Dependency Cycles

Circular dependencies are detected at compile time:

error[EFFECT020]: Circular layer dependency detected
  --> src/layers.ts
   |
   = note: Dependency cycle:
           AuthService β†’ UserRepo β†’ Database β†’ AuthService
                                               ^^^^^^^^^^^ cycle
   |
 5 | @layer(AuthService, { requires: [UserRepo] })
   |        ^^^^^^^^^^^ depends on UserRepo
12 | @layer(UserRepo, { requires: [Database] })
   |        ^^^^^^^^ depends on Database
18 | @layer(Database, { requires: [AuthService] })
   |        ^^^^^^^^ depends on AuthService (creates cycle)

Error Handler Completeness

Warns when error handlers don't cover all error types:

warning[EFFECT010]: Error handler doesn't cover all error types
  --> src/handler.ts:22:3
   |
22 |   Effect.catchTag("NotFound", () => Effect.succeed(null))
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: Unhandled: DbError, ValidationError
           from getUser() at line 12, validateInput() at line 14
   = help: Add handlers for DbError, ValidationError

Schema Type Drift

Detects when types and schemas diverge:

error[EFFECT030]: Schema `UserSchema` is out of sync with type `User`
  --> src/models.ts:5:3
   |
 5 |   email: string;  // field added to interface
   |   ^^^^^ new field not in UserSchema
   |
   = help: Regenerate with @derive(EffectSchema) or update manually

Zero-Cost Optimizations

@compiled β€” Eliminate Generator Overhead

The @compiled decorator transforms Effect.gen calls into direct flatMap chains:

import { compiled } from "@typesugar/effect";

class UserService {
  @compiled
  getWithPosts(id: string) {
    return Effect.gen(function* () {
      const user = yield* getUser(id);
      const posts = yield* getPosts(user.id);
      return { user, posts };
    });
  }
}

// Compiles to:
class UserService {
  getWithPosts(id: string) {
    return Effect.flatMap(getUser(id), (user) =>
      Effect.map(getPosts(user.id), (posts) => ({ user, posts }))
    );
  }
}

Or use compileGen() directly:

import { compileGen } from "@typesugar/effect";

const program = compileGen(
  Effect.gen(function* () {
    const x = yield* getX();
    const y = yield* getY(x);
    return x + y;
  })
);

@fused β€” Pipeline Fusion

The @fused decorator detects and fuses consecutive Effect operations:

import { fused } from "@typesugar/effect";

class DataPipeline {
  @fused
  process(data: Data) {
    return pipe(getData(data), Effect.map(parse), Effect.map(validate), Effect.map(transform));
  }
}

// Compiles to (map∘map fusion):
class DataPipeline {
  process(data: Data) {
    return pipe(
      getData(data),
      Effect.map((x) => transform(validate(parse(x))))
    );
  }
}

Fusion rules applied:

  • map(map(fa, f), g) β†’ map(fa, x => g(f(x)))
  • flatMap(succeed(a), f) β†’ f(a)
  • flatMap(map(fa, f), g) β†’ flatMap(fa, x => g(f(x)))

specializeSchema β€” Compile-Time Validation

Generate specialized validators from Effect Schema at compile time:

import { specializeSchema } from "@typesugar/effect";
import { Schema } from "effect";

const UserSchema = Schema.Struct({
  id: Schema.String,
  name: Schema.String,
  age: Schema.Number,
});

// Generic combinator walk at runtime
const validateSlow = Schema.decodeSync(UserSchema);

// Direct field checks, no combinator overhead
const validateFast = specializeSchema(UserSchema);

The specialized validator compiles to direct type checks:

const validateFast = (input: unknown): User => {
  if (typeof input !== "object" || input === null) {
    throw new Error("Expected object");
  }
  const obj = input as Record<string, unknown>;
  if (typeof obj.id !== "string") {
    throw new Error("Field 'id': expected string");
  }
  if (typeof obj.name !== "string") {
    throw new Error("Field 'name': expected string");
  }
  if (typeof obj.age !== "number") {
    throw new Error("Field 'age': expected number");
  }
  return input as User;
};

Testing Utilities

Mock Effect services with full type safety:

import { mockService, testLayer, assertCalled } from "@typesugar/effect";

// Create typed mock
const mockUserRepo = mockService<UserRepo>({
  getUser: (id) => Effect.succeed({ id, name: "Test User" }),
});

// Override for specific test
mockUserRepo.getUser.mockImplementation(() => Effect.fail(new NotFound()));

// Create test layer
const TestUserRepo = testLayer(UserRepo, mockUserRepo);

// Run test
const result = await Effect.runPromise(pipe(program, Effect.provide(TestUserRepo)));

// Verify calls
assertCalled(mockUserRepo, "getUser", ["123"]);

Do-Notation

Enhanced do-notation with proper E/R type inference:

// Error and requirement types accumulate correctly:
let: {
  user << getUserById(id); // Effect<User, NotFound, UserRepo>
  posts << getPosts(user.id); // Effect<Post[], DbError, PostRepo>
}
yield: ({ user, posts });

// Result: Effect<{ user, posts }, NotFound | DbError, UserRepo | PostRepo>

Derive Macros

Auto-generate Effect implementations:

import { EffectSchema, EffectEqual, EffectHash } from "@typesugar/effect";

@derive(EffectSchema)
interface User {
  id: string;
  name: string;
  age: number;
}
// Generates: export const UserSchema = Schema.Struct({ ... })

@derive(EffectEqual, EffectHash)
interface Point {
  x: number;
  y: number;
}
// Generates: PointEqual and PointHash implementations

API Reference

Service & Layer

| Export | Description | | ------------------------- | ----------------------------------------------------------- | | @service | Generate Context.Tag and accessors | | @layer(Service, opts?) | Create layer with dependency tracking | | layerMake<R>(...layers) | ZIO-style explicit wiring from listed layers | | resolveLayer<R>(opts?) | Implicit wiring from @layer registrations in import scope | | formatDebugTree() | Format resolved graph as a tree string | | serviceRegistry | Registered services | | layerRegistry | Registered layers |

Zero-Cost Macros

| Macro | Description | | -------------------------- | ------------------------------------------------- | | @compiled | Transform Effect.gen to direct flatMap chains | | compileGen() | Expression-level generator compilation | | @fused | Fuse consecutive Effect operations | | fusePipeline() | Expression-level pipeline fusion | | specializeSchema() | Compile Schema to direct validation | | specializeSchemaUnsafe() | Compile Schema without error wrapping |

Diagnostics

| Code | Category | Severity | Description | | --------- | ------------------- | -------- | ------------------------------------ | | EFFECT001 | Service Resolution | error | No layer provides required service | | EFFECT002 | Service Resolution | error | Layer provides wrong service type | | EFFECT003 | Service Resolution | warning | Multiple layers provide same service | | EFFECT010 | Error Completeness | warning | Unhandled error types | | EFFECT011 | Error Completeness | info | Redundant error handler | | EFFECT020 | Layer Dependency | error | Circular layer dependency | | EFFECT021 | Layer Dependency | info | Unused layer in composition | | EFFECT030 | Schema Drift | error | Schema/type drift detected | | EFFECT040 | Type Simplification | info | Type could be simplified |

Testing

| Export | Description | | ------------------------------------ | ------------------------------------ | | mockService<T>() | Create typed mock with call tracking | | testLayer(tag, mock) | Create test layer from mock | | combineLayers(...layers) | Combine test layers | | assertCalled(mock, method) | Verify method was called | | assertNotCalled(mock, method) | Verify method was not called | | assertCalledTimes(mock, method, n) | Verify call count |

Derive Macros

| Export | Description | | ----------------------- | ----------------------- | | @derive(EffectSchema) | Generate Schema.Struct | | @derive(EffectEqual) | Generate Equal instance | | @derive(EffectHash) | Generate Hash instance |


How It Works

@typesugar/effect uses TypeScript's compiler API to:

  1. Analyze Effect patterns at compile time
  2. Transform abstractions into direct code
  3. Emit diagnostics with source locations and suggestions
  4. Preserve Effect's runtime semantics (fibers, interruption, scheduling)

The optimization removes abstraction overhead while keeping runtime behavior intact. Your code runs on Effect's full fiber runtime β€” just faster.


License

MIT