aweft

@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.

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) => Server

Make a server.

Params

  • options.sources: where the modules come from; start loads every module every source lists

  • options.store: the application's store, handed to every factory as store; nothing else is

  • options.gate: who may reach what; a Gate, or the name of a module that is one. open is the trusted case, and there is no default

  • options.listener: where connections and requests come from; node() ships

  • options.handlers.failed: where a hook, a route or the gate that threw is reported

  • options.limits.requests: how many requests one address may make in a window, before the gate; 600 a minute here, false for none

  • options.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>> & Accepting

Everyone, 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) => Sliding

A sliding-window counter: at most count hits per key in any windowMs span.

Params

  • window: { count, windowMs }, both positive; windowMs at 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) => NodeListener

A 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 with heartbeatMs (nothing pings without it), maxPayload (1 MiB here, Infinity for none) and forwarded (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.

reasonfix
closedOpen a new connection and ask again.
failedRead what handlers.failed was told under the module's name.
invalid-limitGive count a positive number of hits per window.
invalid-limitGive windowMs a positive number of milliseconds, at most 2147483647.
invalid-optionGive maxPayload a positive number of bytes, or Infinity for no bound.
missingLoad the module, and give it a call function.
missingName a module the sources list whose instance is a gate, or pass a Gate object.
missingPass all three: createServer({ sources, gate, listener }).
missingPass an object with identify and access, open, or the name of a module that is one.
no-acceptPass accept in the share handlers, or open for the trusted case.
not-a-responseReturn a Response from the request hook, or undefined to decline.
not-a-responseReturn a Response from the route.
not-an-optionPass sources, and make anything you would have passed as a prop a module that others deps on.
over-boundSend a smaller body, or raise maxPayload on the listener.
refusedSign in, or ask for something the gate allows.
route-conflictRename one of the two routes, or unload one of the modules.
startedCall 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 origin

  • recipes/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 stands

  • recipes/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 health

  • recipes/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-visit

  • recipes/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 file

  • recipes/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 mail

  • recipes/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 fetch

  • recipes/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 sitemap

  • recipes/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 module

  • recipes/server: the package's own recipe