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

@schema-reflection/algebra

v0.1.0

Published

Schema-native relations, graph extraction, and validation for Effect Schema.

Readme

Schema Algebra

Schema Algebra is a standalone schema-native programming toolkit for Effect Schema.

Effect Schema already validates values. Schema Algebra uses those same schema nodes as the place to declare reusable IDE semantics: relation metadata today, and paths, traversal, constraints, lenses, projections, diffs, patches, generation, fingerprints, migrations, previews, and agent-safe edits over time.

This package is intentionally UI-free. It does not depend on React, CodeMirror, the Schematics server, or browser-only APIs. It should stay usable in Node, browsers, tests, CLIs, and agents.

Status

Implemented:

  • Relation.id, Relation.ref, and Relation.refs schema combinators
  • Relation.derivedId for definitions derived from object values
  • Relation.pathRef and Relation.pathRefs for path-like references
  • typed relation edges on references
  • relation annotation storage on Effect Schema AST nodes
  • relation graph extraction from a schema and decoded value
  • duplicate ID validation
  • unresolved reference validation
  • scoped references through parent definitions or sibling fields
  • relative scoped references such as ../form from nested values
  • relation diagnostics with structured paths and relation metadata

Planned:

  • canonical Path helpers
  • reusable Traversal over Effect Schema ASTs and values
  • shared Annotation utilities
  • field-scoped Constraint diagnostics
  • immutable Lens and Patch primitives
  • schema-aware Projection, Diff, Generate, and Fingerprint modules
  • workspace integration through @schematics/core
  • IDE and agent features derived from the same algebra graph

The package was renamed from the earlier schema-relations experiment. The current code is Phase 1 of the broader algebra plan.

Development

Requires Node.js 22.18+ and pnpm 10.20.0.

# From the Schema Reflection repository root
pnpm install
pnpm --filter @schema-reflection/algebra test
pnpm --filter @schema-reflection/algebra typecheck
pnpm --filter @schema-reflection/algebra build
pnpm --filter @schema-reflection/algebra pack

To use a local build, install the archive produced by pnpm pack. Runtime and TypeScript exports both resolve to dist; building does not require the Schematics workspace.

pnpm add /path/to/schema-reflection-algebra-0.0.0.tgz [email protected]

Effect is pinned to 4.0.0-beta.68, matching the source package.

Documentation site

Run pnpm docs:dev to preview the VitePress site. Run pnpm docs:check and pnpm docs:build to check the examples and produce the static site in build/. The homepage uses Twoslash for TypeScript type hovers, matching the Triplex docs.

Quick Start

Declare IDs and references directly on schema fields:

import { Schema } from "effect";
import { Relation } from "@schema-reflection/algebra";

const ActionSchema = Schema.Struct({
  id: Relation.id("Action"),
  kind: Schema.Literals(["email", "task", "webhook"]),
  label: Schema.String,
});

const WorkflowSchema = Schema.Struct({
  id: Relation.id("Workflow", { display: "name" }),
  name: Schema.String,
  actionIds: Relation.refs("Action"),
});

const WorkspaceSchema = Schema.Struct({
  actions: Schema.Array(ActionSchema),
  workflows: Schema.Array(WorkflowSchema),
});

Build a graph:

const graph = Relation.graph(WorkspaceSchema, {
  actions: [{ id: "send-email", kind: "email", label: "Send email" }],
  workflows: [{ id: "onboarding", name: "Onboarding", actionIds: ["send-email"] }],
});

graph.definitions;
// [
//   { type: "Action", id: "send-email", path: ["actions", "0", "id"] },
//   { type: "Workflow", id: "onboarding", path: ["workflows", "0", "id"], display: "Onboarding" },
// ]

graph.references;
// [
//   { target: "Action", id: "send-email", path: ["workflows", "0", "actionIds", "0"] },
// ]

Validate the same graph:

const diagnostics = Relation.validate(WorkspaceSchema, {
  actions: [],
  workflows: [{ id: "onboarding", name: "Onboarding", actionIds: ["missing"] }],
});

diagnostics;
// [
//   {
//     severity: "error",
//     code: "unresolved-ref",
//     path: ["workflows", "0", "actionIds", "0"],
//     message: 'Unresolved Action reference "missing"',
//     relation: { target: "Action", id: "missing", ... }
//   }
// ]

The top-level names are also exported for compatibility:

import { buildRelationGraph, validateRelations } from "@schema-reflection/algebra";

API

Relation.id(type, options?)

Declares that a string field defines an entity ID.

const UserSchema = Schema.Struct({
  id: Relation.id("User"),
});

Options:

  • display: a path inside the containing object used as a human-readable label.
  • scope: a relation scope. Use Relation.parent(type) for nested IDs, or Relation.path(path) for a value elsewhere in the root value.

Example with display text:

const WorkflowSchema = Schema.Struct({
  id: Relation.id("Workflow", { display: "name" }),
  name: Schema.String,
});

Relation.ref(target, options?)

Declares that a string field references an entity ID.

const WorkflowSchema = Schema.Struct({
  actionId: Relation.ref("Action"),
});

Options:

  • scope: explicit scope resolved through Relation.parent or Relation.path.
  • scopedBy: path inside the nearest object whose string value determines the reference scope.

Relation.refs(target, options?)

Declares an array of references.

const WorkflowSchema = Schema.Struct({
  actionIds: Relation.refs("Action"),
});

This is equivalent to:

Schema.Array(Relation.ref("Action"));

Relation.pathRef(target, options?)

Declares that a string field references a path-like ID. Validation behavior is the same as Relation.ref; the graph records valueKind: "path" so consumers can distinguish path references from ordinary IDs.

const MappingEntrySchema = Schema.Struct({
  formField: Relation.pathRef("FormField", {
    scopedBy: "../form",
    edge: "maps_form_field",
  }),
});

scopedBy can point at a sibling path or use .. segments to resolve from an ancestor object. This stays value-relative; Schema Algebra does not know about files or workspaces.

Relation.pathRefs(target, options?)

Declares an array of path-like references.

const RuleSchema = Schema.Struct({
  facts: Relation.pathRefs("AttributePath"),
});

Relation.derivedId(schema, type, options)

Annotates an object schema as defining an ID derived from one of its own fields. This is useful when the identifier is not literally named id, such as form field paths or generated PDF field names.

const FormFieldSchema = Relation.derivedId(
  Schema.Struct({
    path: Schema.String,
    label: Schema.String,
  }),
  "FormField",
  {
    id: "path",
    scope: Relation.parent("Form"),
    display: "label",
  },
);

Derived definitions are still ordinary graph definitions. They participate in duplicate detection, unresolved-reference validation, and graph queries.

Typed edges

References can carry an edge label.

const PolicySchema = Schema.Struct({
  formId: Relation.ref("Form", { edge: "requires" }),
});

Edges do not affect validation. They make the graph more useful for impact analysis, explainability, and agent queries.

Relation.parent(type)

Uses the nearest enclosing definition of type as the relation scope.

const FieldSchema = Schema.Struct({
  id: Relation.id("Field", { scope: Relation.parent("Form") }),
  label: Schema.String,
});

const FormSchema = Schema.Struct({
  id: Relation.id("Form"),
  fields: Schema.Array(FieldSchema),
});

In this example, field IDs are scoped to their containing form ID.

Relation.path(path)

Uses a value at a root-relative path as the relation scope.

const DocumentSchema = Schema.Struct({
  workspaceId: Schema.String,
  localId: Relation.id("Document", { scope: Relation.path("workspaceId") }),
});

Relation.key(path)

Normalizes a string path or tuple path into the internal path representation.

Relation.key("steps.0.actionId"); // ["steps", "0", "actionId"]
Relation.key(["steps", "0", "actionId"]); // ["steps", "0", "actionId"]

Relation.graph(schema, value)

Returns a RelationGraph:

interface RelationGraph {
  readonly definitions: readonly RelationDefinition[];
  readonly references: readonly RelationReference[];
}

Definitions include:

  • type: relation type, such as "Action"
  • id: string ID value
  • path: path to the ID field
  • scope: optional resolved scope
  • display: optional display string

References include:

  • target: relation target type
  • id: referenced ID value
  • path: path to the reference field or array element
  • scope: optional resolved scope
  • scopedBy: optional path used to resolve the scope

Relation.validate(schema, value)

Returns structured diagnostics:

interface RelationDiagnostic {
  readonly severity: "error" | "warning" | "info";
  readonly code: "duplicate-id" | "unresolved-ref" | "invalid-relation-value";
  readonly path: readonly string[];
  readonly message: string;
  readonly relation: RelationDefinition | RelationReference;
}

Current validation checks:

  • duplicate IDs with the same type, ID, and scope
  • references that cannot resolve to a definition
  • relation annotations attached to non-string values

Scoped Relations

Scoped relations are useful when IDs are only unique inside a parent entity.

const FieldSchema = Schema.Struct({
  id: Relation.id("Field", { scope: Relation.parent("Form") }),
  label: Schema.String,
});

const FormSchema = Schema.Struct({
  id: Relation.id("Form"),
  fields: Schema.Array(FieldSchema),
});

const PolicySchema = Schema.Struct({
  id: Relation.id("Policy"),
  formId: Relation.ref("Form"),
  requiredFieldIds: Relation.refs("Field", { scopedBy: "formId" }),
});

For each requiredFieldIds entry, the nearest object is the policy. The scopedBy: "formId" option reads policy.formId and validates the field reference against field definitions scoped to that form.

This supports invariants like:

Policy.requiredFieldIds[*]
  references Field.id
  scoped through Policy.formId

Effect Schema Traversal Notes

Relation extraction currently walks these Effect Schema AST shapes:

  • type literals / structs
  • tuple and array-like tuple rest nodes
  • unions
  • refinements
  • transformations
  • suspends

The traversal is intentionally private for now. Phase 2 will promote a stable Traversal module so other algebra features do not need to reimplement AST walking.

Roadmap

See the roadmap for proposed modules and Schematics integration.

Origin and compatibility

Extracted from bjacobso/schematics (packages/algebra, commit b76a5312fb4feb4a9ed83d481d2749355b72342c). The runtime source is unchanged; the existing tests are included with a small type-safety fix for optional AST annotations. The relation annotation key remains @schematics/algebra/relation so schemas annotated by the original package remain compatible. The original MIT license and attribution are retained.