mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
A reference is a request that outlives its provider: a missing provider or a version that does not fit leaves
it unbound, waiting for the name (failed { waiting }, a new FailureCause kind), local and remote alike. An
attempt parks until its dependencies are live, with no timer and no backoff, woken on every state change of a
dependency; its readyTimeout runs only for its own steps. Requests wait only for the host's publication (the
startup deadlock is gone); load resolves at the first outcomes of its host-lifetime objects. Getters wait through
waiting until their readyTimeout. Registry readers are waiting dependencies, woken at every usable value; a last
good value is dropped when a contribution it was built from leaves. Unbound holds count for keep-alive and are
revoked; retry passes on to failed blockers once per operation (operation id and visited list on the wire); the
dependency deadline is gone and parked_long reports a long wait on a remote attempt.
94 lines
4.6 KiB
TypeScript
94 lines
4.6 KiB
TypeScript
// Errors: what Chord refuses and how a caller tells the cases apart (`ChordError` with `code` and `reason`).
|
|
// Run from packages/chord:
|
|
// node --conditions=source --experimental-strip-types test/next/examples/05-errors.ts
|
|
import { BACKGROUND_CONTEXT } from "../../../src/context/index.ts";
|
|
import { int, local, method, obj, str } from "../../../src/next/contracts/index.ts";
|
|
import { isChordError } from "../../../src/next/data/index.ts";
|
|
import { createHost, defineFacet, type HostError } from "../../../src/next/host/index.ts";
|
|
import { constant } from "../../../src/next/state/index.ts";
|
|
|
|
const context = BACKGROUND_CONTEXT;
|
|
|
|
/** Prints how `work` failed: a ChordError's code, reason and message, or a plain error's message. */
|
|
async function attempt(label: string, work: () => unknown): Promise<void> {
|
|
try {
|
|
await work();
|
|
console.log(`${label}: ok`);
|
|
} catch (error) {
|
|
if (isChordError(error))
|
|
console.log(`${label}: ${error.code}${error.reason ? `/${error.reason}` : ""} - ${error.message}`);
|
|
else console.log(`${label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
}
|
|
|
|
const Echo = local("example.echo", {
|
|
version: 1,
|
|
params: { id: int() },
|
|
members: { id: int(), echo: method(obj({ text: str() }), str()) },
|
|
});
|
|
// Two types that get each other while they activate: a cycle.
|
|
const Ping = local("example.ping", { version: 1, params: {}, members: { n: int() } });
|
|
const Pong = local("example.pong", { version: 1, params: {}, members: { n: int() } });
|
|
const Broken = local("example.broken", { version: 1, params: { id: int() }, members: { n: int() } });
|
|
const Sloppy = local("example.sloppy", { version: 1, params: { id: int() }, members: { n: int() } });
|
|
|
|
const facet = defineFacet((host) => {
|
|
host.serve(Echo, ({ id }) => ({ id: constant(id), echo: async ({ text }) => text }));
|
|
host.serve(Broken, () => {
|
|
throw new Error("broken: missing configuration");
|
|
});
|
|
// Plugins are bundled without type checks; Chord checks the implementation's members when the object activates.
|
|
host.serve(Sloppy, () => ({ n: constant(1), extra: constant(2) }) as never);
|
|
});
|
|
// Ping and Pong are served by a second plugin: types with empty params activate at load.
|
|
const cycle = defineFacet((host) => {
|
|
host.serve(Ping, (_, self) => ({ n: self.get(Pong, {}).n }));
|
|
host.serve(Pong, (_, self) => ({ n: self.get(Ping, {}).n }));
|
|
});
|
|
|
|
// Failed activations are reported to onError; 07-failures.ts shows the other reports.
|
|
const onError = (error: HostError): void => {
|
|
if (error.kind === "activation_failed") console.log(`reported: ${error.object} failed ${error.failure.kind}`);
|
|
else console.log("reported:", error.kind);
|
|
};
|
|
|
|
// A dependency cycle fails every object on it broken, with invalid/cycle naming the chain. Load still resolves: the
|
|
// start barrier waits for each object's first outcome, live or failed.
|
|
const host = createHost({ onError });
|
|
await host.load(
|
|
[
|
|
{ name: "@example/errors", facet },
|
|
{ name: "@example/cycle", facet: cycle },
|
|
],
|
|
context,
|
|
);
|
|
await attempt("get in a cycle", () => host.get(Ping, {}, context));
|
|
const echo = await host.get(Echo, { id: 1 }, context);
|
|
|
|
// Arguments are checked against the contract at the object: wrong types and unknown fields are invalid/args.
|
|
// @ts-expect-error: text must be a string
|
|
await attempt("wrong argument", () => echo.echo({ text: 1 }, context));
|
|
// @ts-expect-error: an unknown field
|
|
await attempt("unknown field", () => echo.echo({ text: "hi", extra: true }, context));
|
|
|
|
// Params select the object and are checked on every get: invalid/params.
|
|
// @ts-expect-error: id must be an integer
|
|
await attempt("bad params", () => host.get(Echo, { id: "one" }, context));
|
|
|
|
// An implementation with a member its contract does not declare fails activation.
|
|
await attempt("unknown member", () => host.get(Sloppy, { id: 1 }, context));
|
|
|
|
// A get needs the provider's version and fingerprint; another declaration of the type does not fit. That is not the
|
|
// caller's fault (a reload may bring the version): the get waits up to its readyTimeout, then rejects with the cause.
|
|
const EchoV2 = local("example.echo", { version: 2, params: { id: int() }, members: { id: int() } });
|
|
await attempt("contract mismatch", () => host.get(EchoV2, { id: 1 }, context, { readyTimeout: 100 }));
|
|
|
|
// A factory that throws fails broken: the get rejects at once with its error, and no object is left behind.
|
|
await attempt("throwing factory", () => host.get(Broken, { id: 1 }, context));
|
|
|
|
// After release, every use of a reference throws released.
|
|
echo.release();
|
|
await attempt("released reference", () => echo.echo({ text: "hi" }, context));
|
|
|
|
await host.close(context);
|
|
await attempt("get after close", () => host.get(Echo, { id: 1 }, context));
|