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

@systemfsoftware/effect-cell-types

v6.0.0

Published

Type-level contracts for the repo's cell taxonomy, starting with the Workflow decision channel — branded Workflow<C, D, E> types plus a runtime make constructor shared across the repo's cells.

Readme

@systemfsoftware/effect-cell-types

The type-level contract for a *.workflow.ts cell. A workflow is a pure decision — a command in, a Result out — and Workflow<Command, Decision, Error> pins that shape in the type system. The contract is checked by tsc from the file's content (an exported value that violates the shape stops the build), not by a lint rule keyed on the file's name. Beside the types the package ships exactly one runtime value — the identity constructor make — and the type tests (test-types/Workflow.tst.ts, run by tstyche) prove the channel guards still bind.

The contract

import type { Workflow } from '@systemfsoftware/effect-cell-types'

type Decide = Workflow<Command, Decision, Error>
//          = ((command: Command) => Result<Decision, Error>) & WorkflowBrand

When both channels are inhabited, Workflow<Command, Decision, Error> is the function type (command: Command) => Result<Decision, Error> carrying the nominal WorkflowBrand conjunct — a phantom readonly TypeId-keyed field that no runtime property backs. The brand is what makes the workbook nominal: Workflow.make is the only constructor that applies it, and every surface that runs a decision — the decide member a Cell.layer spec demands — requires it, so a decision that skipped make is a compile error at the call site that would have run it, with the brand named in the diagnostic. A never channel does not silently collapse to that function: it resolves to a marker interface that no function can satisfy, so the mistake is a compile error with the remediation attached (below). The success channel is shaped the same way: Workflow.make refuses a decision channel that is not a tagged union of at least two schema tagged classes sharing one TypeId — a single outcome, an untagged variant, or variants with divergent family brands each resolve to a marker interface whose property name is the remediation.

The constructor

Executors build a workflow from the command's schema class and a decider over that class — runtime identity, one assertion across the branded return:

import { make } from '@systemfsoftware/effect-cell-types'
import { Result } from 'effect'
import * as S from 'effect/Schema'

export class DecideInput extends S.Class<DecideInput>('DecideInput')({
  exitSuccess: S.Boolean,
}) {}

export const decide = make(
  DecideInput,
  (input) => (input.exitSuccess
    ? Result.succeed(new RestartDecisionContinue())
    : Result.fail(new RestartDecisionExhausted())),
)

The command is constrained on the value, not on a type parameter inferred from the decider's parameter, and that is the whole mechanism. A constraint on such a parameter is a structural predicate, and TypeScript cannot express "this type came from a class declaration" — so a marker placed there is just a property, and interface Fake extends Marker {} satisfies it. A declared type produces no value, so it cannot reach an argument position at all: an interface at the command position is refused with "only refers to a type, but is being used as a value here". Schema.Class and Schema.TaggedClass are both accepted; a Schema.Struct, a plain class, an object literal and a primitive are each refused.

No type argument needs writing: the command type comes from the class, so the decider's parameter needs no annotation. The never-channel conditional still lives on the return type, so a total decision (Result<Decision, never>) resolves to UninhabitedError and the call site fails with "This expression is not callable", while a Promise- or bare-value-returning decider is rejected at the argument. make is a runtime value, so consumers need it as an ordinary import only where they construct workflows; everywhere else import type still erases at compile time.

Worked example

decideRestart in @systemfsoftware/effect-daemon-spec (src/internal/restart-decision.workflow.ts) is the real consumer — a supervisor deciding what to do when a child exits. The sibling schema file, which defines DecideInput and RestartStrategy, is elided here.

import { Workflow } from '@systemfsoftware/effect-cell-types'
import { Result } from 'effect'
import * as Match from 'effect/Match'
import * as S from 'effect/Schema'

const RestartDecisionTypeId: unique symbol = Symbol.for(
  '@systemfsoftware/effect-daemon/RestartDecision',
)
type RestartDecisionTypeId = typeof RestartDecisionTypeId

export class RestartDecisionContinue extends S.TaggedClass<RestartDecisionContinue>()('Continue', {}) {
  readonly [RestartDecisionTypeId] = RestartDecisionTypeId
}

export class RestartDecisionRestart extends S.TaggedClass<RestartDecisionRestart>()('Restart', {
  indices: S.NonEmptyArray(S.Int),
}) {
  readonly [RestartDecisionTypeId] = RestartDecisionTypeId
}

export class RestartDecisionExhausted extends S.TaggedError<RestartDecisionExhausted>()('Exhausted', {}) {
  readonly [RestartDecisionTypeId] = RestartDecisionTypeId
}

const restartIndicesFor = (
  strategy: RestartStrategy,
  failedIndex: number,
  total: number,
): readonly [number, ...readonly number[]] =>
  Match.value(strategy).pipe(
    Match.when('one_for_one', () => [failedIndex] as const),
    Match.when(
      'one_for_all',
      () => [0, ...Array.from({ length: Math.max(0, total - 1) }, (_, i) => i + 1)] as const,
    ),
    Match.when(
      'rest_for_one',
      () =>
        [
          failedIndex,
          ...Array.from({ length: Math.max(0, total - failedIndex - 1) }, (_, i) => failedIndex + 1 + i),
        ] as const,
    ),
    Match.exhaustive,
  )

export const decideRestart = Workflow.make(
  DecideInput,
  (input): Result.Result<
    RestartDecisionContinue | RestartDecisionRestart,
    RestartDecisionExhausted
  > =>
    Match.value(input).pipe(
      Match.when({ exitSuccess: true }, () => Result.succeed(new RestartDecisionContinue())),
      Match.when(
        { exitSuccess: false, intensityExceeded: true },
        () => Result.fail(new RestartDecisionExhausted()),
      ),
      Match.orElse(() =>
        Result.succeed(
          new RestartDecisionRestart({
            indices: restartIndicesFor(input.strategy, input.failedIndex, input.totalChildren),
          }),
        )
      ),
    ),
)

The shape to copy: one exported decision built by Workflow.make, taking the command's schema class and a decider over that class, whose body returns Result values via Result.succeed and Result.fail. The command channel comes from the class, the decision and error channels are inferred from the annotated return, and make is the only door to the WorkflowBrand conjunct — annotating a function Workflow<…> directly is still refused wherever the brand is demanded, because a workflow that never passed through make is not a decision anything may run. The error channel is a real variant (RestartDecisionExhausted) — giving up is a decision the caller must branch on, so declaring the error channel never is rejected, not allowed.

What it rejects at compile time

All six violations fail tsc; the messages below are what tsc reports (verified against this package and [email protected]).

| Violation | tsc reports | Why it is rejected | | ----------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | A Promise return | Type 'Promise<Decision>' is not assignable to type 'Result<Decision, Err>' | a workflow is a synchronous pure decision; async work belongs in the executor shell around it | | An Effect return | Type 'Effect<Decision, never, never>' is not assignable to type 'Result<Decision, Err>' | the workflow returns a value, not an effect handle; the executor runs effects and hands the workflow its input | | never decision channel | Type '...' is not assignable to type 'UninhabitedDecision' | a workflow that can never produce a decision can never succeed | | never error channel | Type '...' is not assignable to type 'UninhabitedError' | a workflow that cannot fail decides nothing; fold the function into its owning module | | An untagged error variant | Type '...' is not assignable to type 'UntaggedError' | an error variant needs a _tag a consumer can dispatch on; declare the errors as S.TaggedError instances | | A single-variant decision channel | Type '...' is not assignable to type 'SingleVariantDecision' | a decision chooses between at least two distinguishable outcomes; one variant is a calculation wearing a decision's shape | | An untagged decision variant | Type '...' is not assignable to type 'UntaggedDecision' | a decision variant needs a _tag a consumer can dispatch on; declare the variants as S.TaggedClass instances | | Decision variants with no shared TypeId | Type '...' is not assignable to type 'UnsharedTypeId' | one decision family carries one TypeId — a Symbol.for brand on every variant class | | A bare decider in a Cell.layer spec | Type '(command: Cmd) => Result<Dec, Err>' is not assignable to type 'WorkflowBrand' | only a Workflow.make value satisfies the decide member; a lambda that skipped make is not a decision a description may run | | A plain interface at the command position | 'Cmd' only refers to a type, but is being used as a value here | the command is constrained on the value, and a declared type produces none — so there is no marker to smuggle |

The two never cases are where the content-vs-filename distinction pays off. Workflow<C, never, E> resolves to UninhabitedDecision and Workflow<C, D, never> to UninhabitedError — interfaces whose only property is required and whose type is the remediation, so the compile error points at the fix:

export interface UninhabitedDecision {
  readonly __WORKFLOW_DECISION_CHANNEL_IS_NEVER__:
    'this workflow can never succeed; give it a decision variant it can return'
}

export interface UninhabitedError {
  readonly __WORKFLOW_ERROR_CHANNEL_IS_NEVER__:
    'this workflow cannot fail, so it decides nothing; give it an error variant or fold the function into its owning module'
}

export interface UntaggedError {
  readonly __WORKFLOW_ERROR_CHANNEL_CARRIES_NO_TAG__:
    'this error carries no _tag the consumer can dispatch on; declare it as an S.TaggedError'
}

export interface SingleVariantDecision {
  readonly __WORKFLOW_DECISION_CHANNEL_HAS_ONE_VARIANT__:
    'this workflow decides one outcome, which is not a decision; add the variant it chooses between, or fold the function into its owning module'
}

export interface UntaggedDecision {
  readonly __WORKFLOW_DECISION_CHANNEL_CARRIES_NO_TAG__:
    'a decision variant carries no _tag the consumer can dispatch on; declare the variants as S.TaggedClass instances'
}

export interface UnsharedTypeId {
  readonly __WORKFLOW_DECISION_VARIANTS_DO_NOT_SHARE_A_TYPE_ID__:
    'the decision variants must share one TypeId — a Symbol.for family brand on each variant class'
}

The never checks use [Decision] extends [never], not Decision extends never: the tuple wrap stops conditional-type distribution, without which never satisfies the conditional vacuously and the marker is never reached.

Result.gen bodies work — and are checked more tightly

A Workflow.make body may be a Result.gen generator, so long as it stays one path: the failing outcome rides an arm of an exhaustive dispatch, and the generator yields the outcome once:

import { Workflow } from '@systemfsoftware/effect-cell-types'
import { Result } from 'effect'
import * as Match from 'effect/Match'
import * as S from 'effect/Schema'

class Decision {}
class Err {
  constructor(readonly reason: string) {}
}
class Input extends S.Class<Input>('Input')({ valid: S.Boolean }) {}

const decide = Workflow.make(
  Input,
  (input): Result.Result<Decision, Err> =>
    Result.gen(function*() {
      const outcome = Match.value(input).pipe(
        Match.when({ valid: false }, () => Result.fail(new Err('invalid input'))),
        Match.orElse(() => Result.succeed(new Decision())),
      )
      return yield* outcome
    }),
)

A conditional yield* — an if or ternary that places a failing yield* on one path — opens a second path inside the decision and is refused by the make-body-purity lint rule; the failure must live on one path of the dispatch (or a first-statement guard that converges immediately), never behind a mid-body branch.

Result.gen infers its error channel from the union of the Results the body yields, so the failing arm above makes the inference exactly Err and the declaration holds. A body with no failing yield infers unknown, which does not satisfy a declared error type — so an unreachable error channel is rejected rather than silently allowed:

const decide = Workflow.make(
  Input,
  (input): Result.Result<Decision, Err> =>
    Result.gen(function*() {
      return new Decision()
    }),
)
// tsc: Type 'Result<Decision, unknown>' is not assignable to type 'Result<Decision, Err>'

If the workflow genuinely cannot fail, the error channel says so — and that is a plain function inside its owning module, not a workflow.

A wrong channel breaks the whole consumer cone

Measured on the real consumer: when decideRestart's error channel was set to never, tsc produced errors in the workflow file, 11 in its property test, and 4 in a downstream executor (supervisor-body.executor.ts). A lint rule flags one file; a type breaks every consumer. The workflow file, its tests, and everything that calls it fail together, at compile time, before anything runs.

Install

pnpm add -D @systemfsoftware/effect-cell-types

A devDependency — consumers mostly use the types (import type), and the one runtime export, make, is an identity function. effect is a peer dependency: bring your own (you already have it).