Skip to content

http ​

Function, in @jr2/orchestrator/src/http-effect.ts. It is an export of @jr2/orchestrator, on the Authoring surface page, in Effects.

ts
function http<
  S extends z.ZodType | undefined = undefined,
  const U extends HttpDefinition<S>["url"] = string,
  const A extends HttpDefinition<S>["auth"] = undefined,
  const H extends HttpDefinition<S>["headers"] = undefined,
>(
  def: HttpDefinition<S> & { url: U; auth?: A; headers?: H },
): EffectLogic<HttpInput<S>, HttpOutput> & OpenOf<HttpOpenParts<U, A, H>>

Make an http Effect: a call to a URL, with Effect secrets that the Agent never reads. Put it in actors, and invoke it by its key: actors: { comment: http({ ... }) }, then invoke: { src: "comment" }.

It runs in the Instance Effector, which jr2 up deploys when a Machine has an http Effect. With body: file(...), it runs in the Sandbox Effector of the Workspace whose body invokes it. The Effector makes the call. The Orchestrator holds no secret.

Every outcome is output (HttpOutput): response, unreachable, refused or unknown. A 4xx or 5xx status is output too. jr2 sends the call at most one time, unless you mark it idempotent. When its state exits while the call runs, jr2 stops waiting for it. The Effector can still send it.

Each field of HttpDefinition tells what it refuses. createMachine also refuses an Effect written directly in an invoke: machine "<id>": state "<state>" invokes an Effect inline — a Part is a key of actors, so it has a name that the ledger, the report and customize() share: actors: { <name>: http(...) }, then invoke: { src: "<name>" }.

Parameters

  • def: The definition. Each field tells what it does.

    • method: HttpMethod

      The HTTP method: GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS. It refuses another: http({ method: "<method>" }): the method is one of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS.

    • url: string | Text | typeof open

      The url: http:// or https://, then a literal host. No input, param or Effect secret can change where the call goes. An Effect secret can be only in the path: text`https://api.telegram.org/bot${secret("tg")}/sendMessage`.

      Or open: a packaged Machine that cannot know the host leaves the url Open, and the composer binds it one time with customize(machine, { effects: { <slot>: { url } } }).

      It refuses:

      • a scheme or host that is not literal: http({ url }): the url is an http:// or https:// url whose scheme and host are literal — a secret may sit only in its path, after a "/": text`https://api.example.com/bot${secret("tg")}/send` (ADR-0068)
      • an Effect secret after ? or #: http({ url }): secret("<name>") sits in the url's query or fragment — a secret may sit only in its path; send it in `auth` or a header instead: headers: { "x-api-key": text`${secret("...")}` } (ADR-0068)
      • a host that is not a host: http({ url }): "<scheme>://<host>" is not a host
      • a user name or password in the url: http({ url }): the url carries userinfo — use `auth`
    • headers (optional): Readonly<Record<string, string | Text>>

      Headers that are the same for each run. Each name is lowercase. A value is a string, or a text with an Effect secret.

      It refuses:

      • a name that is not lowercase, or that frames or routes the request (host, content-length, transfer-encoding, connection, upgrade, te, trailer, keep-alive, proxy-*): http({ headers }): "<name>" is not a header an Effect sets — a lowercase name that does not frame or route the request (ADR-0068)
      • an authorization header beside auth: http({ headers }): "authorization" is set by `auth` — write one of them
    • auth (optional): { readonly bearer: Secret; } | { readonly basic: { user: Text; password: Text; }; } | Text

      The Authorization header: bearer(secret("x")), basic(user, password), or a text that is the whole value.

    • input (optional): S

      The zod schema of the input of the Effect: what the input of the invoke, or fromPick, gives it. jr2 parses the input with it before the call. When it starts, input that does not agree with it makes the Effect fail (its onError route): http "<slot>": its input does not match the definition's input schema — <path>: <message>, one <path>: <message> for each problem, joined by ; .

    • request (optional): (input: S extends z.ZodType ? z.infer<S> : void) => HttpRequestParts

      A function that makes the part of the request that changes for each run, from the input: path segments, and a json or text body. It cannot change the host, a header or an Effect secret. Do not write it beside body.

    • body (optional): FileBody<S>

      A Worktree file as the body: file(path, { slot?, segments?, contentType? }). The Effect then runs in the Sandbox Effector of its Workspace. Do not write request beside it: the segments of file are the path. It refuses another body: http({ body }): a body is file(path, { slot?, segments?, contentType? }) — json and text come from `request` .

    • timeout (optional): Duration

      How long jr2 waits for the answer: a number and a unit, such as "30s" or "2m". Default "30s". With no answer in that time, the output is unknown. It refuses a value that is not a positive duration: http: timeout "<timeout>" is not a positive duration (like "30s", "2m").

    • maxBody (optional): number

      The largest number of bytes of a text response body that the output keeps. Default 65536. When jr2 cuts the body, truncated is true. It refuses a value that is not a positive whole number: http({ maxBody }): a positive number of bytes.

    • idempotent (optional): boolean

      true when it is safe to send the call two times: it has its own idempotency key, or it only reads. Default false. After an Orchestrator restart, jr2 cannot always tell if the call went out. Then it sends an idempotent call again, and gives any other call the output unknown.

Returns: An Effect, for actors.

Example

ts
import { z } from "zod";
import { bearer, http, jr2Setup, secret } from "@jr2/orchestrator";

const comment = http({
  method: "POST",
  url: "https://jira.example.com/rest/api/2/issue",
  auth: bearer(secret("jira")),
  input: z.object({ key: z.string(), body: z.string() }),
  request: ({ key, body }) => ({ path: [key, "comment"], json: { body } }),
});

export const machine = jr2Setup({
  types: {} as { context: { key: string }; input: { key: string } },
  actors: { comment },
}).createMachine({
  id: "comment",
  context: ({ input }) => input,
  initial: "commenting",
  states: {
    commenting: {
      invoke: {
        src: "comment",
        input: ({ context }) => ({ key: context.key, body: "The build passed." }),
        onDone: "done",
      },
    },
    done: { type: "final" },
  },
});

See also

The docs of Kit version 0.5.1. Each page is also at its .md URL.