@peripheries/runtime
v0.27.0
Published
The host's runtime contract for federated component bundles: pinned shared specifiers, the import map, the types of what a cell component receives, and sibling order (`/rank`).
Downloads
5,793
Readme
@peripheries/runtime
The contract between the host and federated component bundles:
pinned: the shared specifiers bundles leave external (react,react/jsx-runtime,react-dom,zustand,@stack/store,@peripheries/ui), their pinned ranges and/_shared/*.jsURLs, and the import map the host serves (/_shared/import-map.jsonis the same payload).contract: what a cell component receives (CellProps,BaseProps,DocumentCellProps,WriteResult,Size,CellContext).config: the cell, page and structure configuration shapes.graph:NodeRecord, the state names, and the traversal configuration.query:QueryDescriptorversion 1 (kind,version,document,variables,result,order_by), and where a livequery_datanode keeps it (evaluation: "live",query_descriptor).validateDescriptoris in@peripheries/renderer/core.catalog: the knowledge a component repo emits (KnowledgeRow,KnowledgeSnippet,KnowledgeLink), its kinds, relations and size budgets.@peripheries/renderer/catalogchecks it.theme: the semantic token contract (SEMANTIC_TOKENS), the cascade layer order the host declares (LAYER_ORDER), and the version 2 org theme (ThemeV2).version: the version a page is read at (VersionContext { versionId, versionKind },VERSION_KINDS) and the read-side shapes a host returns at a version (ResolvedVersionContext,StateAsOf,NodeStateView,Via,ReleaseProblem,RecordedEmbed).page-viewandsubmission: a page as read by its anchor (PageView), a form's submission (SubmissionData), and answers as stored (AnswerValue,FileAnswer).actionsandui-context: the declared actions' fields, and what the host tells an assistant about where the person is.@peripheries/runtime/rank(its own entry): sibling order ongraph_node.rank(compareRank,sortByRank,rankBetween,initialRanks,rankAfterLast,minimalReRank,isValidRank), overfractional-indexing. For hosts and scripts; components get sorted data and don't import it.@peripheries/runtime/script-refs(its own entry, 0.24.0): a template's$script.<name>@<version>references and the pins they become.resolveScriptRef(token, ctx)finds the build by name (at upload);checkScriptPin({ script_id, script_version }, ctx)checks a pin by id and never resolves a name (advisory: a run never gates on it, the database's fetch gate does);checkScriptRefs(json, ctx)resolves every token in a document, or checks only syntax with{ offline: true }. One reachability rule: the row is in the template's org, an org it resolves from, or the Stack org (STACK_ORG_ID); another org's row only at org scope; live; its code and assets under its own org's prefix inproject-assets; never a global row. A pin'sbuild_sha256is the publisher's (64 lowercase hex) or null. Refusals are named (SCRIPT_REF_UNPINNED,SCRIPT_VERSION_UNKNOWN,SCRIPT_REF_UNREADABLE,SCRIPT_REF_OTHER_ORG,SCRIPT_REF_ARCHIVED,SCRIPT_REF_PROJECT_SCOPED,SCRIPT_REF_PATH_OUTSIDE_ORG,SCRIPT_REF_AMBIGUOUSwhen the nearest org tier has two fetchable builds of one name and version); a failed read throws. Reads go through the client you pass (a supabase-js client fits), so row-level security limits what it sees; the module imports none. When the client hasrpc, each tier also asks the database'sscript_pin_lookup(by id,script_pin_get): the rows the org's runs may fetch, including an advisor org's or the Stack org's that the caller can't read itself. Where both answer, the check here decides; two builds of one ref in a tier (two advisors publishing the same name and version) are ambiguous, naming both orgs. The lookups answer the service role and the org's staff, on a session or on a run token of that org; anyone else gets empty reads, which read as unknown by name and unreadable by id. They need a database with the pin lookups (script_pin_lookup,script_pin_get): against one without them, a client withrpcgets a thrown error, never a quiet fallback. The database stays the authority; this is the early refusal.@peripheries/runtime/form-refs(its own entry):script-refs' twin for a template that embeds another template's form,$form.<template_key>@<release>.resolveFormRef(token, ctx)returns the form root a template's release holds (form_root_graph_node_id,template_key,release_id,release_number,org_id, andprocess_id, null when the caller can't read the source template's graph);checkFormRefs(json, ctx | { offline: true })resolves a document. It reads only what a release's readers read, as form_embed does: the form root nodes with the key, their authored placements (never an embedded copy or an instance's), the releases holding them, and of those the composite release with the number, cut and unarchived. So a template's own mandate, which reads other templates' cut releases but not their process rows, resolves like its staff. Any release in the template's own org; in another org's only the library one, when the caller can read the channel (FORM_REF_NOT_LIBRARY). That rule isn't always enforced: a caller that can't read the channel (a derive-grant holder reading any cut release of the template it was granted) resolves any cut release, and form_embed takes it. The org tiers arescript-refs' (own org, the orgs it resolves from, the Stack org; an advisor's template never reaches a client's form), with the same project rule, read from the release. Refusals:FORM_REF_UNPINNED,FORM_VERSION_UNKNOWN,FORM_REF_PROJECT_SCOPED,FORM_REF_ARCHIVED,FORM_REF_AMBIGUOUS,FORM_REF_NOT_LIBRARY; a failed read throws. A release the caller can't read reads as unknown.@peripheries/runtime/template-refs(its own entry): the same for a whole template,$template.<slug>@<release>, where the slug is a live template'smetadata.template_slug.resolveTemplateRef(token, ctx)returnsprocess_id,template_slug,release_id,release_number,scope_node_idandorg_id;checkTemplateRefs(json, ctx | { offline: true })resolves a document. Its release rule, tiers, project rule and refusals areform-refs' (TEMPLATE_REF_UNPINNED,TEMPLATE_VERSION_UNKNOWN,TEMPLATE_REF_PROJECT_SCOPED,TEMPLATE_REF_ARCHIVED,TEMPLATE_REF_AMBIGUOUS,TEMPLATE_REF_NOT_LIBRARY).
Names for bundles. Import the store as @stack/store (useStore,
getStore, selectors) and list it in external and peerDependencies. The
host installs its modules on globalThis.__STACK_RUNTIME__ (RUNTIME_GLOBAL),
which the shims read. Those are the only names: 0.8.0 dropped the old
client-named specifier, global and store hook.
The main entry is types and plain data only. It replaces the component repos' scripts that copy host files by relative path.
Publishing: on npm as @peripheries/runtime, built output only, with
@peripheries/renderer (npm run publish:packages).
Version it with the host: a breaking change here is a breaking change for
every bundle.
