@plurnk/plurnk-schemes
v1.18.0
Published
Framework + contract for the @plurnk/plurnk-schemes-* URI handler packages.
Readme
plurnk-schemes
Framework + contract for @plurnk/plurnk-schemes-* URI handler packages. Consumed by plurnk-service.
Documentation
SPEC.md— author-facing contract.- Constellation: plurnk-contracts, 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 {
RepresentationPreparationRequest, RepresentationPreparationResult,
SchemeHandler, SchemeManifest, SchemeCtx,
} from "@plurnk/plurnk-schemes";
export default class Foo implements SchemeHandler {
static manifest: SchemeManifest = { /* step 3 */ };
async prepareRepresentation(
{ target, pathname }: RepresentationPreparationRequest,
ctx: SchemeCtx,
): Promise<RepresentationPreparationResult> {
const { content, mimetype } = await acquire(target);
const written = await ctx.entries.write(pathname, {
channels: { body: { content, mimetype } },
});
return written.status >= 400 ? written : { status: 200 };
}
}Core owns READ. A stored scheme needs no READ method; an acquisition scheme
implements scope-blind prepareRepresentation, writes its complete channels,
and lets core select the channel and apply operation-owned semantics. Exact
READ, exact FIND, and COPY/MOVE source selection all reuse that acquisition;
the producer receives the canonical authority for correlation, but none of
their scopes, matchers, or channel choices. The supplied SchemeCtx is already
bound to that authority, so entry capability calls remain pathname-oriented.
Implement only
the remaining delegated methods you support—find, editBatch, send, and
the other optional methods in SchemeHandler. COPY and MOVE are engine-owned
compositions over ctx.entries and editBatch, so plugins do not override
them or author COPY/MOVE effect envelopes. A regional editBatch
returns EditBatchResult with its typed EditBatchReceipt; the engine validates
and projects it.
The optional synchronous attributions(context) hook may return no, one, or
many opaque tags for each provider emission attempt ({§plugin-attribution}).
implements SchemeHandler gives compile-time signature checking. The statement
and path types (FindStatement, SendStatement, UrlPath, etc.) are re-exported
from this package, so you depend on and peer (^1) 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",
writableBy: ["model", "client"],
volatile: false,
modelVisible: true,
lineAnchors: true, // publish stable line anchors without EDIT
textEditScopes: true, // use the standard text-edit capability
glyph: "🦊", // optional client-only display marker
documentation, // deep doc from docs/foo.md, pulled through the skills catalog
};documentation— the deep doc (ops, channels, edge cases) with an exact H2Summary. The consumer materializes it as a pull-ableworker:///_plurnk/plurnk/<name>.mdentry: FIND catalogs the summary and the model READs the body on demand, off the hot path. It is analogous to executor supplementaldetails. 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— optional opaque client display metadata. It is discoverable through the client capability wire and never rendered into model teaching; clients choose fallback, fonts, and theme.
Declare lineAnchors: true when stable textual representations should publish
and accept shared anchors without supporting EDIT. Declare textEditScopes: true
only when editBatch accepts PLURNK's shared
text-coordinate algebra. Core's universal READ projection derives and resolves
any model-facing line anchor; scheme handlers receive numeric
ResolvedEditStatement coordinates only. Delegate those edits to
ctx.entries.operations.editBatch; it owns the anchor recheck and atomic
collision guard.
4. Discoverable references
Put operation contracts and examples in documentation. Its ## Summary orients the model during FIND; the model READs the reference when it needs the details. No second catalog is injected into every packet.
That's the whole contract: declare, implements SchemeHandler, manifest with self-doc. Publish, install, discovered.
Exports
Types
- Manifest:
SchemeManifest(including capabilitytraits,documentation, and client-onlyglyph) andWriterTier; contracts-ownedLoopPolicy/DEFAULT_LOOP_POLICYare re-exported. - Behavior contract:
SchemeHandler, numeric-onlyResolvedEditStatement(also exported asEditStatement), and optionalPacketSectionTransformer(PacketSectionDraft); the remaining re-exported scheme-facing grammar types (PlurnkStatement+ per-op statements +ParsedPath/LocalPath/UrlPath). - Results: universal
SchemeResultplus RFC 9457ProblemDetails, optionalEntryResult/ProposalResult/PassthroughResultauthoring shapes,SchemeResultBase, matcher navigationMatchEvidence, and target-shaped standardEntryFindResultpagination/count metadata. - Capability ctx:
SchemeCtxand its entry, channel, notification, projection, and subscription domains. Entry schemes reuse typed standard operations at canonical workspace addresses.
Helpers (export default class, static methods)
MimetypeClassifier.isBinary/.isJson/.normalizeAutoText(+TEXT_PRIMITIVE_MIMETYPEnamed export) - registry-free mimetype taxonomy and scheme-local helpers. Configured consumers useMimetypes.classify()for handler-aware binary decisions.Slicer.lines/.linesRaw/.textReplacement/.lineMarkerEdit/.lineMarkerEditBatch- universal text-region projection and same-snapshot replacement;Slicer.pagehandles ordered results, and.coversAvailablederives complete coverage from their compactRangeExtent.PathMimetype.resolve(pathname, default, mimetypes)— path-extension mimetype resolver.Matcher.matchAgainstContent(body, content, mimetype, mimetypes)- boolean resource selection overMimetypes.query(glob/regex/jsonpath/xpath), returning locator/exact-regionMatchEvidence.Results.problem/.failure/.assert/.assertReadResult/.assertMatchEvidenceList/.attachInstance/.isEntry/.isProposal/.isPassthrough/.isErrorStatus- RFC 9457 result builders, validators, durable-occurrence attachment, and guards.PacketSections.assertDrafts(value, subject?)— validates the exact tokenless section-draft list returned by a packet transformer.SchemeDiscovery.discover({ cwd? })— scope-agnosticnode_modulesscan forplurnk.kind:"scheme"packages (trust-gated, fail-hard on prefix collision); returns descriptors plus canonical static attribution lists for represented packages (SPEC §6, {§plugin-attribution}).
SchemeCtx is the stable semantic API for trusted in-process schemes, not a
sandbox. The consumer injects its implementation; database layout and private
service modules remain outside the compatibility contract.
Consumers pass only this contract to handlers. Consumer-owned adapters inject
their daemon collaborators separately instead of extending the context.
Tests
test:lint, test:unit.
