Skip to content

Oyster

Run your own Apps on Cloudflare Workers or your own server. Each App gets its own database, and you choose which version is live.

  • @fungi.computer/oyster
  • v0.6.0
  • MIT

Fastest path

  1. Install

    Add it to your project with npm, pnpm, Yarn or Bun.

    TERMINAL
    npm install @fungi.computer/oyster react react-dom wrangler
  2. Apply owner state

    A disabled Instance refuses backend requests without resolving a Release.

    home.ts
    import * as Effect from "effect/Effect";
    import * as Schema from "effect/Schema";
    import {
    AppInstanceError,
    createAppInstance,
    instanceIds,
    ownerStateSchema,
    type Instance,
    } from "@fungi.computer/oyster";
    import {
    APP_INVOCATION_CEILING_MS,
    createInvocationGate,
    invocationFactsSchema,
    } from "@fungi.computer/oyster/invocation";
    const instanceId = instanceIds.instance.generate();
    let stored: Instance | null = null;
    const instance = createAppInstance({
    instanceId,
    read: () => Effect.succeed(stored),
    write: (record) =>
    Effect.sync(() => {
    stored = record;
    }),
    resolve: () => Effect.fail(new Error("No Releases in this example")),
    abort: () => Effect.void,
    purge: () => Effect.void,
    connect: () => Effect.fail(new Error("No backend in this example")),
    });
    const selection = {
    instanceId,
    owner: { kind: "installation", id: "installation_example" },
    appId: "app_example",
    releaseId: "rel_example",
    authorityVersion: 2,
    } as const;
    const disabled = Schema.decodeUnknownSync(ownerStateSchema)({
    ...selection,
    status: "disabled",
    });
    const receipt = await Effect.runPromise(instance.apply(disabled));
    console.log(receipt.status); // "disabled"
    // A request runs inside an invocation frame: the caller facts the host admitted,
    // a deadline, and an abort signal the gate owns.
    const gate = createInvocationGate<null>({
    abortGuest: () => Promise.resolve(),
    close: () => Promise.resolve(),
    });
    const facts = Schema.decodeUnknownSync(invocationFactsSchema)({
    instanceId,
    selection,
    artifactDigest: `sha256:${"0".repeat(64)}`,
    subject: {
    kind: "call",
    context: {
    actor: { kind: "member", memberId: "member_example" },
    computerId: null,
    },
    },
    grants: [],
    ancestry: [],
    deadlineAt: Date.now() + APP_INVOCATION_CEILING_MS,
    });
    const refused = await gate.run(facts, null, (frame) =>
    Effect.runPromise(
    Effect.result(instance.fetch(frame, new Request("https://app.test/"))),
    ),
    );
    if (refused._tag === "Failure" && refused.failure instanceof AppInstanceError)
    console.log(refused.failure.code); // "instance_inactive"