pi-mono/packages/chord/test/examples/49-plugin-api/main.ts
Mario Zechner e9c0ff4d84 feat(chord): Chord v2 is the package's root
src/next is src (the main export), src/next-node is src/node with short subpaths (/http, /workers, /plugins,
/plugins/runtime, /app, /surfaces, /web, /react, /tui, /tui/launcher); the foundations get /json and /watch;
Chord v1 moves to src/old (/old, /old/node) until p2 runs on v2; tests and docs move likewise (test/, docs/,
test/old, docs/old). Durable and env import the foundations from their subpaths.
2026-11-02 22:36:55 +00:00

80 lines
4 KiB
TypeScript

// The app's versioned API (slice 12 §1, decision "Plugins import only the app's versioned API"). Plugins import their
// host only through the app's API modules, a version in the path: `@acme/app/v3` (shared by every kind) and its
// per-kind modules (`@acme/app/v3/server`, `/v3/web`); the app lists the versions it still provides (`api.versions`)
// and each kind's modules (`kinds.<kind>.hostPackages`). Here (`plugins/`, the API in `app/`): a v3 plugin and a v2
// plugin both load (v2 is a thin layer over v3); refused at build, each with its error: a v1 plugin (no longer
// provided), a facet mixing v2 and v3, a direct import of Chord, a server facet importing the web's module.
// Run from packages/chord:
// node --conditions=source --experimental-strip-types test/examples/49-plugin-api/main.ts
import { fileURLToPath } from "node:url";
import { BACKGROUND_CONTEXT } from "../../../src/context/index.ts";
import { createHost, defineFacet } from "../../../src/host/index.ts";
import { createPlugins, evaluateFacet } from "../../../src/node/plugins/index.ts";
import * as v2 from "./app/v2.ts";
import * as v3server from "./app/v3/server.ts";
import * as v3web from "./app/v3/web.ts";
import * as v3 from "./app/v3.ts";
const context = BACKGROUND_CONTEXT;
const root = fileURLToPath(new URL("./plugins/", import.meta.url));
// 1. The component with the app's API: its name and versions (newest first), and per kind the modules its facets may
// import. Chord is no host package of plugins: the API re-exports what Acme supports of it.
const plugins = createPlugins(
{
roots: [{ path: root, required: false }],
api: { name: "@acme/app", versions: ["v3", "v2"] },
kinds: {
server: { platform: "node", hostPackages: ["@acme/app/v3", "@acme/app/v3/server", "@acme/app/v2"] },
web: { platform: "browser", hostPackages: ["@acme/app/v3", "@acme/app/v3/web", "@acme/app/v2"] },
},
onError: (error) => console.log("plugins:", error.kind),
},
context,
);
// 2. The server's modules (this process's instances) and its own start-data facet, base code: it serves the
// `Greetings` registry, composing the lines in plugin-name order.
const modules = { "@acme/app/v3": v3, "@acme/app/v3/server": v3server, "@acme/app/v2": v2 };
const core = defineFacet((host) => host.serve(v3.Greetings, { compose: (table) => table.map(({ value }) => value) }));
const host = createHost({ onError: () => {} });
const loaded = await plugins.load(
host,
{ kind: "server", core: [{ name: "@acme/core", facet: core }], modules },
context,
);
// 3. Who loaded, and who was refused (each build error names the file, line and rule).
console.log("1. greetings:");
for (const line of (await host.registry(v3.Greetings, context)).value) console.log(` ${line}`);
console.log("2. refused at build:");
for (const { plugin, error } of loaded.failed) {
const [, ...errors] = (error as Error).message.split("\n");
console.log(` ${plugin}: ${errors.join("; ")}`);
}
// 4. The web kind provides `/v3/web`: the panel plugin's web facet builds, and a page (here a host in this process,
// given the web's modules) evaluates and loads it.
const built = await plugins.build(["@acme/panel"], context);
console.log("3. the panel plugin:", built.facets.map((facet) => `${facet.facet} ${facet.kind}`).join(", "));
const web = built.facets.find((facet) => facet.facet === "web" && facet.kind === "script");
if (web?.kind === "script") {
const webModules = { "@acme/app/v3": v3, "@acme/app/v3/web": v3web, "@acme/app/v2": v2 };
const panels = defineFacet((h) =>
h.serve(v3web.Panels, { compose: (table) => table.map(({ value }) => value.title) }),
);
const page = createHost({ onError: () => {} });
const facet = evaluateFacet(web, { hostPackages: webModules, plugin: built.plugins["@acme/panel"]! });
await page.load(
[
{ name: "@acme/page", facet: panels },
{ name: "@acme/panel", facet },
],
context,
);
console.log(" panels on the page:", (await page.registry(v3web.Panels, context)).value.join(", "));
await page.close(context);
}
await host.close(context);
plugins.close();