Skip to content

Web API ​

Import from @portablesheep/brevity-web for HTML fragments, form feedback, and static assets. For examples, see HTML helpers.

Escaped fragments ​

FunctionContract
html tagged templateEscape interpolated text; compose nested fragments and arrays
escapeHtml(value: string): stringEscape &, <, >, ", and '
trustedHtml(markup: string): HtmlFragmentMark already-trusted markup for unescaped composition

HtmlValue permits strings, numbers, bigint, booleans, null/undefined, fragments, and readonly nested arrays. Nullish values render empty text; unsupported objects throw TypeError. Fragment .toString() returns the markup.

Use quoted attributes, application-owned element/attribute names, and separate URL validation. HTML escaping is not sanitization of arbitrary trusted markup, CSRF protection, or authorization. Never trust request input as a fragment. Do not use ordinary interpolation in executable script/style contexts.

renderPage(res, template, data, options = {}) ​

Template<Data> is (data) => HtmlFragment. Layout<Data> is (body, data) => HtmlFragment. PageOptions<Data> accepts layout and status (default 200). Templates/layouts must be synchronous fragment-returning callbacks; await data before rendering. Returning a string or another value throws TypeError.

Sets HTML UTF-8 Content-Type and X-Content-Type-Options: nosniff, then sends. Layouts receive the original data. It does not add a doctype, CSP, cookies, CSRF, or global app layout automatically.

Form feedback ​

createFormState(options?: FormOptions): FormState snapshots:

OptionDefault/meaning
valuesString/undefined values by field name
errorsReadonly message arrays by field name
labelsSummary display labels by field name
idPrefix"form"; letter followed by letters/digits/underscore/hyphen

An invalid prefix throws TypeError. IDs combine the prefix with a deterministic UTF-16LE hex encoding of the field name; do not infer IDs yourself. Use distinct prefixes if more than one form shares a page.

MethodResult
value(name)Retained string, default ""
field(name, { describedBy? } = {})FormField with id, name, value, invalid, describedBy
fieldErrors(name)Escaped list with its ID, or empty fragment
errorSummary()Linked messages, alert role, heading and tabindex, or empty fragment

field().describedBy combines your hint ID with the error list ID only when invalid. Apply it, the label association, and aria-invalid to your own control. No input controls, validation rules, DOM focusing, transport, or error class conventions are automatically supplied.

html
<% const field = it.form.field("text", { describedBy: "text-hint" }); %>
<%= it.form.errorSummary() %>
<form method="post" action="/notes">
  <label for="<%= field.id %>">Note</label>
  <p id="text-hint">Write between 1 and 200 characters.</p>
  <textarea id="<%= field.id %>" name="<%= field.name %>"
    aria-invalid="<%= field.invalid %>" aria-describedby="<%= field.describedBy %>"
    maxlength="200"><%= field.value %></textarea>
  <%= it.form.fieldErrors("text") %>
  <button type="submit">Save note</button>
</form>

JSON state ​

serializeJson(data: unknown): string uses JSON.stringify and escapes <, >, &, U+2028 and U+2029. Undefined top-level values throw TypeError; other normal JSON limitations (cycles, bigint) remain.

jsonScript(id, data): HtmlFragment returns an inert application/json script with escaped ID and safely serialized data. Parse its textContent in the browser; validate the resulting unknown data. It is not executable code.

ts
import { fileURLToPath } from "node:url";
import { html, staticAssets } from "@portablesheep/brevity-web";
import { createIslands, islandManifest } from "@portablesheep/brevity-islands";
import { createNotebook } from "../tutorial/app.js";
import type { NoteStore } from "../tutorial/store.js";
import { nativeRoutes } from "../native/server.js";

export function islandApp(store: NoteStore) {
  const island = createIslands({ manifest: "/assets/islands.json", loader: "/assets/loader.js" });
  const app = createNotebook(store, (form) =>
    island(
      "counter",
      { initialText: form.value("text") },
      {
        fallback: html`<div id="note-counter">Character counter available with JavaScript.</div>`,
      },
    ),
  );
  app.router.get("/assets/islands.json", (_req, res) =>
    res.json(islandManifest({ counter: "/assets/counter.js" })),
  );
  app.use(
    staticAssets({
      "/assets/counter.js": {
        file: fileURLToPath(new URL("./dist/counter.js", import.meta.url)),
      },
      "/assets/loader.js": {
        file: fileURLToPath(import.meta.resolve("@portablesheep/brevity-islands/loader.js")),
      },
    }),
  );
  nativeRoutes(app);
  return app;
}

Static assets ​

staticAssets(assets: Readonly<Record<string, StaticAsset>>) returns core-compatible middleware. A StaticAsset has file, optional contentType, and optional cacheControl (default "no-cache").

Each key must be a canonical, unencoded absolute URL path. Invalid paths/header values and unknown file extensions without an explicit content type throw during setup. Common web extensions have built-in MIME types.

Only exact raw path matches are served (query strings allowed); encoded or normalized aliases do not expand the allowlist. GET reads the file, HEAD sends its size without a body, and other methods on a mapped path receive 405 with Allow. Unmapped paths continue middleware.

The helper reads regular files on demand and sets Content-Type, nosniff, Cache-Control, and Content-Length. Missing/unreadable/non-regular files fail through core's error handling, not a fabricated successful response. It is not a directory traversal mapper, range/ETag server, or frontend compiler. Build browser assets separately and supply an explicit manifest for production.

Exports and types

Runtime exports: HtmlFragment, html, escapeHtml, trustedHtml, serializeJson, jsonScript, renderPage, createFormState, and staticAssets. Type exports: HtmlFragment, HtmlValue, Template, Layout, PageOptions, FormOptions, FormField, FormState, and StaticAsset. HtmlFragment is also a runtime class export so optional template integrations can recognize trusted fragments with instanceof. Its constructor, like trustedHtml, accepts already-trusted markup; prefer html for escaped content.

Small core. Build your way.