Skip to main content
Every piece of an Effect app runs your code in a handler: block actions and views, reference resolve and search, commands, message actions, events and routes. This page covers what they have in common. The pages for each piece cover their options.

Handler forms

A handler takes one of two forms:
  • A generator function, function* (context) { … }. This is the recommended form: yield* runs an Effect or reads a service, and the value you return is the result.
  • A function that returns an Effect.
A block’s view can also return JSX or a Promise of JSX. See Blocks and views. These two actions do the same thing:
Effect.succeed widens "done" to string, which the state schema rejects, so it needs as const. A generator’s return value keeps its literal types without it.

Keep handlers unwrapped

Don’t wrap a handler in Effect.fn. It hides the context’s type, so args, state and the rest become any and mistakes stop being type errors. reintersect check warns about it with effect-fn-handler. To trace a step, add a span to the Effect inside the handler:

The context

Each handler gets the same context as on the Promise API, with these differences: waitUntil suits work the member doesn’t wait for, such as recording an audit entry:
Every other field (args, state, tallies, actor, options, message, params and so on) has the same type and meaning as on the Promise API.

Schemas

Every schema on the Effect API is an Effect Schema with no requirements, a Schema.Schema<A, I, never>. Passing a Zod, ArkType or Valibot schema is a type error. The SDK decodes props, action arguments, form input, install config and environment values before your handler sees them. Block state is decoded before your view and actions read it and encoded back to JSON before Reintersect stores it. That means block state can use a transforming schema: your code works with a Date and Reintersect stores the ISO string.
An action returns a partial state. The SDK merges it into the current state, then encodes the result. The SDK encodes initialState when you define the block. A value that doesn’t match the schema throws at startup with Block "launch" state does not match its schema. Forms, agent tools and the manifest use JSON Schema, which the SDK builds with JSONSchema.make. A recursive schema, such as one built with Schema.suspend, is rejected when the manifest syncs.

Environment, secrets and install config

Declare what the app reads on createReintersectApp, and declare on each piece what that piece reads:
src/schemas.ts
src/syncTasks.ts
Pass the same declarations to the app with createReintersectApp({ name: "Tasks", env, installConfig: InstallConfig }). Adding a piece that reads an environment value or install config the app doesn’t declare is a type error. See Environment and config for where the values come from. Environment values always arrive as strings, so each schema must decode from a string, such as Schema.NumberFromString. A schema that doesn’t, such as Schema.Number, is a type error that names the key. For an optional value with a default, decode from Schema.UndefinedOr(Schema.String) and give it a jsonSchema annotation, so reintersect check can describe it in the manifest:

Secrets

secret() declares a secret environment value. Your handlers receive it as a Redacted<string>. Read it with Redacted.value(env.TRACKER_TOKEN) where you need it. Everywhere else, including logs and string templates, it prints as <redacted>.