// MicroHost JavaScript SDK, API version 1 (SDK_API_VERSION = 1 in crates/engine-api). // // Ambient declarations for site code. Anything not declared here is not part of the SDK: // engines do not provide it, and conformance scenarios check that it stays absent. // See sdk/README.md for the supported feature set and the explicit exclusions. // // Every host call (database, mail, storage, env, fetch) returns a Promise. Failures are thrown // with a stable `code` property; messages are generic and carry no paths or SQL text. /** Header input accepted by Headers, Response and fetch. Names are matched case-insensitively. */ type HeadersInit = Headers | Record | [string, string][]; /** Body input accepted by Response and fetch. */ type BodyInit = string | Uint8Array | null; /** A SQL parameter. Mapping is defined in sdk/README.md (section "ctx.db"). */ type SqlParam = | null | undefined | boolean | number | bigint | string | Uint8Array | ArrayBuffer; /** Error thrown by host calls. `code` is stable; `message` is generic. */ interface MicroHostError extends Error { readonly code: | "capability_denied" | "CAPABILITY_DENIED" | "QUOTA_EXCEEDED" | "INVALID_INPUT" | "HOST_UNAVAILABLE" | "HOST_INTERNAL" | "invalid_admin_request" // Lowercase codes raised by the SDK modules (microhost/auth, /db, /session, /sql). | "invalid_input" | "quota_exceeded" | "host_unavailable" | "db_use_sql_template" | "db_multiple_rows" | "db_duplicate_column" | "db_no_such_column" | "db_where_required" | "db_invalid_direction" | "sdk_module_disabled"; } /** Returned only after host verification of a private admin invocation. */ interface VerifiedAdminRequest { readonly userId: string; readonly role: "viewer" | "editor" | "developer" | "owner"; readonly workspace: string; readonly site: string; readonly extension: string; readonly name: string; readonly operation: string; readonly issuedAt: number; } /** A safe error with code and message invalid_admin_request. */ interface AdminRequestError extends Error { readonly code: "invalid_admin_request"; } /** Verifies the eight signed admin headers through the host; signing keys never reach JavaScript. */ declare function verifyAdminRequest(request: Request, ctx: SiteContext): VerifiedAdminRequest; /** Request headers have lowercase names. Only the methods listed here exist in v1. */ declare class Headers { constructor(init?: HeadersInit); get(name: string): string | null; has(name: string): boolean; set(name: string, value: string): void; append(name: string, value: string): void; delete(name: string): void; } /** Absolute https URL. Only the properties listed here exist in v1; URLSearchParams does not. */ declare class URL { constructor(input: string, base?: string | URL); readonly href: string; readonly origin: string; readonly protocol: string; readonly host: string; readonly hostname: string; readonly pathname: string; readonly search: string; readonly hash: string; toString(): string; } /** The request handed to the handler. The body is fully buffered and size limited. */ declare class Request { /** Not constructible in v1; instances are provided to the handler. */ private constructor(); /** Uppercase HTTP method. */ readonly method: string; /** Absolute URL, `https://host/path?query`. */ readonly url: string; /** Header names are lowercase. */ readonly headers: Headers; /** Decoded route captures. This is an immutable empty object for exact and convention routes. */ readonly params: Readonly>; text(): Promise; json(): Promise; arrayBuffer(): Promise; } interface ResponseInit { /** 200 to 599 inclusive. Other values throw a RangeError. */ status?: number; headers?: HeadersInit; } /** The response the handler must return. Hop-by-hop headers and content-length are stripped by the host. */ declare class Response { constructor(body?: BodyInit, init?: ResponseInit); /** Serializes `value` with JSON.stringify and sets content-type: application/json. */ static json(value: unknown, init?: ResponseInit): Response; /** Sets content-type: text/html; charset=utf-8 and x-content-type-options: nosniff. */ static html(html: string, init?: ResponseInit): Response; /** Redirect with a Location header. Only 301, 302, 303, 307 and 308 are accepted. */ static redirect(url: string | URL, status: 301 | 302 | 303 | 307 | 308): Response; } interface SiteDbResult { columns: string[]; rows: unknown[][]; rowsAffected: number; /** Number for safe integers, bigint otherwise, null when not applicable. */ lastInsertRowid: number | bigint | null; } interface SiteDbStatement { sql: string; params?: SqlParam[]; } /** Immutable SQL fragment produced by the `microhost/sql` tagged template. */ interface SqlFragment { readonly text: string; readonly parameters: readonly SqlParam[]; } /** Build parameterized SQL. Interpolated fragments compose; arrays expand to placeholders. */ declare function sql(strings: TemplateStringsArray, ...values: (SqlParam | SqlFragment | readonly SqlParam[])[]): SqlFragment; interface DbRows> extends Array { readonly hasMore: boolean; first(): T | null; next(): Promise>; } type DbFilter = Partial<{ [K in keyof T]: T[K] | readonly T[K][] | null }>; interface DbQuery { orderBy(key: keyof T & string | SqlFragment, direction?: "asc" | "desc"): DbQuery; limit(count?: number): DbQuery; offset(count: number): DbQuery; all(): Promise>; [Symbol.asyncIterator](): AsyncIterator; } interface DbTable { readonly name: string; readonly columns: readonly (keyof T & string)[]; readonly primaryKey: K | null; get(key: T[K]): Promise; find(filter?: DbFilter): Promise; where(filter?: DbFilter): DbQuery; count(filter?: DbFilter): Promise; insert(values: Partial): Promise; update(keyOrFilter: T[K] | DbFilter, values: Partial): Promise; delete(keyOrFilter: T[K] | DbFilter): Promise; stmt: { insert(values: Partial): SqlFragment; insertOrIgnore(values: Partial): SqlFragment; }; } interface MicroHostDbModule { all>(statement: SqlFragment): Promise; one>(statement: SqlFragment): Promise; run(statement: SqlFragment): Promise<{ rowsAffected: number; lastInsertRowid: number | bigint | null }>; tx(statements: readonly SqlFragment[]): Promise; id(): string; now(): string; } /** Present on ctx only when the site was granted database access. */ interface SiteDb { query(sql: string, params?: SqlParam[]): Promise; execute(sql: string, params?: SqlParam[]): Promise; /** All-or-nothing. Rejects on the first failing statement after rollback. No interactive transactions in v1. */ transaction(statements: SiteDbStatement[]): Promise; } /** A named workspace database handle. Same shapes as `ctx.db`; governed by a per-database grant. */ interface SiteWorkspaceDb { query(sql: string, params?: SqlParam[]): Promise; execute(sql: string, params?: SqlParam[]): Promise; transaction(statements: SiteDbStatement[]): Promise; } /** Present on ctx only when the release requested workspace databases and a grant exists. */ interface SiteWorkspaceDbs { /** `name` is 1 to 64 letters, digits, underscores or hyphens; anything else throws INVALID_INPUT. */ get(name: string): SiteWorkspaceDb; } interface SiteMailMessage { to: string | string[]; subject: string; text: string; html?: string; } /** Present on ctx only when the site was granted mail. The sender is the site identity, not chosen by tenant code. */ interface SiteMail { /** Resolves when accepted for delivery; it does not mean the provider has delivered it. */ send(message: SiteMailMessage): Promise<{ messageId: string }>; } /** Present on ctx only when the site was granted storage. Keys are safe relative paths. */ interface SiteStorage { get(key: string): Promise; /** Strings are stored as UTF-8. */ put(key: string, value: string | Uint8Array): Promise; /** * Atomically appends to the object, creating it when missing. Concurrent appends never lose or * interleave bytes inside one call. The object may not grow past 16 MiB and counts against the storage quota. */ append(key: string, value: string | Uint8Array): Promise; delete(key: string): Promise; /** * Returns a response that makes the platform stream the object to the visitor, with `Range`, * `ETag` and `304` handled for you. Your code decides who may download (check the session, then * call this); the bytes never pass through your handler, so an object of any size up to the * store limit works. `init.headers` such as `Content-Type`, `Content-Disposition` and * `Cache-Control` are kept; framing and validators come from the object. A missing object answers * 404. Only the site's own objects can be named. */ sendFile(key: string, init?: { headers?: HeadersInit }): Response; /** Sorted names under `prefix`, at most `limit` (default and maximum 1000). No cursors in v1. */ list(prefix: string, options?: { limit?: number }): Promise; } /** * A small in-memory cache with a time to live. Present on ctx with the storage permission. Entries * belong to the site, are shared by all its requests, and are lost on a restart or earlier when the * site's budget (8 MiB by default) needs room: a miss is always normal, so never keep the only copy * of anything here. Keys are 1 to 256 bytes without control characters, values at most 64 KiB, and * `ttl` is 1 to 86400 seconds (default 300). Calls reject with `INVALID_INPUT` for bad input and * `QUOTA_EXCEEDED` for a value larger than the whole budget. */ interface SiteCache { /** The cached value, or null when it is missing or expired. */ get(key: string, options?: { as?: "text" }): Promise; get(key: string, options: { as: "bytes" }): Promise; /** Stores a string or bytes, replacing any older entry and restarting its time to live. */ set(key: string, value: string | Uint8Array, options?: { ttl?: number }): Promise; /** Removes an entry; resolves with whether a live one existed. */ delete(key: string): Promise; /** * Drops the cached answers of this site's routes that set `cache` in the manifest, so the next * request runs the script again. Give exactly one of `path` (that request path, whatever its * query or `vary` headers), `prefix` (every path that starts with it) or `all: true`. Resolves * with the number of entries dropped. Only your own site is touched. Needs the same permission * as the rest of `ctx.cache`. */ purgeResponses(scope: { path: string } | { prefix: string } | { all: true }): Promise; /** * Atomically adds `by` (default 1, may be negative) to the integer in `key` and resolves with the * new value. A missing or expired entry starts at `by` with the given `ttl`; a live one keeps * its expiry. Rejects with `INVALID_INPUT` when the entry is not a decimal integer. */ incr(key: string, options?: { by?: number; ttl?: number }): Promise; /** * The cached text; on a miss runs `compute`, stores what it returns and returns it. Two requests * that miss together both compute: there is no single-flight. */ getOrSet(key: string, options: { ttl?: number } | undefined, compute: () => string | Promise): Promise; getOrSet(key: string, compute: () => string | Promise): Promise; } /** Present on ctx only when the site declares jobs and was granted jobs. */ interface SiteJobs { /** * Queues a run of a job declared in the manifest and resolves with its durable run id. `payload` * is any JSON value up to 64 KiB, read by the job with `request.json()`. While a run with the same * `key` is queued or running, that run is returned instead of a new one. `delaySeconds` is 0 to * 604800. Rejects with `INVALID_INPUT` for an unknown job, `QUOTA_EXCEEDED` when the queue is full. */ enqueue( name: string, payload?: unknown, options?: { delaySeconds?: number; key?: string }, ): Promise<{ runId: number }>; } /** Present on ctx only when the site was granted secrets access. Unknown or reserved names resolve to undefined. */ interface SiteEnv { get(name: string): Promise; } /** The context passed to the handler. Optional members are absent (undefined) unless granted. */ interface SiteContext { db?: SiteDb; dbs?: SiteWorkspaceDbs; mail?: SiteMail; storage?: SiteStorage; cache?: SiteCache; jobs?: SiteJobs; env?: SiteEnv; /** Present only on private /_admin/ invocations. */ admin?: { verify(request: Request): VerifiedAdminRequest }; } /** A requested capability was not granted or is unavailable for this execution. */ interface CapabilityUnavailableError extends Error { readonly code: "capability_denied"; } interface FetchInit { method?: string; headers?: HeadersInit; body?: string | Uint8Array; } interface FetchResponse { readonly status: number; readonly ok: boolean; readonly headers: Headers; text(): Promise; json(): Promise; arrayBuffer(): Promise; } /** Outbound HTTPS through the egress broker. Host, redirects and size are policy-controlled. No AbortSignal, streams or cookies. */ declare function fetch(input: string | URL, init?: FetchInit): Promise; declare class TextEncoder { readonly encoding: "utf-8"; encode(input?: string): Uint8Array; } declare class TextDecoder { readonly encoding: "utf-8"; decode(input?: Uint8Array | ArrayBuffer): string; } declare const crypto: { /** Fills `array` with random bytes. At most 65536 bytes per call. */ getRandomValues(array: T): T; }; /** Output is bounded (64 KiB per execution) and goes to the site log, never to the visitor. */ declare const console: { log(...args: unknown[]): void; info(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void; }; /** * Handler signature. The module's default export must be an async function that resolves to a * Response. Any other resolved value is an error (InvalidResponse, HTTP 502). * * @example * export default async function handle(request: Request, ctx: SiteContext): Promise { * return Response.json({ ok: true }); * } */ type SiteHandler = (request: Request, ctx: SiteContext) => Promise; declare module "microhost/sql" { export interface SqlFragment { readonly text: string; readonly parameters: readonly SqlParam[]; } export function sql(strings: TemplateStringsArray, ...values: (SqlParam | SqlFragment | readonly SqlParam[])[]): SqlFragment; } declare module "microhost/db" { export const all: MicroHostDbModule["all"]; export const one: MicroHostDbModule["one"]; export const run: MicroHostDbModule["run"]; export const tx: MicroHostDbModule["tx"]; export const id: MicroHostDbModule["id"]; export const now: MicroHostDbModule["now"]; } declare module "microhost/auth" { export function hashPassword(password: string): Promise; export function verifyPassword(password: string, storedHash: string): Promise; } declare module "microhost/session" { export interface Session { readonly userId: string; readonly data: Readonly>; } export interface SessionOptions { readonly maxAgeSeconds?: number; readonly data?: Record; } export function create(userId: string, options?: SessionOptions): Promise; export function get(request: Request): Promise; export function destroy(request: Request): Promise; export function destroyAll(userId: string): Promise; } declare module "db" { export * from "microhost/db"; } // Build-profile declarations. These names are exported by the server-shipped `microhost` module // while resolving microhost.config.js; they are not available to request handlers. /** * Opt-in response cache of a route: the platform keeps the answer of a safe GET for `ttl` seconds * (1 to 86400) and serves it without running the script. A request with a `Cookie` or * `Authorization` header is never served from or stored in the cache unless the route lists that * header in `vary`. Only `200` answers without `Set-Cookie`, `Cache-Control: no-store`, `private` * or `no-cache` and up to 256 KiB are stored. `vary` lists up to 4 request headers that are part * of the key (for example "accept-language"). The route must accept GET. */ interface BuildRouteCache { readonly ttl: number; readonly vary?: readonly string[]; } interface BuildRoute { readonly path: string; readonly script: string; readonly methods: readonly string[]; readonly cache?: BuildRouteCache; } /** Names accepted in the manifest `sdk` list. `db` implies `sql`. */ type BuildSdkModule = "auth" | "db" | "session" | "sql"; /** Value returned by `defineExtension`; passed in the `admin` list of `defineSite`. */ interface BuildExtension { readonly [key: string]: unknown; } /** Value returned by `field.(...)`, used in extension input and output descriptors. */ interface BuildField { readonly [key: string]: unknown; } interface BuildFieldOptions { /** Author-friendly spelling of the schema's `enum`. */ readonly values?: readonly string[]; readonly [key: string]: unknown; } type BuildFieldFactory = (options?: BuildFieldOptions) => BuildField; interface BuildSite { readonly schemaVersion: 1; readonly schemaCompat?: { readonly min: number; readonly max: number }; readonly static?: { readonly root?: string; readonly spaFallback?: boolean }; /** * Custom pages for errors the platform produces. Keys: a status from 400 to 599, "4xx", "5xx" or * "_" (everything else). A value ending in ".js" is a script that reads the status from * `request.params.status`; any other value is a file inside `public/`. The visitor always gets the * original status. */ readonly errorPages?: { readonly [statusOrClass: string]: string }; /** * The path the post-activation health check requests (default "/"). It must start with "/", be * at most 256 bytes of visible ASCII, contain no "..", "?" or "#", and not start with "/_admin". * It may name a static file, a route or a script route; the check needs a 2xx answer. */ readonly healthCheck?: { readonly path: string }; /** * Scheduled and background jobs, by name. A job is a script under `jobs/`; it runs on a cron * `schedule` (read in `timezone`, default UTC) or on demand, and needs the jobs capability. */ readonly jobs?: { readonly [name: string]: { readonly script: string; readonly schedule?: string; readonly timezone?: string; readonly overlap?: "skip" | "queue"; readonly catchUp?: "none" | "latest"; readonly timeoutMs?: number; readonly retries?: number; }; }; readonly routes?: readonly BuildRoute[]; readonly permissionsRequested?: { readonly database?: boolean; readonly workspaceDatabases?: readonly string[]; readonly email?: boolean; readonly storage?: boolean; readonly secrets?: boolean; readonly externalHttp?: readonly string[]; }; readonly limitsRequested?: { readonly timeoutMs?: number; readonly memoryMb?: number }; /** SDK modules the release imports. Absent: every module is importable. */ readonly sdk?: readonly BuildSdkModule[]; readonly admin?: readonly BuildExtension[] | unknown; } declare function defineSite(site?: Omit): BuildSite; declare function route( path: string, script: string, options?: { methods?: readonly string[]; cache?: BuildRouteCache }, ): BuildRoute; /** Declares an admin extension. Queries default to GET/viewer/read, actions to POST/editor/write. */ declare function defineExtension(extension: { readonly [key: string]: unknown }): BuildExtension; declare const field: { string: BuildFieldFactory; number: BuildFieldFactory; status: BuildFieldFactory; date: BuildFieldFactory; boolean: BuildFieldFactory; text: BuildFieldFactory; textarea: BuildFieldFactory; }; declare const files: { list(prefix?: string): readonly string[]; read(path: string): string | Uint8Array; };