@plurnk/plurnk-schemes
v0.31.4
Published
Framework + contract for the @plurnk/plurnk-schemes-* URI handler family.
Readme
plurnk-schemes
Framework + contract for @plurnk/plurnk-schemes-* URI handler packages. Consumed by plurnk-service.
Documentation
SPEC.md— author-facing contract.- Constellation: plurnk-grammar, plurnk-mimetypes, plurnk-providers, plurnk-execs.
Write a scheme
Ship a scheme by publishing a package — under any scope (@acme/whatever; discovery keys on plurnk.kind, not the @plurnk scope) — that declares itself and default-exports a SchemeHandler. Install it and it lights up; there is no first-party allow-list (scope-agnostic discovery, SPEC §6).
1. Declare in package.json
{
"plurnk": { "kind": "scheme", "name": "foo" }
}plurnk.name is the URI prefix you claim (foo://…). The consumer's scope-agnostic node_modules scan registers you by it; two packages claiming one prefix fail-hard.
2. Default-export a SchemeHandler
import type {
SchemeHandler, SchemeManifest, SchemeCtx, SchemeResult, ReadStatement,
} from "@plurnk/plurnk-schemes";
export default class Foo implements SchemeHandler {
static manifest: SchemeManifest = { /* step 3 */ };
async read(statement: ReadStatement, ctx: SchemeCtx): Promise<SchemeResult> {
/* … reach the substrate ONLY through ctx capabilities (SPEC §3.bis, §5) … */
}
}Implement only the op methods you support — read / find / edit / copy / move / send / … — all optional; the engine calls handler[op.toLowerCase()](statement, ctx) and returns 501 for any op you omit. implements SchemeHandler gives you compile-time signature checking. The statement + path types (ReadStatement, SendStatement, UrlPath, …) are re-exported from this package, so you depend on and exact-pin only @plurnk/plurnk-schemes — grammar rides underneath.
3. Declare the manifest — including self-doc
import { readFile } from "node:fs/promises";
// Deep doc lives in docs/foo.md (convention); loaded at module init.
const documentation = await readFile(new URL("../docs/foo.md", import.meta.url), "utf-8");
static manifest: SchemeManifest = {
name: "foo",
channels: { body: "text/markdown" },
defaultChannel: "body",
category: "data",
scope: "session",
writableBy: ["model", "client"],
volatile: false,
modelVisible: true,
glyph: "🦊", // display icon; omit → the name is shown
example: "READ(foo://thing/42)", // terse hot-path one-liner, rendered every turn
documentation, // deep doc from docs/foo.md, pulled at plurnk://docs/foo.md
};example— the scheme's terse hot-path one-liner, rendered in the live catalogue every turn (like an execs runtime'sexample). Keep it to one canonical usage line; depth goes indocumentation. Omit → not advertised.documentation— the deep doc (ops, channels, edge cases). The consumer materializes it as a pull-ableplurnk://docs/<name>.mdentry the model READs on demand — off the hot path. MirrorsExecInfo.documentation. Convention: keep it in adocs/<name>.mdfile (root) and load it at module init with the snippet above —../resolves the same fromsrc/(test) anddist/(built); adddocs/**/*tofiles. A missing file fails-hard at import.glyph— a display icon (emoji / nerdfont). Omit it and the schemenameis rendered in its place.
4. Self-doc: terse pushes, depth pulls
example/glyph render every turn — keep them terse. documentation is the deep prose; the consumer materializes it at plurnk://docs/<name>.md for the model to READ on demand. Don't dump prose into example (it floods the hot path) — put it in documentation.
That's the whole contract: declare, implements SchemeHandler, manifest with self-doc. Publish, install, discovered.
Exports
Types
- Manifest/flags:
SchemeManifest(incl.example/documentation/glyphself-doc),SchemeFlagAffinity,WriterTier,LoopFlags,DEFAULT_LOOP_FLAGS. - Behavior contract:
SchemeHandler+ optionalPacketSectionTransformer(PacketSection); the re-exported scheme-facing grammar types (PlurnkStatement+ per-op statements +ParsedPath/LocalPath/UrlPath). - Result families:
SchemeResult/EntryResult/ProposalResult/PassthroughResult/SchemeResultBase/TelemetryEvent. - Capability ctx:
SchemeCtx+EntryCaps/ChannelCaps/TagCaps/NotifyCaps/SubscriptionCaps/CrossSchemeCaps, plusEntryData/ChannelState/SubscriptionHandle/ProposalAware.
Helpers (export default class, static methods)
SchemeResolver.forLoop(handlers, flags)— active-scheme resolution under loop flags.MimetypeClassifier.isBinary/.isLineNavigable/.isJson/.normalizeAutoText(+TEXT_PRIMITIVE_MIMETYPEnamed export) — mimetype classification.Slicer.lines/.linesRaw/.jsonItems/.lineMarkerEdit/.jsonItemEdit—<L>slicing + structural EDIT.PathMimetype.resolve(pathname, default, mimetypes)— path-extension mimetype resolver.Matcher.matchAgainstContent(body, content, mimetype, mimetypes, baseLine?)— body-matcher dispatch overMimetypes.query(glob/regex/jsonpath/xpath).Results.error/.logCoordinate/.isEntry/.isProposal/.isPassthrough/.isErrorStatus— result builders + guards.SchemeDiscovery.discover({ cwd? })— scope-agnosticnode_modulesscan forplurnk.kind:"scheme"packages (trust-gated, fail-hard on prefix collision); returns descriptors for the consumer to register (SPEC §6).
The capability ctx (SchemeCtx) is the DB-free authoring surface for siblings — interfaces only; plurnk-service injects the db-backed impl (see SPEC §3.bis). The db-backed implementations themselves (CRUD primitives, entry-op handlers, channel writes, subscription registry) stay in plurnk-service.
Tests
test:lint, test:unit.
