@aweftjs/server
Connections and requests behind a gate. A listener you supply hands over web-standard requests and WebSocket handshakes; the server asks the gate who each one is and what it may reach, turns each accepted socket into a sync link plus a call channel, and runs the hooks of the modules the gate allows. It opens nothing, mints nothing, and knows no user.
Quickstart
import { auth, paths } from '@aweftjs/auth';
import { fromDirectory } from '@aweftjs/modules/node';
import { createServer } from '@aweftjs/server';
import { node } from '@aweftjs/server/node';
import { createStore, memoryDriver } from '@aweftjs/store';
const store = createStore({ driver: memoryDriver(), declare: { ...paths } });
const server = createServer({
sources: [fromDirectory('./modules'), auth],
store,
gate: 'auth/Gate',
listener: node({ port: 8080 }),
});
await server.start();That is the whole boot, and everything else your application does is a module in ./modules. The sources say where modules come from, and start loads every module every one of them lists, in dependency order: there is no load list, and a file you drop in that directory is running after the next boot. The gate says who may reach what, as an object or as the name of a module that is one; there is no default. The listener is where connections come from; node() ships, and the contract is small enough to write for another runtime. store is optional and is the only thing this package hands a module.
The props rule, which is a guarantee, not a habit: the platform hands your factories store and nothing else. There is no props option and no way to add one, so anything your application makes (a rules table, a scheduler, a document you hold open, a client of another service) is a module, and the modules that need it name it in deps. That is what gives you the ordering for free: a module that opens a document is built before the module that shares it, because it said so. server.loader is the loader this built, for follow, for a test, and for loading or unloading while the server runs.
A microservice that knows nothing about users installs no auth, keeps no store, and types the one word:
import { createServer, open } from '@aweftjs/server';
const server = createServer({ sources: [fromDirectory('./modules')], gate: open, listener: node({ port: 8081 }) });stop() ends every connection, stops the listener, and then unloads every module in reverse load order, so each module's stop runs after nothing can reach it. A stop that throws reaches handlers.failed under its module's name and the rest still unload, so one module that cannot let go does not strand the ones underneath it.
The gate
A gate is any object with two functions. A module instance is one; an object literal is one; open is one.
interface Gate<C> {
identify(request: Request, peer: { address }): { context: C } | { refused: Refusal[] };
access({ name, instance }, context: C): Refusal[]; // empty allows
}identify runs once per connection (at the handshake, before any socket opens) and once per HTTP request. What it answers as context reaches every hook as an argument; a refusal answers 401 with the reasons, and for a handshake no socket is ever opened. access runs before a module sees a connection, a call or a request, and its reasons refuse the call, 403 the request, or skip the hook. Both may be asynchronous. A throw out of either is a defect: 500, and reported (see below), never a refusal.
A gate may be named instead of passed. gate: 'auth/Gate' is the name of one of the modules your sources list, and start reads it off the loader. A name that is not loaded, or whose instance has no identify and access, is refused at start with reason missing.
A composed gate is a module. To keep the battery's policy and add a rule of your own, write a module that deps on the gate you are wrapping and name yours:
export const deps = ['auth/Gate'];
export default ({ imports }) => ({
identify: (request, peer) => imports.Gate.identify(request, peer),
access: async (module, context) => {
const reasons = await imports.Gate.access(module, context);
if (reasons.length > 0) return reasons;
return module.instance.admin === true && context.user !== admin
? [{ code: 'not-admin', message: `${module.name} is for the administrator` }]
: [];
},
});There is no compose and no chain of gates: two policies in a row is one function calling another, which a module already is.
The server interprets nothing in the context and reads nothing off a module for the gate. @aweftjs/auth's gate reads a module's public: true and treats absent as private; that is that gate's word, and a gate you write may read another or none. A gate with no session in it fits in ten lines:
const allowlist = (addresses: string[]): Gate<{ address: string }> => ({
identify: (_request, peer) => peer.address !== undefined && addresses.includes(peer.address)
? { context: { address: peer.address } }
: { refused: [{ code: 'address', message: `${String(peer.address)} is not on the list` }] },
access: () => [],
});Calls between modules through imports are not gated: modules in one process trust each other. The gate stands between the outside and a module.
What a module can carry
The server reads five things off a loaded module's instance, all optional:
export default ({ store }) => ({
// Once per connection the gate lets this module see. Return the function run when it ends.
connection: async ({ link, request, context, close }) => {
const board = await store.open(`board:${context.user}`);
link.share('board', board.root, { accept: (commit) => mine(commit) ? [] : [{ code: 'not-yours', message: '' }] });
return () => store.close(board);
},
// An `ask` from the client naming this module, after the gate allowed it.
call: async (args, context, { progress }) => { progress('reading'); return report(args); },
// HTTP, keyed by exact method and path.
routes: { 'GET /api/export': async (request, context) => new Response(await csv(context.user)) },
// An HTTP request no route matched. A `Response` answers it; `undefined` declines it.
request: async (request) => serve(new URL(request.url).pathname),
// What the server did, every event, after the fact.
observe: (event, context) => { if (event.kind === 'call') count(event.name, event.ms); },
});A document a hook shares is an @aweftjs/core observable: createObject, createArray and createMap make one, atomic groups writes into one commit, and a plain object or array in a slot is refused. The store's open hands back one ready to share.
connection hooks run in load order, which is dependency order, for the modules the gate allows; the functions they return run in reverse when the connection ends. A call is answered once every hook has run, so state a hook sets up is there for call. close() ends the connection from inside a hook, which is how a middleware module kills one. A module reloaded while a connection is open keeps that connection's hook state on the instance that made it, and its end functions with it; calls and routes go to the new instance. A share on a connection's link requires accept: the hole where any signed-in client writes anywhere is refused before anything crosses (no-accept). open is the handlers for the trusted case. accept is handed the arriving commit before it applies, so the document still reads as it was and a rule reads commit.deltas; the delta shape and the refused payload the other end gets are in @aweftjs/sync's README, under "Your rules go in accept", which is where that behaviour is tested.
call answers ask(name, args, { progress, timeout }) from the other end: progress streams back before the result, a throw answers with its reason and message. A module that is not loaded or has no call is missing; one the gate refuses is refused with the reasons.
routes are matched on METHOD /path exactly, the query aside; two loaded modules declaring one key are refused at start() by both names, and a conflict a later load introduces answers 500 to the request that meets it. No match is 404. A route answers with a Response; anything else is 500 and reported as not-a-response.
request is the rule for what no route matched, for the answers a table of exact paths cannot hold: a directory of files, or anything under one prefix. Every loaded module that declares it is asked, in load order, behind the gate, and the first Response is the request's answer; undefined declines and the next module is asked. A module the gate refuses is skipped rather than answered on the spot, so a public module still answers under a private one loaded before it. When every module declines the request is 404, as it was; when the gate refused one along the way and nothing answered it is 403 carrying that refusal's reasons, the same shape a refused route gets, so a private site does not read as an empty one. That body is JSON, { reasons: [{ code, message }] }. A hook that throws is 500, reported under its module's name, and it ends the walk: no module after it is asked, because a defect is not a decline. One that answers something that is not a Response is 500 and reported as not-a-response (design 248).
observe hears what the server did (design 260). Every loaded module that declares it is handed every event, in load order: connection (a socket the gate let in, with the handshake request) and closed (with ms since it opened); call (the module asked for and its instance when one is loaded, the args as sent, the outcome as { result } or { error }, and ms), for asks the server refused itself too, where the error is its missing, refused or closed; request (method, path, status, ms, and name, the module whose route or request hook answered, absent when the server answered itself; never the body); refused (a commit a share's accept turned away, with the topic and the reasons; an accept that throws is a refusal with the one reason accept-threw, as sync reads it); failed (what reaches handlers.failed). context is what identify answered for that connection or request, and the same reference reaches every hook and every event of one connection, so an observer that tells connections apart keys on it. The hook is not gated, because the server is telling its own modules what it did. Nothing waits for an observer and nothing reads what it answers: a throw or a rejection is reported under the observer's name and emitted to no one, so an observer that throws on every event does not chase its own tail, and the event it threw on is unaffected. args and result are the caller's own objects, not copies.
The client
One socket carries both the link (binary) and the requests (text). @aweftjs/client opens it and holds both:
import { createClient } from '@aweftjs/client';
const client = createClient({ url: 'wss://app.example/' });
const board = await client.share('board').ready;
const report = await client.ask('notes/Export', { month: '2026-09' }, { progress: (p) => bar.set(p) });That package attaches the link and the request channel the moment the socket is made, so the share and the ask above reach the server even though they are written while the socket is still connecting. It has to, for the reason this section already gives: the server's hooks run at the handshake and its first frames are on the wire before the client's open event fires, and a message that arrives before anything listens is lost, on every WebSocket implementation there is.
Identity is fixed for a connection's life: it is what identify said at the handshake. After signing in or out over HTTP, the client reconnects. A browser cannot set a cookie on an open socket, and @aweftjs/auth/client does that reconnect for the page.
The listener
interface Listener {
start({ request, socket }): Promise<void>;
stop(): Promise<void>;
}request(request, peer) answers a Request with a Response. socket(request, peer) is the handshake: it answers a Response to refuse the upgrade with that status, or a function to hand the opened socket to. peer is { address }, because a web-standard Request carries no address and a gate that keys on one has nowhere else to read it.
node(options) on @aweftjs/server/node ships: Node's http plus ws, the one runtime dependency in the stack. node({ port, host }) owns a server and closes it on stop; node({ server }) answers on a server you made (TLS, or a shared port) and never closes it. heartbeatMs pings every open socket and terminates one that does not answer, and nothing pings without it. maxPayload bounds a WebSocket message and an HTTP body alike (a declared length over it is 413, a body that crosses it is cut off): 1 MiB with nothing set, and Infinity is the one way to remove the bound. forwarded is for a listener behind a proxy you trust: true reads the scheme from x-forwarded-proto and the peer address from the last entry of x-forwarded-for, the one that proxy appended, since the first entry is whatever the client wrote; 'x-real-ip' reads the address from that header instead, for a proxy that sets it. Off, both headers are ignored. A TRACE is answered 405 before any handler sees it. port is readable after start, so port: 0 works in a test.
A listener for another runtime proves itself with listenerChecks() from @aweftjs/testing, the way a store driver or a sandbox runner does:
for (const c of listenerChecks()) test(c.name, () => c.run(() => {
const listener = myListener();
return { listener, url: () => myUrl(listener) };
}));Before the gate
Two things are checked before identify runs, so a refused request costs no gate work, and each has a value here that one option changes.
createServer({
sources, store, gate, listener,
limits: { requests: { count: 600, windowMs: 60_000 } }, // the value with nothing set
origins: ['https://app.example'], // beside the request's own host
});limits.requests counts requests and handshakes per peer address over a sliding window; over the count, the answer is 429 with a Retry-After in seconds and the reason limit, and for a handshake no socket opens. limits: { requests: false } removes the count. The address is the listener's word: behind a proxy, start the Node listener with forwarded, or the proxy is the one address every request shares. Clients behind one address share one count. The count is coarse on purpose; the sign-in route keeps its own attempt counts (@aweftjs/auth).
origins is the Origin rule. A request carrying an Origin header is refused with 403 and the reason origin when that origin's host is not the request's own host, port included, and the request is a handshake or has a method other than GET, HEAD or OPTIONS. A request with no Origin header passes: that is a client that is not a browser, and it holds no cookie a browser set. A list adds origins a page may send from; 'any' removes the rule. The browser's own SameSite rule already keeps the cookie off a cross-site request; this rule is what stops a sign-in forged from another site, which needs no cookie, and it holds when an application widens the cookie.
sliding({ count, windowMs }) is exported, the counter behind the limit, for a module that counts something of its own: take(key) answers { ok: true } or { ok: false, retryAfter }, and clear(key) forgets a key.
Every answer the server gives carries X-Content-Type-Options: nosniff unless the module set the header itself.
When something throws
A call that throws a refusal, an error carrying a reason, answers its caller with it and is not reported: the caller heard what the module meant it to. A call that throws anything else answers its caller failed with the words the call failed and nothing of the error, and is reported: a module's own bug names files and values the caller has no business reading. Everything else reaches handlers.failed(name, error) on createServer too: a connection hook that throws (the connection is closed), an end function that throws (the rest still run), a route that throws (a bare 500), the gate that throws (500), and a route conflict met by a request (500). Without a handler, a call's error is written to the console and the process goes on, because any client can reach a public call; every other failure is raised where nothing catches it, and the process says so and ends. Pass a handler in production.
What this package never decides
Who is on a connection, whether it lives, and who may reach a module: the gate's. Who may write a commit: accept, per share. What a module is for and what it holds: the module's, and what it needs is deps. Users, sessions, cookies: @aweftjs/auth, or whatever you load instead. The two bounds above have values so that a server forgotten about is still bounded; every other limit or interval is yours.
It does decide one thing about modules, and only one: everything your sources list is loaded at start and unloaded at stop. Which modules exist is still yours, and so is anything you load or unload through server.loader while it runs.
When a client sends bytes that are not a frame, the link ends and the connection with it: the end functions run and the socket closes, so the client hears.
Every error this package raises carries a reason: missing (createServer without one of its three, or a gate named that is not a loaded gate), not-an-option (loader or props passed to createServer, which builds its own loader), route-conflict, no-accept, not-a-response, started. The design notes are in docs/design/ 071 to 073, 240 and 241.
The design notes
A design NNN above is the note of that number in docs/design/, which says what was decided, why, what it costs, and what would reverse it.
API
Every export of @aweftjs/server, its signature as the compiler resolves it, and its block comment.
@aweftjs/server
Accept
type Accept = (socket: SocketLike) => void;What socket answers to accept an upgrade: the function the listener hands the opened socket to.
Accepting
interface Accepting extends ShareHandlers { readonly accept: (commit: Commit) => readonly WireReason[]; }Share handlers that say who may write. On a connection's link, accept is required, so no module shares a document writable by omission (design 071).
Connection
interface Connection<C = unknown> { readonly link: GatedLink; readonly request: Request; readonly context: C; close(): void; }What a connection hook is handed.
Ending
type Ending = (() => unknown) | undefined | void;What a connection hook returns: nothing, or the function run when the connection ends.
Gate
interface Gate<C = unknown> { identify(request: Request, peer: Peer): Identified<C> | Promise<Identified<C>>; access(module: Named, context: C): readonly Refusal[] | Promise<readonly Refusal[]>; }Who may reach a module. Required by createServer; nothing ships as a default.
Any object with these two functions is a gate, so a module instance is one, and so is an object literal ten lines long. open is the trusted case.
GatedLink
interface GatedLink { share<T extends object>(name: string, document: T | undefined, handlers: Accepting): Shared<T>; }The link a connection hook receives: a sync link whose every share says who may write.
Identified
type Identified<C> = { readonly context: C; } | { readonly refused: readonly Refusal[]; };What identify answers: the context every hook receives for this connection or request, or the reasons the caller is turned away. The server interprets neither.
Limits
interface Limits { readonly requests?: { readonly count: number; readonly windowMs: number; } | false | undefined; }The bounds checked before the gate (design 272).
Listener
interface Listener { start(handlers: ListenerHandlers): Promise<void>; stop(): Promise<void>; }Where connections and requests come from. node on @aweftjs/server/node ships; a listener for another runtime is proven by listenerChecks() from @aweftjs/testing.
ListenerHandlers
interface ListenerHandlers { request(request: Request, peer: Peer): Promise<Response>; socket(request: Request, peer: Peer): Promise<Response | Accept>; }What a listener calls.
Named
interface Named { readonly name: string; readonly instance: unknown; }A loaded module, as the gate is asked about it.
Origins
type Origins = readonly string[] | 'any';What origins on createServer takes: more origins a browser may send from, or the rule off.
Outcome
type Outcome = { readonly result: unknown; } | { readonly error: unknown; };What an ask came to: the module's answer, or what was thrown, the server's own refusals included.
Peer
interface Peer { readonly address: string | undefined; }Where a request or a connection came from, as far as the listener can tell.
Progress
interface Progress { progress(value: unknown): void; }No block comment on this export.
Refusal
interface Refusal { readonly code: string; readonly message: string; readonly path?: readonly string[]; } (from @aweftjs/core)One reason a commit was refused.
code is the stable token to branch on and message is for a person. path names the slot the reason is about, from the document root down, spelled the way the document spells its own keys: an object key as itself, a map id in text form, an array position in hex. Core never reads any of the three; it carries them so that a rule, a link and an application all say refusal the same way.
Route
type Route<C = unknown> = (request: Request, context: C) => Response | Promise<Response>;An HTTP route: (request, context) to a Response.
Server
interface Server { start(): Promise<void>; stop(): Promise<void>; readonly loader: Loader; }No block comment on this export.
ServerError
interface ServerError extends Error { readonly reason: string; }An error this package raises, with a reason a caller can branch on.
Reasons: missing (createServer without one of its three, a gate named that is not a loaded gate, or an ask naming a module that is not loaded or has no call), not-an-option (loader or props passed to createServer, which builds its own), route-conflict, no-accept (a share on a connection without accept), not-a-response (a route or a request hook answered with something else; reported, never thrown to a caller), started, over-bound (a request body past the listener's maxPayload), refused (the gate refused an ask), closed (an ask on a connection that has ended), failed (the module's call threw something that is not a refusal; the caller hears only that, and the error is reported), invalid-limit (a count or window that is not a positive number) and invalid-option (a listener setting of the wrong shape).
ServerEvent
type ServerEvent = { readonly kind: 'connection'; readonly at: number; readonly request: Request; } | { readonly kind: 'closed'; readonly at: number; readonly ms: number; } | { readonly kind: 'call'; readonly at: number; readonly name: string; readonly instance?: unknown; readonly args: unknown; readonly outcome: Outcome; readonly ms: number; } | { readonly kind: 'request'; readonly at: number; readonly method: string; readonly path: string; readonly status: number; readonly ms: number; readonly name?: string; } | { readonly kind: 'refused'; readonly at: number; readonly topic: string; readonly reasons: readonly WireReason[]; } | { readonly kind: 'failed'; readonly at: number; readonly name: string; readonly error: unknown; };What the server did, as it tells the modules that declare observe (design 260). Every event carries at, the time it was made. args and result are the caller's own objects, handed whole; a request's body is never here.
ServerHandlers
interface ServerHandlers { readonly failed?: ((name: string, error: unknown) => void) | undefined; }No block comment on this export.
ServerModule
interface ServerModule<C = unknown> { connection?(connection: Connection<C>): Ending | Promise<Ending>; call?(args: unknown, context: C, tools: Progress): unknown; readonly routes?: Readonly<Record<string, Route<C>>>; request?(request: Request, context: C): Response | undefined | Promise<Response | undefined>; observe?(event: ServerEvent, context: C): unknown; }What the server reads off a loaded module's instance. Every field is optional; a module with none of them is never asked about.
Nothing about who is on the other end is decided here: context is what the gate said.
ServerOptions
interface ServerOptions { readonly sources: readonly Source[]; readonly store?: unknown; readonly gate: Gate | string; readonly listener: Listener; readonly handlers?: ServerHandlers | undefined; readonly limits?: Limits | undefined; readonly origins?: readonly string[] | 'any' | undefined; }No block comment on this export.
Sliding
interface Sliding { take(key: string, now?: number): Taken; clear(key: string): void; }No block comment on this export.
Taken
type Taken = { readonly ok: true; } | { readonly ok: false; readonly retryAfter: number; };Whether one more fits, and when the next will when it does not.
Window
interface Window { readonly count: number; readonly windowMs: number; }How many inside how long.
createServer
createServer: (options: ServerOptions) => ServerMake a server.
Params
options.sources: where the modules come from;startloads every module every source listsoptions.store: the application's store, handed to every factory asstore; nothing else isoptions.gate: who may reach what; aGate, or the name of a module that is one.openis the trusted case, and there is no defaultoptions.listener: where connections and requests come from;node()shipsoptions.handlers.failed: where a hook, a route or the gate that threw is reportedoptions.limits.requests: how many requests one address may make in a window, before the gate; 600 a minute here,falsefor noneoptions.origins: where a browser may send a state-changing request or a handshake from; the request's own host here, a list of origins to add, or'any'
Returns start, stop, and the loader this server built. Nothing is loaded and nothing listens until start.
Throws a ServerError with reason missing when sources, gate or listener is absent, so a JavaScript caller cannot start a server with no gate by leaving the field out, or when gate is neither a name nor an object carrying identify and access; not-an-option when loader or props is present, because this builds its own; and invalid-limit when limits.requests is not { count, windowMs } of positive numbers or false.
Example
const server = createServer({
sources: [fromDirectory('./modules'), auth],
store,
gate: 'auth/Gate',
listener: node({ port: 8080 }),
});
await server.start();open
open: Gate<Record<string, never>> & AcceptingEveryone, everything, every commit.
As a gate, it identifies every caller with an empty context and allows every module: the microservice that knows nothing about users types gate: open, and that one word is what to grep for. As share handlers, it accepts every commit: link.share('doc', doc, open).
Example
const sources = [fromDirectory('./modules')];
const server = createServer({ sources, gate: open, listener: node({ port: 8080 }) });sliding
sliding: ({ count, windowMs }: Window) => SlidingA sliding-window counter: at most count hits per key in any windowMs span.
Params
window:{ count, windowMs }, both positive;windowMsat most 2147483647
Returns take and clear. A key not seen inside the window holds nothing; a key whose every hit has left the window is dropped when the map has grown past a few thousand; and past 65 536 keys one is dropped for each new one, the oldest under its count among the oldest few, else the oldest, so a flood of keys bounds the memory it costs at the price of a count, and a key at its count keeps it unless the flood puts that many keys at theirs. retryAfter is never more than the window.
Throws invalid-limit when either number is not positive or the window is over the bound.
Example
const attempts = sliding({ count: 5, windowMs: 900_000 });
const taken = attempts.take('ada@example.com');
if (!taken.ok) answer(429, { 'retry-after': String(taken.retryAfter) });@aweftjs/server/node
GivenServer
interface GivenServer extends NodeSettings { readonly server: HttpServer; readonly port?: undefined; readonly host?: undefined; }A server the application made (for TLS, or to share a port). This listener answers on it and never closes it.
NodeListener
interface NodeListener extends Listener { readonly port: number | undefined; }No block comment on this export.
NodeOptions
type NodeOptions = OwnServer | GivenServer;No block comment on this export.
NodeSettings
interface NodeSettings { readonly heartbeatMs?: number | undefined; readonly maxPayload?: number | undefined; readonly forwarded?: boolean | 'x-real-ip' | undefined; }What either kind of server takes.
OwnServer
interface OwnServer extends NodeSettings { readonly port: number; readonly host?: string | undefined; readonly server?: undefined; }A server of this listener's own, closed on stop. port: 0 takes a free one, readable after start.
node
node: (options: NodeOptions) => NodeListenerA listener over Node's http server and ws.
Params
options:{ port, host }for a server of its own, or{ server }for one the application made; either withheartbeatMs(nothing pings without it),maxPayload(1 MiB here,Infinityfor none) andforwarded(off here)
Returns the listener, with port readable once started.
Throws a ServerError with reason started when it is already started, and invalid-option when maxPayload is not a positive number or Infinity. A request body over maxPayload ends the body stream with over-bound rather than being read to the end.
Example
const listener = node({ port: 8080, heartbeatMs: 30_000 });
const server = createServer({ sources, store, gate, listener });Refusals
Every error this package raises carries a reason to switch on and a fix that says what to do. These are its reasons, from errors.txt.
| reason | fix |
|---|---|
closed | Open a new connection and ask again. |
failed | Read what handlers.failed was told under the module's name. |
invalid-limit | Give count a positive number of hits per window. |
invalid-limit | Give windowMs a positive number of milliseconds, at most 2147483647. |
invalid-option | Give maxPayload a positive number of bytes, or Infinity for no bound. |
missing | Load the module, and give it a call function. |
missing | Name a module the sources list whose instance is a gate, or pass a Gate object. |
missing | Pass all three: createServer({ sources, gate, listener }). |
missing | Pass an object with identify and access, open, or the name of a module that is one. |
no-accept | Pass accept in the share handlers, or open for the trusted case. |
not-a-response | Return a Response from the request hook, or undefined to decline. |
not-a-response | Return a Response from the route. |
not-an-option | Pass sources, and make anything you would have passed as a prop a module that others deps on. |
over-bound | Send a smaller body, or raise maxPayload on the listener. |
refused | Sign in, or ask for something the gate allows. |
route-conflict | Rename one of the two routes, or unload one of the modules. |
started | Call stop before starting it again. |
Recipes
The programs in the stack's gate that use this package, each a job someone would have.
recipes/full-stack: A page and a server in one directory: the page reaching the server in development through the dev server's proxy, and a sign-in that sets the cookie on that one originrecipes/static: A generated site served by the stack's own server: one process is the whole deployment, and the page still comes alive where it standsrecipes/health: A deploy's verification: the health endpoint polled until the shipped build is the one answering, and the two states a poll must not mistake for healthrecipes/logs: A page recorded end to end in a browser and the visit read back: an error, a rejection, a console line, a failed call on both sides, a commit's shape with a private slot absent, a typed character never stored, sign-in mid-visitrecipes/uploads: A page uploads pictures under the gate and they paint from/files/<id>; each refusal reaches the page with its reason, a module makes a file of its own, and the static battery behind it never sees a filerecipes/notify: Two pages of one user hear a send live and mark it read for each other, a device registered from the page, email and push against two fakes, a failed mail kept, a forged write refused, a restart, and a server with no store sending a contact form's mailrecipes/room: An act module stored on the server, run in a frame on the page: the board it shares reaches the server, its ask carries the page's identity, its own stage navigates on the page's URL under the host act, an error inside reaches the page's logs, and the frame paints but cannot fetchrecipes/posts-to-pages: Pages written while the application runs: a post published over a socket becomes a page, and a scheduled full write refreshes the sitemaprecipes/backend: The boot pattern to copy: a twelve-line boot file and a folder of modules, one holding a document, one the rules, one a scheduler, one the gate, and one configuring a battery modulerecipes/server: the package's own recipe