Menu

MicroHost JavaScript SDK (API version 1)

Site code is an ES module. Its default export is an async handler:

export default async function handle(request, ctx) {
  const { rows } = await ctx.db.query("SELECT 1 AS one");
  return Response.json({ one: rows[0][0] });
}

This file is the compact SDK reference. The full developer documentation, with guides and examples, is in docs/developers/. Types for editors: microhost.d.ts.

The cross-engine conformance suite in crates/engine-api/conformance/*.json (48 scenarios) checks the handler contract, Request, Response, status and redirect rules, headers, URL, text encoding, randomness, import rules, limits, capability absence, microhost/auth and private admin-request verification (verifyAdminRequest, ctx.admin; five admin_verify_* scenarios). It has no scenarios yet for microhost/sql, microhost/db, microhost/session, fetch, mail or ctx.dbs. An engine is admitted only after it passes the suite; a feature marked "yes" below is one the suite exercises.

Supported

Area Surface Conformance
Handler export default async function (request, ctx) must resolve to a Response yes
Request method, url (always https://<host><decoded path><query>), headers, immutable params, text(), json(), arrayBuffer() yes
Request body Fully buffered, at most max_request_body_bytes (1 MiB). No streams. partly
Response new Response(body, { status, headers }), body string, Uint8Array or null yes
Status 200 to 599 for the constructor; other values throw RangeError yes
Response.json Sets content-type: application/json yes
Response.html Sets content-type: text/html; charset=utf-8 and x-content-type-options: nosniff yes
Response.redirect Location header; status 301, 302, 303, 307 or 308 only yes
Headers new Headers(init), get, has, set, append, delete; case-insensitive names; repeated Set-Cookie values are kept separate partly
URL new URL(input, base?); href, origin, protocol, host, hostname, pathname, search, hash, toString() yes (pathname)
TextEncoder / TextDecoder UTF-8 only yes (encode length)
crypto.getRandomValues Integer typed arrays, at most 65536 bytes per call yes
console log, info, warn, error; bounded to 64 KiB per execution; site log only; ctx.env values are redacted partly
fetch https: URLs, method, headers, body (string or Uint8Array); resolves to status, ok, headers, text(), json(), arrayBuffer(). Exact allowed hosts, redirects re-checked. Implemented through the in-process egress broker; not verified end to end. no
ctx.db query, execute, transaction; present only with the database permission yes
ctx.storage get, put, append, delete, list, sendFile; present only with the storage permission partly
ctx.mail send({ to, subject, text, html? }) returns { messageId } when accepted into the durable outbox. Delivered over the site's own SMTP server if the owner set one, else the platform SMTP server a platform administrator configures; without either a site cannot be granted mail. no
ctx.jobs enqueue(name, payload?, { delaySeconds?, key? }) returns { runId }; present only when the site declares jobs and was granted jobs no
ctx.env get(name); unknown or reserved names resolve to undefined; present only with the secrets permission no
ctx.dbs get(name) handle with query, execute, transaction; declared in microhost.d.ts; integration unverified no
async / await Full promise support inside the handler yes
Imports Static import with relative specifiers ending in .js, inside the release private namespace; reserved microhost/* modules; at most 256 modules and 8 MiB of source yes

ctx.db details

Absence without permission

ctx.db, ctx.storage, ctx.mail, ctx.env, ctx.jobs and ctx.dbs are undefined unless the release requests the permission and an administrator granted it. Code must not assume they exist. Conformance scenarios *_absent_without_permission check this.

SDK modules

Four reserved, lazily loaded modules, requested with the manifest sdk list (auth, db, session, sql). Details: SDK modules.

Import Manifest name Engines
microhost/sql sql Wasm, native, Linux
microhost/db (alias db) db (implies sql) Wasm, native, Linux
microhost/auth auth Wasm, native, Linux
microhost/session session Implemented once in the shared SDK layer for all engines. Verified end to end on Wasm (dev VM); native and Linux load the same module and dispatch, but a full cycle against real SQLite is not yet tested there.

A manifest with no sdk key leaves every module importable; with an sdk list only the listed modules can be imported. An administrator can disable a module per site (GET/PATCH /admin/api/v1/sites/{id}/sdk-modules); the effective set is requested minus disabled. The switch is not authorization: ctx.db is governed only by the database grant.

Database modules (microhost/sql, microhost/db)

import { sql } from "microhost/sql";
import { all, one, run, tx, id, now, tasks, tables } from "microhost/db";

Passwords (microhost/auth)

hashPassword(password) returns a PHC string ($argon2id$v=19$m=19456,t=2,p=1$...); verifyPassword(password, storedHash) returns a boolean and accepts only that profile. Passwords are at most 1024 UTF-8 bytes, an execution may make at most 2 auth calls, at most 4 hashes run at once platform-wide, and one call has a 2 second deadline. Errors have lowercase codes (invalid_input, quota_exceeded, host_unavailable, ...). For unknown users, verify against a valid dummy hash of the same profile; that is author guidance. See Auth and sessions.

Sessions (microhost/session)

create(userId, { maxAgeSeconds, data }) resolves with a Set-Cookie value, get(request) with { userId, data } or null, destroy(request) with a clearing cookie value, destroyAll(userId) with nothing. Cookie: __Host-mh_session=<token>; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=<s>. The token is opaque (256 bits); only its SHA-256 is stored in the site_sessions table that you create with this migration:

CREATE TABLE site_sessions (
  token_hash BLOB PRIMARY KEY CHECK (length(token_hash) = 32),
  user_id TEXT NOT NULL,
  data_json TEXT NOT NULL DEFAULT '{}',
  expires_at INTEGER NOT NULL
);
CREATE INDEX site_sessions_user_id ON site_sessions(user_id);

Expiry uses the database clock. Limits: user id 256 bytes, data 1024 bytes of JSON, lifetime 1 to 2,592,000 seconds. Every call needs the database grant. Append the cookie with response.headers.append("Set-Cookie", cookie). CSRF checks (Origin, a per-session token) and login throttling are the author's responsibility. See Auth and sessions.

Errors

Response headers

The host removes hop-by-hop headers (for example connection) and content-length, rejects header names that are invalid and values containing CR or LF, limits a Set-Cookie value to 4096 bytes, and enforces the response size limit (4 MiB by default). A rejected header fails the request with InvalidResponse (502; ADR-0012). Multiple Set-Cookie headers are preserved.

Not supported

These are absent in v1. Code that uses them fails with a ReferenceError or undefined. Do not polyfill them with globals; the platform does not provide the capabilities behind them.

Versioning

SDK_API_VERSION is 1. A change that removes a supported item, or that changes its observable behaviour, requires a new version and new conformance cases. Adding an item requires conformance cases for it before an engine that provides it can be admitted. The version is pinned in each release record, and sdk-js/runtime/v1/ must not be edited once releases exist against it.

ctx.mail.send resolves with an outbox message id when the message is accepted. It does not confirm delivery; the host delivers queued mail asynchronously and may retry or expire it.

Route parameters

An explicit route may use whole-segment named parameters, such as /posts/:slug, or one terminal catch-all, such as /files/*rest. Captures are available as decoded strings in request.params; catch-all segments are joined with /. The object is immutable. Exact and convention routes receive an empty object. See Routing.

Config script (microhost.config.js)

Deployments may include microhost.config.js, which the server evaluates once per deploy in a capability-free build guest. It imports the server-provided microhost module and exports a manifest through defineSite. This module is not part of the request-handler API. The build has no ctx, database, mail, storage, secrets, network, clock or randomness. Relative imports are confined to the staged release. files.list(prefix) returns sorted safe release paths and files.read(path) reads bounded release content.

import { defineSite, route, files } from 'microhost';

const posts = files.list('content/posts/').filter(path => path.endsWith('.json'));
export default defineSite({
  routes: posts.map(path => route(`/posts/${path.split('/').at(-1).slice(0, -5)}`, 'api/post.js')),
  permissionsRequested: { database: true },
});

The module also exports defineExtension and field for admin extensions (all build exports are declared in microhost.d.ts; defineSite also accepts sdk, the list of requested SDK modules). route methods default to GET. Omitted values resolve to schema version 1, static root public, SPA fallback off, no explicit routes, no requested capabilities or limits, and no admin extensions; schemaCompat comes from the server's release default when omitted. Convention /api/* routing is a runtime rule and is not expanded into the resolved route list. The result is validated by the same schema-v1 manifest and admin descriptor validators as microhost.json, and stored as canonical JSON. If both microhost.config.js and microhost.json exist, the script wins and the deploy warns manifest_json_ignored. Build limits: 2 s, 50,000,000 fuel, 32 MiB, 64 KiB output, 1,000 files listed, 1 MiB of reads. See Config script.