Let an artifact describe its own boundary
Bundle a supported input or output contract with the program data that uses it. An offline consumer can inspect the declared shapes without a lookup into your running application.
Contracts as data
@schema-reflection/core
Experimental · source onlyA TypeScript type disappears when the program is built. A portable contract can travel with the data and still tell the next consumer what is valid.
01 Why it matters
Your application has a Candidate schema, but a stored artifact, an offline inspector, or a different runtime cannot import the application module that defines it. A generated type only helps code compiled against that module. It does not give a loaded document a contract the consumer can inspect and validate at runtime.
Core describes a deliberate portable subset of Effect Schema in versioned JSON. MetaSchema can encode that subset, check a loaded description, reconstruct a working schema, and validate values against it. Unsupported semantics fail with a diagnostic rather than quietly turning into a weaker contract.
02 What it unlocks
Bundle a supported input or output contract with the program data that uses it. An offline consumer can inspect the declared shapes without a lookup into your running application.
MetaSchema.validate enforces the portable contract, including exact object keys, and returns a frozen JSON snapshot. A payload with an unexpected admin field need not be accepted just because its known fields look valid.
Pure expression data can refer to declared inputs and local bindings and express supported Boolean operations. Definition loaders can check those references instead of trusting a string that merely resembles a path.
03 How to use it
Start with one contract that needs to cross a boundary or live longer than a process. Confirm every schema node is supported, exercise an encode/decode/validate round trip, and keep richer application behavior outside the artifact.
Start with plain JSON-shaped domain data: supported primitives, arrays, structs, optional keys, literals, and unions. Keep runtime-specific transforms and callbacks outside that contract.
Use MetaSchema.encode on the supported Effect Schema. On the receiving side, MetaSchema.decode checks the envelope, version, and semantic constraints before you reconstruct or use it.
Use MetaSchema.validate for the portable contract's strict value boundary. If you use MetaSchema.toSchema and native Effect decoding instead, select the native decoding options you need explicitly.
The example below describes the experimental source API. This package is not available on npm yet.
Core is experimental and source-only, currently using Effect 4.0.0-rc.115. It is separate from the Schematics workspace's @schematics/core runtime package.
import { Schema } from "effect"
import { MetaSchema } from "@schema-reflection/core"
const Candidate = Schema.Struct({
id: Schema.String,
status: Schema.Literals(["pending", "approved"]),
note: Schema.optionalKey(Schema.String),
})
const json = MetaSchema.encode(Candidate) // send it anywhere
const contract = MetaSchema.decode(json) // checked on arrival
const schema = MetaSchema.toSchema(contract) // a working schema again
MetaSchema.validate(contract, { id: "c1", status: "pending" })Interactive simulation loads when this panel comes into view.
The interactive panel is a Foldkit/Foldworks simulation of the behavior, not an execution of this package. The definition above is an API excerpt; supply your application's domain values and contracts.
04 In practice
An artifact carries a Candidate contract with an id, an optional note, and a pending/approved status. Another consumer needs to inspect and validate it without importing the original application.
Encode the supported schema into a versioned contract and store it with the artifact.
Decode the description and reject an unsupported format or semantic node before trusting it.
Validate a candidate against the loaded contract. An archived status or an unexpected key produces a diagnostic rather than a silent downgrade.
05 How it composes
Each library owns one kind of meaning. Combine the ones your application needs, with an explicit boundary between their jobs.
The experimental portable predicate API can carry an input shape and expression together using core. A generic nested-leaf rule still requires its host's leaf interpreter.
The experimental portable action API uses contracts for input, state, events, and results. The published legacy serialize API does not bundle attached schemas, so choose a compatible artifact format.
Workflow definitions use portable shapes and expressions to check recorded inputs, step outputs, and branch decisions. Core supplies those contracts; the workflow host supplies persistence and execution.
Both begin with domain schemas, but portable shape and relation meaning are distinct. MetaSchema rejects unsupported annotations rather than preserving algebra's relationship metadata. Keep a relation declaration or adapter alongside the portable contract.
A future filesystem layout could use portable contracts for inspecting stored definitions. The proposed filesystem package currently routes native Effect schemas; serializable layouts and constraints are design work.
These are application composition patterns, not a promise of automatic adapters. Align Effect peers and artifact formats before combining runtimes; source prototypes can differ from published releases.
See the family's approval scenario →06 Where it stops
Refinements, transformations, defaults, annotations, recursion, tuples, and non-JSON values are outside the current subset. Core identifies unsupported schema paths; it does not silently drop those semantics.
The artifact formats are experimental. A JSON contract is not a live database schema migration or an arbitrary Effect program. Keep the producer, consumer, and format versions aligned.
All libraries are pre-1.0. Pin exact versions and plan for changes to APIs and artifact formats.
Continue with the piece next to it