Examples
Examples
Run your own Apps on Cloudflare Workers or celld: installable App Releases, each with its own Durable Object, SQLite database and declared bindings
Examples
Section titled “Examples”examples/standalone ships in the npm package, ready to copy as in the quick
start. The generated API reference and the in-memory example below ship with
them. The rest live in the
Oyster repository:
examples/standaloneis the whole host from the quick start.examples/notes-appis a full App: UI, backend methods and SQLite. Copy it to start an App with a backend.test/standalone.native.mjspacks it, installs it on the standalone Oyster and drives its UI in a browser, on workerd and on celld.examples/notes.jsis a small backend with its own SQLite and no external grants. It reads or writes one note.src/testing/worker.tswithwrangler.test.jsonchosts Instances in a Durable Object with a Worker Loader. The same pre-bundled Worker runs underwrangler dev(workerd) andcelld dev.test/core.mjsexercises replay, state transitions and owner fences in plain Node.- https://oyster.shiit.app/ runs that same host with Fungi’s seven starter Apps.
Drive one Instance in memory
Section titled “Drive one Instance in memory”This self-contained example drives one Instance through in-memory ports. A
disabled Instance never resolves or connects a backend, so no Release is needed.
The request runs in a frame from createInvocationGate, the same gate the
standalone Instances and the Hub open for every call; the example is
docs/home.ts, which the package docs check typechecks.
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"