Skip to content

API

ogygia/plugin

The API for writing a plugin's server realm, covering its hooks and what it can learn about a request.

On this page

This page is generated from the package’s own .d.ts files on every build, so the signatures, descriptions and examples below always match the source.

import { … } from 'ogygia/plugin';

Exports deferredRegion · defineServer · late_chunk · late_slot · og_html_region · open_late_scope · region · register_route_config · take_late_regions · LATE_BOOT_SCRIPT · STORED_DOCUMENT_META · HandleContext · LateRegion · PluginCapabilities · RequestContext · RequestFacts · ServerApp · ServerHooks · CapabilityOpened · DevtoolsEventInput · ResolveOptions · RouteConfig · RouteConfigMatcher · ServerPlugin

deferredRegion

function

A deferred region value whose HTML the browser fetches from url (a capability the plugin minted, '' for an inert one). HTML only: whatever islands the answer carries wake themselves.

function deferredRegion(id: string, url: string): DeferredRegion;

defineServer

function

Typed identity: a plugin's server half.

function defineServer<Options = undefined, State = undefined>(plugin: ServerPlugin<Options, State>): ServerPlugin<Options, State>;

late_chunk

function

A late chunk: inert template + nothing else — the boot script (or the runtime, post-SPA-swap) performs the swap. Multiple chunks for one slot apply in order (progressive states).

function late_chunk(id: string, html: string): string;

late_slot

function

Wrap html as the swappable slot id (display:contents — layout-invisible).

function late_slot(id: string, html: string): string;

og_html_region

function

A pure-HTML region — what import.meta.og.code() / .md() inline. The html is baked at build (Shiki / markdown), so this is an INLINE region whose component ({@link RawHtml }) simply renders the html in the page's own SSR pass. Static by construction: it ships as HTML and no client JS of its own. Rendered like any region: <Region of={…} />.

function og_html_region(html: string): InlineRegion;

open_late_scope

function

ONLY A RENDERER THAT DELIVERS LATE CHUNKS ARMS THEM. The server router's streamed document opens this request's late scope before it renders (and drains it after); anywhere else — a Kit page render — a promise of registers nothing, because nothing would ever fill its slot (a Kit page's header sat in an empty slot forever).

function open_late_scope(): void;

region

function

Make a held region with type-checked props.

  • A plain component import → an inline region (renders in this pass; can't cross the wire).
  • A component imported with { region: 'raw' } → a dual region: inline where it's made, a signed ticket where it travels. Minting the marked form is server-only (the signer lives on the SSR leg) — call it in a load / remote / render context.

A dual region is awaitable. await region(Card, props) renders the component to HTML on the server and bakes it into the ticket, so the client swaps it in with no extra request. In an async generator (query.live) or an async remote, yield region(…) / return region(…) is awaited by the language, so the HTML travels automatically — LiveView over the channel you already have. A held region you don't await renders inline where it lands (first paint, same SSR pass).

const region: typeof make_region & {
  snippet: typeof region_snippet;
}

register_route_config

function

A routes() table joins (keyed: dev re-evaluates hooks.server.ts and calls routes() again; a matcher with the same key REPLACES its stale twin instead of shadowing it).

function register_route_config(key: string, matcher: RouteConfigMatcher): void;

take_late_regions

function

The router drains THIS request's registered late regions after the document render (through this seam, never by importing hooks — its virtual: imports don't resolve outside Vite). null when nothing registered / nothing armed. Draining clears the request's list.

function take_late_regions(): LateRegion[] | null;

LATE_BOOT_SCRIPT

const

The classic inline boot: observes streamed-in <template data-og-late> nodes DURING parse and swaps each into its slot immediately (stylesheet links hoist to head first so the swap never paints unstyled). Kept dependency-free and tiny — it must run before any module script can.

const LATE_BOOT_SCRIPT: string

STORED_DOCUMENT_META

const

Stamped into a document served FROM a store (not the render that just happened): the browser runtime then treats render: 'live' lakes as stale on first mount and revalidates them.

const STORED_DOCUMENT_META = "<meta name=\"ogygia-stored\" content=\"hit\">"

HandleContext

interface

A request as an AROUND hook sees it: before anything is decided about it.

interface HandleContext<Options = undefined> {/*…*/}

HandleContext.event

Kit's own event, untouched.

readonly event: RequestEvent;

HandleContext.options

readonly options: Options;

HandleContext.app

readonly app: ServerApp<Options>;

LateRegion

interface

interface LateRegion {/*…*/}

LateRegion.id

id: string;

LateRegion.promise

promise: Promise<unknown>;

PluginCapabilities

interface

A capability URL for an endpoint the plugin serves (a hole the browser fetches later), held to the rules of the app's own region holes: the same lifetime, expiry in unix seconds, and the same answer once one has expired, so the runtime renews it the way it renews a region hole. The URL carries the plugin's own fields first, then exp and sig.

interface PluginCapabilities {/*…*/}

PluginCapabilities.mint

Sign a capability minted now: its exp (long on a page a plugin may store or a shared cache keeps, else the app's ttl) and the sig over message(exp).

mint(message: (exp: string) => string): {
    exp: string;
    sig: string;
  };

PluginCapabilities.open

Check a request for a capability mint made, by its URL's exp and sig (with renew=1, the runtime's one retry of an expired one). Refused: the response to answer with, the 403 a region request gets. Accepted: headers to send with the answer (a renewal's carry the fresh URL).

open(url: URL, message: (exp: string) => string): CapabilityOpened;

RequestContext

interface

One request, as one plugin sees it.

interface RequestContext<Options = undefined, State = undefined> {/*…*/}

RequestContext.event

Kit's own event, untouched.

readonly event: RequestEvent;

RequestContext.path

The decoded pathname.

readonly path: string;

RequestContext.state

This plugin's per-request state, from its state().

readonly state: State;

RequestContext.options

readonly options: Options;

RequestContext.app

readonly app: ServerApp<Options>;

RequestContext.facts

The request's shared facts.

readonly facts: RequestFacts;

RequestContext.routeConfig

This request's value for a key this plugin claimed inside Kit's config page option (b.routeConfig(key): export const config = { <key>: … } on a Kit page or layout, merged the way Kit merges it, or a programmatic router's config), or undefined when nothing declares it.

routeConfig(key: string): unknown;

RequestContext.renderHole

Render one signed hole in place, with this request's event (its cookies ride in): the hole's HTML, or null when it cannot render now (the stored fallback stays, the browser fetches).

renderHole(endpoint: string): Promise<string | null>;

RequestContext.perVisitor

Name the first reason this render is per-visitor (later reasons do not replace it).

perVisitor(reason: string): void;

RequestFacts

interface

FACTS — per-request shared state. A fact is filled by whoever learns it and read by whoever needs it; neither side imports the other. Keyed by the Request (every request has its own record, made on first touch, gone with the request), so a plugin's own code can reach them outside a hook too: a flag() read during the render holds its request.

Plugins add their own facts by declaration merging:

declare module 'ogygia/plugin' { interface RequestFacts { myThing?: string } }

One global slot, not module scope: a plugin package and core can end up as two module instances of this file (src beside dist, a dual install), and a fact written through one must be read through the other.

interface RequestFacts {/*…*/}

RequestFacts.storable

This render MAY be stored and served to others later (a plugin decided so before the render): the holes it mints get prerender-grade capabilities, and data-source receipts are kept.

storable: boolean;

RequestFacts.perVisitor

The first reason this render is per-visitor (a flag read, a streamed promise, a personal cookie or header a plugin observed), named for the dev note — or null.

perVisitor: string | null;

RequestFacts.streamed

The page streams load promises after the document.

streamed: boolean;

RequestFacts.private

The response belongs to one browser (devtools switched on for it): never store or share it.

private: boolean;

RequestFacts.answerForm

An app transform renders this router-fetched page as an ANSWER, a form a store keyed by path does not hold.

answerForm: boolean;

RequestFacts.sources

Data-source receipts (s:<id>:<fp>) the render filed — collected only once a plugin asks for them by setting a list here (a storable render's reverse index, a fragment's provenance).

sources: string[] | null;

RequestFacts.assignments

Every flag decided for this request (name → variant): federation carries them as claims.

assignments: Record<string, string>;

ServerApp

interface

The server process, as one plugin sees it (handed to setup, and on every ctx.app).

interface ServerApp<Options = undefined> {/*…*/}

ServerApp.name

The plugin's name (its package name).

readonly name: string;

ServerApp.options

The options the app gave the plugin in vite.config (JSON).

readonly options: Options;

ServerApp.notify

Call a shared hook on every plugin that has it.

readonly notify: Notify;

ServerApp.ask

Ask a shared hook: the first answer that is not undefined, in plugin order.

readonly ask: Ask;

ServerApp.facts

A request's facts (made on first touch) — for the plugin's own code outside a hook.

facts(request: Request): RequestFacts;

ServerApp.current

The facts of the request the running code belongs to (a load, a render), or null outside one.

current(): RequestFacts | null;

ServerApp.sign

Sign message with the app's region key (HMAC): a capability only this app can mint.

sign(message: string): string;

ServerApp.verify

Check a signature sign made (constant-time).

verify(message: string, signature: string): boolean;

ServerApp.capability

Signed capabilities for an endpoint the plugin serves, held to the rules of region holes.

readonly capability: PluginCapabilities;

ServerApp.devtools

Record a server-realm event into the current request's devtools stream (a no-op unless devtools is on for that browser).

devtools(event: DevtoolsEventInput): void;

ServerApp.warn

Warn once per message, prefixed with the plugin's name.

warn(message: string): void;

ServerHooks

interface

The shared hooks. Each is optional on a plugin; the signature is the contract.

interface ServerHooks {/*…*/}

ServerHooks.invalidate

Drop every stored page carrying any of tags (a peer said they are stale). Never re-announced.

invalidate(tags: string[]): void | Promise<void>;

ServerHooks.invalidated

Stored pages were dropped: tags names what (p:<path>, s:<source>:<fp>), or 'all'.

invalidated(tags: string[] | "all"): void | Promise<void>;

ServerHooks.lastBuild

Record version as the build the shared store last saw; answer the one before (or undefined).

lastBuild(version: string): string | undefined | Promise<string | undefined>;

CapabilityOpened

type

What capability.open decided.

type CapabilityOpened = {
  ok: true;
  headers: Record<string, string>;
} | {
  ok: false;
  response: Response;
};

DevtoolsEventInput

type

What a call site passes to emit — the envelope is added by the bus.

type DevtoolsEventInput = CompileEventInput | ServerEventInput | WireEventInput | RuntimeEventInput | HubEventInput | NavEventInput;

ResolveOptions

type

Kit's resolve() options (a transformPageChunk, filterSerializedResponseHeaders, …).

type ResolveOptions = NonNullable<Parameters<Parameters<Handle>[0]["resolve"]>[1]>;

RouteConfig

type

A route's merged config, as far as the claimed keys go.

type RouteConfig = Readonly<Record<string, unknown>>;

RouteConfigMatcher

type

Per matcher: undefined = not mine (ask the next one, then the file world) · an object = mine, with this merged config (a claimed key it lacks answers undefined, never the file world).

type RouteConfigMatcher = (pathname: string) => RouteConfig | undefined;

ServerPlugin

type

A plugin's server half: lifecycle hooks plus any shared hooks it implements.

type ServerPlugin<Options = undefined, State = undefined> = Partial<ServerHooks> & {
  setup?(app: ServerApp<Options>): void;
  state?(): State;
  handle?(ctx: HandleContext<Options>, next: (options?: ResolveOptions) => Promise<Response>): MaybePromise<Response>;
  request?(ctx: RequestContext<Options, State>): MaybePromise<void | Response>;
  page?(ctx: RequestContext<Options, State>): MaybePromise<void | Response>;
  response?(ctx: RequestContext<Options, State>, response: Response): MaybePromise<void | Response>;
  done?(ctx: RequestContext<Options, State>): void;
};