aweft

@aweftjs/testing

The checks the rest of the stack is held to: the conformance suite, a model reading of the format to check real implementations against, the tier rule, and seeded randomness.

This package is an integrator. It sits outside the tier ordering because everything is allowed to depend on it, and nothing it exports ships to a user of the library. It is where a claim about the stack becomes a check that fails when the claim stops being true.

Conformance

A fixture is one case stated as plain JSON: the bytes, what they mean, the document before and the document after. Nothing about this repo's types is needed to read one, which is the point. An implementation in another language reads spec/fixtures/ and is conformant when it agrees with every file there.

import { checkFixture, type Fixture } from '@aweftjs/testing';

const fixture: Fixture = JSON.parse(readFileSync('spec/fixtures/001-object-slots.json', 'utf8'));
checkFixture(fixture);   // throws naming the fixture and the check that failed

checkFixture runs the case both directions. It decodes the stated bytes and compares the deltas to the stated JSON, encodes the stated JSON and compares to the stated bytes, applies the commits and compares the document to the stated ending, and re-runs each commit with its own deltas shuffled and reversed to check that the order they arrive in changes nothing.

The commits themselves keep their order. Section 4 of the format requires that, and only the deltas inside one commit are unordered.

The encode direction matters more than it looks. Bytes checked only by decoding them are checked against the package that produced them, so the fixture asks the implementation whether it agrees with itself. The stated JSON is authored (the documents by hand, the delta order by the generator's own rule), so encoding it asks a question the decoder's own output cannot answer. The bytes in a fixture were produced by the reference encoder when the fixture was generated; what anchors that encoder to the prose of the format is a pair of commits spelled out by hand, head by head, in codec's own tests. A second implementation proves itself by agreeing with those fixtures byte for byte.

checkInvalidFixture is the other half. Each case in spec/fixtures/invalid/ names a reason and a stage, and an implementation that refuses the input for a different reason has not agreed on the format, it has agreed on rejecting one string.

import { checkInvalidFixture, type InvalidFixture } from '@aweftjs/testing';
checkInvalidFixture(JSON.parse(readFileSync('spec/fixtures/invalid/011-truncated.json', 'utf8')));

Checking a real implementation

Both check functions take an Applier: one reading of the format, as a function from a starting document and some commits to the document reached.

type Applier = (initial: DocumentJson, commits: readonly Commit[]) => DocumentJson;

A fixture states its commits as CommitJson, which is bytes plus JSON. Your applier is never handed those: checkFixture decodes each one and calls you with Commit, the codec type, with real byte-string ids and reference objects. Read the format from spec/format.md and the codec types, not from the fixture JSON shape.

modelApplier is the default, and it is the harness's own reading: plain data, no reactivity, written from the specification rather than from any package. Passing a second applier is how a real implementation gets held to the same suite:

checkFixture(fixture, myApplier);

Two independent readings reaching the same document from the same bytes is the evidence. One implementation agreeing with itself is not.

When your applier throws on a fixture that is valid, the failure names the fixture, the delta order it was running, and the reason you threw. When it returns the wrong document, the failure prints both documents. Those are the two ways a second reading goes wrong, and the suite is built to tell them apart.

The document model

DocumentJson is flat: observables keyed by id in text form, with the root named separately. Flat rather than nested because an observable can be named from more than one place, and a nested spelling would have to pick one and quietly lose the others.

import { applyCommit, canonicalJson } from '@aweftjs/testing';

const after = applyCommit(before, commit);        // a new document, the input untouched
canonicalJson(after) === canonicalJson(expected)  // how two documents are compared

applyCommit checks every delta before applying any, which is what makes a commit atomic: a commit that breaks a rule leaves the document exactly as it was. Compare documents through canonicalJson rather than directly, or the order keys happened to be inserted in becomes part of the answer.

The recording host

recordingDocument() is a light document from @aweftjs/dom that writes down every node operation, so a test asserts what a mount did to the tree and not only what the tree looks like after.

import { recordingDocument } from '@aweftjs/testing';

const { document, ops } = recordingDocument();
mount(document.body, h('p', {}, 'hi'));
ops;            // ['insert <p> into <body> before end']
ops.length = 0; // clear between the steps of a test

One line per insert, remove, replace, text write, attribute write and clear, in order. A node made elsewhere joins the recording when it is inserted.

The tier rule

Packages are numbered, and a package may import downward only. boundaries.json is the table, this package holds the check, and packages/testing/scripts/check-boundaries.ts runs it over the imports that actually exist.

import { checkGraph } from '@aweftjs/testing';
checkGraph([['core', 'codec']], table);   // [] means the graph is legal

Runtime code is what the rule governs. Tests, scripts and examples are outside it, because a suite importing this harness creates no dependency in anything a user installs, and the definition of done requires exactly that import. They are still printed on every run, so an exclusion that starts hiding something is visible rather than silent.

Testing a module in isolation

loadModule instantiates one module the way @aweftjs/modules would, with its dependencies replaced by whatever the test hands over, so a module's own tests do not need a directory, a document or the modules it depends on.

import { loadModule } from '@aweftjs/testing';
import * as Create from './modules/posts/Create.ts';

const { instance, stop } = await loadModule({
	exports: Create,
	imports: { 'auth/Session': { userOf: () => 'u_1' }, 'lib/Log': { log: () => {} } },
	config: { maxLength: 10 },     // merged over the module's defaults, as an extension would be
	props: { site: 'test' },       // what the application would pass the loader
});

assert.equal((instance as Post).make('a long title'), 'post:a long ti');
await stop();                    // unloads it, calling the instance's stop if it has one

Every name in the module's deps needs an entry in imports, keyed by the full dependency name; a missing one is refused by name before anything is instantiated. The module is loaded through the real loader, so imports reaches the factory keyed by the last segment of each name, exactly as it would in an application.

Testing a whole backend

loadModule above is one module with its dependencies stubbed. loadServer is the level above: a real server, real modules, and the gate the application actually uses, on a listener that opens nothing. It takes createServer's options without the listener, which is the one part a test cannot supply.

import { loadServer } from '@aweftjs/testing';

const server = await loadServer({
	sources: [fromDirectory(modules), auth],
	store: createStore({ driver: memoryDriver(), declare: { ...paths } }),
	gate: 'auth/Gate',
});

const answer = await server.fetch('/api/session', { method: 'POST', body });
const page = await server.open({ headers: { cookie } });
await page.asks.ask('board/Mine');
await server.stop();

A throwaway or a server the suite never stops keeps its own resources alive: a cluster is a child process and a running one holds the test process open. Stop them in an after.

fetch and open are the two seams a listener feeds, so everything a deployment does goes through the same code a deployment does it with. open answers a socket, a link and the call channel; it throws when the gate refuses the handshake, carrying the status the gate answered with, because a browser handed a refusal gets a failed connection and not a response to read.

Signing in is yours. The sequence is a POST to your battery's session route, the Set-Cookie off the answer, and open with that cookie. It is five lines and it is not here, because putting it here would tie this package to one battery's routes and one idea of what a session is. recipes/full-stack/tests/board.test.ts is those five lines.

The security suite

securityChecks({ gate }) is the suite a server passes rather than claims, the way a runner passes roomChecks(). Each case is one named obligation citing the ASVS 5.0 requirements it proves (docs/security.md has the table), run against a server you start, so an application that boots its own modules behind the same server proves the same things about itself.

import { loadServer, securityChecks } from '@aweftjs/testing';

for (const c of securityChecks({ gate: 'auth/Gate' })) {
	test(`${c.requirements.join(' ')}: ${c.name}`, () => c.run((given) => loadServer({
		...given,
		sources: [fromDirectory(modules), auth, ...given.sources],
		store: createStore({ driver: memoryDriver(), declare: { ...paths } }),
	})));
}

start is yours: it loads the probe modules the suite hands it beside your own, starts under the gate the suite names (yours, with one rule of its own composed on top), passes the handlers the suite gives it, and answers { fetch, open, server, stop }, which is what loadServer answers. The suite speaks the session routes by their documented shape, POST and DELETE /api/session as @aweftjs/auth answers them, and imports no battery; a target with no session battery fails the session cases at the first sign-up, naming that. Nothing is skipped.

The suite is append-only: a case is added for every hole ever found and none is removed. It runs over the harness, where fetch may be handed a full URL, which is how the suite says a request arrived over TLS. A target over a real port would adapt fetch and open to it, and none ships: the transport's own bounds are pinned by the listener's tests.

npm run security ties the suite to the table: every requirement the table gives to the stack names a case here or a test elsewhere that exists, and every case cites a requirement the table gives to the stack.

Two ends of one socket

socketPair() is what loadServer opens over, and it is exported because a suite that is about the transport itself wants one without a server.

const [near, far] = socketPair();       // near is the page's end, far the server's
const link = connect(fromWebSocket(near));

What one sends the other hears on a microtask, never synchronously. A send before the socket is open or after it closed reaches nobody, as a real one refuses and drops. Closing either closes both and fires close at both; a second close does nothing. A listener that throws is recorded on thrown and the listeners after it still run, because one socket here carries both the link and the call channel and a throwing link would otherwise mean an ask that never settles.

An end also carries peer and fire. fire('open', {}) is how a suite driving a client's retry hands the page the event a browser would have fired. A listener added while an event is dispatching does not hear that event, which is what a real EventTarget does. Pass 0 for a near end that starts connecting, which is the order a page sees: the server is handed an accepted socket before the page is told about its own.

This is not sync's inProcess. That answers two channels, and a channel is one plane; a socket carries the link and the call channel together and has the readyState and the close event a retry reads.

A database that goes away

throwaway() on the /postgres subpath starts an empty cluster in a temporary directory on a free port, and stops it when you are done.

import { throwaway } from '@aweftjs/testing/postgres';

const db = await throwaway();
const store = createStore({ driver: postgresDriver(await db.pool()) });
// ...
await db.stop();

Every pool() is in a schema of its own, so two checks in one file never see each other's tables, which is also how two applications share one database. The throwaway owns every pool it handed out and ends them all in stop, so do not end one yourself: pg throws "Called end on pool more than once" for the second call, and that throw is its, not this package's.

embedded-postgres and pg are optional peers: a project that never opens a store installs neither, and one that asks for a throwaway without them gets peer-not-installed naming what to install. throwaway takes how the peers are reached, so that refusal can be reached without uninstalling anything.

Reading the page a test drives

audit and walk on the /browser subpath take the page object a browser driver already handed the test and answer what a screen reader and a keyboard would find there (design 267). Neither throws on a finding; the test says what a finding means for it.

import { audit, walk } from '@aweftjs/testing/browser';

const { violations } = await audit(view);
assert.deepEqual(violations, [], violations.map((v) => `${v.rule}: ${v.help}`).join('\n'));

const { stops, problems } = await walk(view);
assert.deepEqual(problems, [], problems.map((p) => `${p.reason} at ${p.target}: ${p.fix}`).join('\n'));

audit puts axe-core into the page and runs it over the document, or over options.root, with the WCAG 2.x A and AA tags (options.tags replaces the list). A violation carries axe's rule id, its wcag* tags as axe writes them (wcag2aa, wcag143), its help sentence and link, and the nodes as a selector and the markup. Run it once per colour scheme: the page's colours are what it measures, and a second run on the same page reuses the script.

walk presses Tab from the top of the page until the focus comes back round, or options.limit (300) presses have gone by, and answers every stop (tag, id, role, name, and whether it draws a ring while it matches :focus-visible) and the problems: focus-not-visible for a stop with no outline and no box-shadow, focus-stuck when a press left the focus where it was, focus-loops when a press sent the focus back round the page without letting it leave, unreachable for a focusable element the walk never landed on, never-cycles when the limit ran out. Whatever the page had focused loses it first. A ring is the element's own outline or box-shadow, or one an ancestor draws for the control inside it through a rule about focus, and nothing else, so a page that shows its focus by changing a border colour is reported as ringless. A radio group is one stop, an element with no box (inside a closed details, a closed dialog, a hidden parent) is not expected, and a frame, a shadow host or a media element's own controls is one stop the focus moves inside without the walk reading where. Run it with no modal dialog open: the page behind one is inert and the walk reports it unreachable.

The page is structural: evaluate(source), addScriptTag({ content }) and keyboard.press(key), which Playwright's Page satisfies (the suite passes one), and this package imports no browser driver. axe-core is an optional peer: a project that never audits installs nothing, and one that asks without it gets axe-not-installed. options.locate says where the script is when the installed one is not the one to use, and is how that refusal is reached without uninstalling anything.

What neither reads is the criteria that need a person: meaning carried by colour alone, the order of the reading, a heading that describes its section, a time limit, an error message that says what to do. The build refuses what the source settles (@aweftjs/build, The access rules) and the mount throws on a nameless Button and a page with no lang or title (@aweftjs/ui); these two read the rendered page for the rest.

Letting scheduled work run

settle() yields to the timer queue ten times, so work that schedules more work gets to run. A single await drains microtasks and nothing else, which is why a suite that awaits once and asserts sees a tree that is half settled. Pass a whole number of one or more for a different count; anything else is refused rather than settling for nothing and failing an assertion further on.

Seeded randomness

A property test is worth having only if a failure can be run again, so a failing assertion prints its seed and that seed reproduces the run exactly.

import { randomBelow, randomFrom } from '@aweftjs/testing';

const random = randomFrom(20260901);
const victim = items[randomBelow(random, items.length)];

There is one generator here rather than one per suite. Copies of the same shift register drifted apart in small ways, and a seed that reproduces a failure under one copy reproduces nothing under another.

The words check

npm run words reads every tracked file for the vocabulary of how the stack was built (who decided a thing, when, through which review) and prints each line that carries a word from the list, with what to write instead. The list is in packages/testing/src/words.ts, one entry per word with its fix. It reads text, not syntax, so it matches the spellings the process used and leaves the words the code needs alone.

import { checkWords } from '@aweftjs/testing';
checkWords([{ path: 'note.md', text }]);   // [] means the text is clean

With paths, node packages/testing/scripts/check-words.ts docs/design reads those files instead of the whole tree.

What a suite has to name

npm run exercised reads each package's surface.txt and errors.txt beside its tests and fails a covered package whose own suite never names one of its value exports, or never quotes one of its refusal reasons (design 285). Both files are generated: surface.txt by npm run surface, one export per line as value name: type with a subpath's lines prefixed by the subpath, and errors.txt by npm run errors, one reason: fix per line. A test counts when it is not the surface list and not a white-box internal.* file; a name in a comment does not count, and a reason as the whole of a regular expression does, since a refusal's message opens with its reason. The covered packages are listed in the script and the list only grows; the rest have their counts printed. testing is exempt, because its surface is the suites of the other packages.

import { checkExercised } from '@aweftjs/testing';
checkExercised('store', surface, errors, tests);   // [] means the suite names everything

The operator sweep

npm run sweep -- <package> copies the repo into a scratch directory, flips one operator at a time in that package's sources (a comparison, a logical operator, a boolean a function returns), runs the package's own suite against each flip, and prints every flip the suite let through with its file, line and operator (design 287). It runs on request and never in the gate: one flip is one run of the suite, two test files at a time so the machine stays usable, and --only=<file>[,<file>] narrows it to the files you are working on. A survivor is either killed by a test or written down as equivalent with the reason, wherever the change that ran the sweep is written up.

import { flipSite, sweepSites } from '@aweftjs/testing';
const [first] = sweepSites(source);        // where the file can be flipped, in order
const flipped = flipSite(source, first!);  // the same text with that one operator changed

Running the gate

npm test at the root is the whole gate: typecheck, the dependency rules, the tier rule over real imports, every package's suite with its coverage threshold, and every proof program. packages/testing/scripts/run-tests.ts is the part that runs the suites, and generate-fixtures.ts rewrites spec/fixtures/ from the generator entries.

API

Every export of @aweftjs/testing, its signature as the compiler resolves it, and its block comment.

@aweftjs/testing

AdapterCheck

interface AdapterCheck { readonly name: string; run(make: MakeAdapter): Promise<void>; }

One named obligation an adapter has to meet.

Applier

type Applier = (initial: DocumentJson, commits: readonly Commit[]) => DocumentJson;

One reading of the format, as a function.

The suite is the same for every implementation, and there is more than one: a model that stores plain data, and a real reactive tree. Both must reach the same document from the same bytes, which is the point of running the fixtures twice.

Params

  • initial: the document to start from

  • commits: the commits to apply, in order, each whole

Returns the document reached. Throws with a stated reason when a commit is refused.

CommitJson

interface CommitJson { readonly bytes: string; readonly deltas: readonly DeltaJson[]; readonly tag?: string; }

A commit as plain JSON: its bytes in hex, the deltas they carry, and an optional tag.

Connected

interface Connected { readonly socket: PairedSocket; readonly link: Link; readonly asks: Requests; readonly request: Request; }

What open answers when the gate let the handshake through.

DeltaJson

interface DeltaJson { readonly type: DeltaType; readonly id: string; readonly ref: RefJson; readonly value?: ValueJson; }

A delta as plain JSON, with value absent exactly when the type is remove.

DocumentJson

interface DocumentJson { readonly root: string; readonly observables: Readonly<Record<string, ObservableJson>>; }

A whole document as plain JSON.

Flat, keyed by id in text form, with the root named separately. Flat rather than nested because an observable can be named from more than one place, and a nested spelling would have to pick one of them and quietly lose the others.

DriverCheck

interface DriverCheck { readonly name: string; run(make: MakeDriver): Promise<void>; }

One named obligation a driver has to meet.

FileAdapter

interface FileAdapter { readonly name: string; put(key: string, stream: ReadableStream<Uint8Array>, options: { readonly type: string; readonly size: number; }): Promise<void>; open(key: string): Promise<ReadableStream<Uint8Array> | undefined>; head(key: string): Promise<{ readonly size: number; } | undefined>; remove(key: string): Promise<void>; }

The part of an uploads adapter this suite exercises.

Stated structurally rather than imported, so testing does not depend on uploads: the package it checks depends on it in turn.

Fixture

interface Fixture { readonly name: string; readonly description: string; readonly initial: DocumentJson; readonly commits: readonly CommitJson[]; readonly final: DocumentJson; }

One conformance case: bytes, what they mean, and the document they produce.

This is the contract an implementation in another language reads. initial is the document before, commits are applied in order, final is the document after, and every one of them is stated as plain JSON so that nothing about this repo's types is needed to consume it.

initial and final are written by hand rather than generated, which is the point: an ending document produced by the implementation under test would only say the implementation agrees with itself.

InvalidFixture

interface InvalidFixture { readonly name: string; readonly description: string; readonly stage: 'decode' | 'apply'; readonly reason: string; readonly bytes: string; readonly initial?: DocumentJson; }

One case that must be refused, and the reason it must be refused for.

reason is the contract, not the message. Two implementations that both reject an input for different stated reasons have not agreed on the format, they have agreed on rejecting one string. stage says where the refusal is due: decode for bytes that are not a commit, apply for a commit that is well formed and cannot be applied, which is the only case that needs initial.

ListenerCheck

interface ListenerCheck { readonly name: string; run(make: MakeListener): Promise<void>; }

One named obligation a listener has to meet.

LoadedModule

interface LoadedModule { readonly instance: unknown; stop(): Promise<void>; }

The instance the module produced, and the way to unload it.

LoadedServer

interface LoadedServer { readonly server: Server; fetch(path: string, init?: RequestInit): Promise<Response>; open(options?: OpenOptions): Promise<Connected>; stop(): Promise<void>; }

A running server with no port, and the two seams a listener would feed it.

MadeListener

interface MadeListener { readonly listener: Listener; url(): string; }

No block comment on this export.

MakeAdapter

type MakeAdapter = () => Promise<FileAdapter> | FileAdapter;

What a check needs: an adapter nobody else is using.

MakeDriver

type MakeDriver = () => Promise<StoreDriver> | StoreDriver;

What a check needs: a driver nobody else is using, and a way to be rid of it.

MakeListener

type MakeListener = () => Promise<MadeListener> | MadeListener;

What a check needs: a listener nobody else is using, and the URL to reach it at once it has started.

MakeRunner

type MakeRunner = () => Runner;

What a check needs: a fresh runner nobody else is using.

Manifest

interface Manifest { readonly name: string; readonly dependencies?: Readonly<Record<string, string>> | undefined; readonly devDependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependenciesMeta?: Readonly<Record<string, { readonly optional?: boolean; }>> | undefined; }

The dependency-bearing part of one package.json.

ModuleUnderTest

interface ModuleUnderTest { readonly exports: ModuleExports; readonly imports?: Readonly<Record<string, unknown>>; readonly config?: Readonly<Record<string, unknown>>; readonly props?: Readonly<Record<string, unknown>>; }

No block comment on this export.

ObservableJson

interface ObservableJson { readonly kind: ObservableKind; readonly slots: Readonly<Record<string, ValueJson>>; }

One observable as plain JSON: its kind, and its slots by key.

OpenOptions

interface OpenOptions { readonly headers?: Record<string, string> | undefined; readonly url?: string | undefined; readonly peer?: Peer | undefined; }

What open may be told about the handshake it is about to make.

Opened

interface Opened { readonly socket: { readonly readyState: number; send(data: string | Uint8Array): void; close(): void; }; readonly link: Link; readonly asks: Requests; }

A connection the target opened: the socket, the link and the call channel over it.

PackageInfo

interface PackageInfo { readonly tier: number | 'integrator'; readonly plane: Plane; readonly imports: readonly string[] | '*'; }

A package's place in the table: how high it sits, which side it runs on, what it reaches.

PairedSocket

interface PairedSocket extends SocketLike { peer: PairedSocket | undefined; readonly sent: (Uint8Array | string)[]; readonly thrown: unknown[]; fire(type: string, event: { data?: unknown; }): void; }

One end of a pair: a socket, plus what it was asked to send.

Plane

type Plane = 'client' | 'server' | 'isomorphic';

Which side a package runs on. isomorphic may be imported from either.

PublishManifest

interface PublishManifest { readonly name: string; readonly version?: string | undefined; readonly private?: boolean | undefined; readonly engines?: Readonly<{ node?: string | undefined; }> | undefined; readonly files?: readonly string[] | undefined; readonly exports?: Readonly<Record<string, string | Readonly<Record<string, string>>>> | undefined; readonly dependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependencies?: Readonly<Record<string, string>> | undefined; readonly publishConfig?: Readonly<{ access?: string | undefined; }> | undefined; readonly scripts?: Readonly<Record<string, string>> | undefined; }

The publishing-relevant part of one package.json.

Recording

interface Recording { readonly document: DocumentLike & { readonly body: ElementLike; readonly head: ElementLike; }; readonly ops: string[]; }

No block comment on this export.

RefJson

interface RefJson { readonly kind: ObservableKind; readonly key: string; }

A ref as plain JSON, with the key always a string so a fixture stays readable.

An object key is itself, an array position is hex, and a map key is the id in its textual form: sixteen base64url characters, as idToText writes it, not hex.

RoomCheck

interface RoomCheck { readonly name: string; run(make: MakeRunner): Promise<void>; }

One named obligation the window has to meet behind a runner.

SecurityCase

interface SecurityCase { readonly name: string; readonly requirements: readonly string[]; }

One case of the suite, as much of it as the table needs.

SecurityCheck

interface SecurityCheck { readonly name: string; readonly requirements: readonly string[]; run(start: StartTarget): Promise<void>; }

One named obligation a server has to meet, and the requirements it proves.

SecurityRow

interface SecurityRow { readonly id: string; readonly level: string; readonly owner: string; readonly check: string; }

One line of the table.

SecurityTarget

interface SecurityTarget { fetch(path: string, init?: RequestInit): Promise<Response>; open(options?: { readonly headers?: Record<string, string> | undefined; readonly url?: string | undefined; }): Promise<Opened>; readonly server: Server; stop(): Promise<void>; }

A started application the suite runs against. loadServer answers this shape as it is.

Site

interface Site { readonly line: number; readonly column: number; readonly from: string; readonly to: string; }

One place a source file can be flipped.

StartOptions

interface StartOptions { readonly sources: readonly Source[]; readonly gate: Gate | string; readonly handlers: { failed(name: string, error: unknown): void; }; }

What the suite hands start: its own probe modules, the gate the target must run under, and where the server reports a module that broke.

StartTarget

type StartTarget = (options: StartOptions) => Promise<SecurityTarget>;

The caller's boot. It loads the sources given beside its own (a store declaring the auth paths, and a session battery answering the auth routes), starts under the gate given, and hands the server the handlers given, so a module the suite breaks on purpose is reported to the suite rather than raised where nothing catches it.

StoreDriver

interface StoreDriver { write(write: { doc: string; root: string; rootKind: string; rows: readonly { id: string; kind: string; edge?: { parent: string; slot: string; } | null; set: Record<string, unknown>; unset: readonly string[]; }[]; dropped: readonly string[]; body: Uint8Array; project?: Record<string, unknown>; }): Promise<number>; declare(declaration: Readonly<Record<string, readonly string[]>>): Promise<void>; find(lookup: { where: { field: string; op: string; value: unknown; }; sort?: { field: string; direction: 'asc' | 'desc'; }; limit?: number; after?: string; }): Promise<{ doc: string; fields: Record<string, unknown>; cursor: string; }[]>; scan(limit: number, after?: string): Promise<{ doc: string; fields: Record<string, unknown>; cursor: string; }[]>; create(doc: string, root: string, rootKind: string): Promise<boolean>; read(doc: string): Promise<{ root: string; rootKind: string; rows: unknown[]; } | null>; since(doc: string, seq: number): Promise<{ seq: number; body: Uint8Array; }[]>; head(doc: string): Promise<number>; truncate(doc: string, seq: number): Promise<void>; forget(doc: string, ids: readonly string[]): Promise<void>; remove(doc: string): Promise<void>; close(): Promise<void>; }

The part of a store driver this suite exercises.

Stated structurally rather than imported, so testing does not depend on store: an integrator may know everything, but a suite that forced a dependency edge would make the package it checks unable to depend on it in turn.

TestExists

type TestExists = (file: string, text: string, kind: 'test' | 'doc') => boolean;

Whether a file holds what a row names: a test of that title, or a document section under that heading.

TestSource

interface TestSource { readonly path: string; readonly text: string; }

One test file of the package.

ThemeSource

interface ThemeSource { readonly path: string; readonly text: string; }

One file to read.

ThemeViolation

interface ThemeViolation { readonly path: string; readonly line: number; readonly where: string; readonly property: string; readonly literal: string; readonly fix: string; }

One value written where it stands rather than named.

ThrownRefusal

interface ThrownRefusal { readonly reason: string; readonly fix: string; }

One refusal a package can throw: the token, and the remedy offered with it.

Named for what it is rather than Refusal, which core and schema already use for the different thing an application's own rule returns.

ValueJson

type ValueJson = null | boolean | number | string | { readonly bytes: string; } | { readonly ref: string; readonly kind: ObservableKind; readonly edge: EdgeKind; };

A value as plain JSON: a primitive, { bytes } for a byte string, or a reference.

Violation

interface Violation { readonly from: string; readonly to: string; readonly rule: 'upward-tier' | 'cross-plane' | 'unknown-package' | 'not-allowed'; readonly detail: string; }

One illegal import edge, and which rule it broke.

WordSource

interface WordSource { readonly path: string; readonly text: string; }

One file to read.

WordViolation

interface WordViolation { readonly path: string; readonly line: number; readonly word: string; readonly fix: string; readonly text: string; }

One line carrying a word from the list.

adapterChecks

adapterChecks: () => AdapterCheck[]

The obligations of an adapter, as named checks.

Returns the checks. Run each with a fresh adapter.

Example

for (const c of adapterChecks()) test(c.name, () => c.run(() => directory(tmp)));

applyCommit

applyCommit: (doc: DocumentJson, commit: Commit) => DocumentJson

Apply one commit to a document.

Params

  • doc: the document before

  • commit: the commit to apply, its deltas in any order

Returns a new document. The input is not modified.

Throws a CodecError naming the rule broken. Every delta is checked before any is applied, which is what makes a commit atomic: a commit that would break a rule leaves the document exactly as it was, so no observer ever sees the halfway state.

aweftPackageOf

aweftPackageOf: (specifier: string) => string | undefined

The package a specifier reaches inside this stack, or undefined for anything else.

Params

  • specifier: a module specifier as written

Returns the package name for @aweftjs/<name> and any subpath under it. A subpath import is still an edge to that package; whether reaching a subpath is legal is the exports map's question, not this one's.

Example

aweftPackageOf('@aweftjs/dom/router'); // 'dom'

canonicalJson

canonicalJson: (value: unknown) => string

A document as one string, with every key in a stated order.

Two documents are the same document when these strings match. Comparing the objects directly would make the order keys happen to be inserted in part of the answer, and applying the same commit in two orders inserts them in two orders.

checkEdge

checkEdge: (from: string, to: string, table: Readonly<Record<string, PackageInfo>>) => Violation | null

Check one import edge against the boundary rules.

Params

  • table: the tier and plane map. boundaries.json at the repo root is the one copy

Returns a Violation, or null when the edge is allowed.

checkExercised

checkExercised: (name: string, surface: string, errors: string, tests: readonly TestSource[]) => string[]

Every export and every reason the suite does not name.

Params

  • name: the package, for the report

  • surface: the text of its surface.txt

  • errors: the text of its errors.txt, empty when it refuses for no reason

  • tests: its test files; the ones that count are chosen here, so hand in the directory

Returns one line per miss, naming the package and the export or reason, empty when the suite names them all. An export counts as named when it appears as a whole word outside a comment; a reason when it appears quoted, or as the whole of a regular expression, since a refusal's message opens with its reason and assert.throws(fn, /reason/) is the shortest way to ask.

Example

checkExercised('store', surface, errors, tests);
// ['store: projectionOf is exported and no test names it', "store: 'not-open' is a refusal and no test quotes it"]

checkFixture

checkFixture: (f: Fixture, applier?: Applier) => void

Run one fixture.

Params

  • f: the fixture, already parsed

Throws an Error naming the fixture and the check that failed, including when the applier itself throws on a case that is valid. Returns nothing on success.

checkGraph

checkGraph: (edges: readonly (readonly [string, string])[], table: Readonly<Record<string, PackageInfo>>) => Violation[]

Check a whole import graph.

Params

  • edges: every package-to-package import in the repo

  • table: the tier and plane map

Returns every violation found, in input order. Empty means the graph is legal.

checkInvalidFixture

checkInvalidFixture: (f: InvalidFixture, applier?: Applier) => void

Run one rejection fixture.

Params

  • f: the fixture. stage says whether the bytes must be refused when decoded, or decode cleanly and be refused when applied to initial.

Throws an Error if the input was accepted, or refused for a different stated reason than the fixture names. Rejecting for the wrong reason counts as a failure: a format whose implementations disagree about why something is invalid has not been specified.

checkManifests

checkManifests: (manifests: readonly Manifest[], allowed: readonly string[], perPackage?: Readonly<Record<string, readonly string[]>>) => string[]

Every dependency declaration the allowlist does not cover.

Params

  • manifests: the packages' manifests

  • allowed: dependency names permitted anywhere; @aweftjs/ packages are always permitted. An entry ending /* covers a family, so @iconify-json/* is one entry

  • perPackage: extra names permitted for specific packages, by package name, in the same spelling

Returns one line per violation, naming the package and the dependency, empty when clean. A third-party peer that is not marked optional is a violation even when the allowlist names it.

Example

const violations = checkManifests(manifests, ['typescript'], {});

checkPublishing

checkPublishing: (manifests: readonly PublishManifest[]) => string[]

Every way a set of manifests would publish something other than what design 256 describes.

Params

  • manifests: the packages' manifests, as package.json holds them

Returns one line per violation, naming the package and what is wrong, empty when clean. The version check compares the manifests against each other rather than against a constant, because the packages version in lockstep and no file is the record of which version that is.

Example

const violations = checkPublishing([JSON.parse(readFileSync('packages/core/package.json', 'utf8'))]);

checkSecurityTable

checkSecurityTable: (rows: readonly SecurityRow[], cases: readonly SecurityCase[], testExists: TestExists) => string[]

Every way the table and the suite could disagree.

Params

  • rows: the table

  • cases: the suite's cases, by name and the requirements each cites

  • testExists: answers whether test: <file>#<title> names a test that is there

Returns one line per problem, empty when the table holds: an id that is not Vn.n.n or appears twice; a level that is not 1 or 2; an owner that is not one of the four words; an empty check; a stack row whose check is not suite: <name> naming a case, test: <file>#<title> naming a test that exists, or doc: <file>#<heading> naming a section that exists; a case citing an id the table lacks or does not mark stack.

Example

const problems = checkSecurityTable(rows, securityChecks({ gate: open }), exists);

checkTheme

checkTheme: (files: readonly ThemeSource[]) => ThemeViolation[]

Every value written where it stands rather than named.

Params

  • files: the source files to read, each with the path to report it under

Returns one entry per literal, in file then position order. Empty means every value in a CSS position came through a $name.

Example

const found = checkTheme([{ path: 'a.tsx', text: readFileSync('a.tsx', 'utf8') }]);

checkWords

checkWords: (sources: readonly WordSource[]) => WordViolation[]

Reads every line of every source against the list and reports each line that carries a word.

Params

  • sources: the files, each with the path it will be reported under

Returns one violation per matching line and rule, in file order then line order. A line carrying two words from the list is reported twice. An empty array means the text is clean.

Example

checkWords([{ path: 'a.md', text: 'Decided by: the maintainer.' }]);
// [{ path: 'a.md', line: 1, word: 'a decided-by header', fix: 'drop the line', text: '...' }]

commitToJson

commitToJson: (commit: Commit, bytes: Uint8Array<ArrayBufferLike>) => CommitJson

A commit and its bytes in the shape a fixture states them, for comparing against one.

deltaFromJson

deltaFromJson: (d: DeltaJson) => Delta

The delta a JSON one names.

This is the direction that makes a fixture's bytes checkable against something beside the decoder: the stated JSON is ordered independently of the encoder's sort, so encoding it and comparing to the stated bytes asks a question the decoder's own output cannot answer.

deltaToJson

deltaToJson: (d: Delta) => DeltaJson

A delta in its JSON form, as a fixture states it.

driverChecks

driverChecks: () => DriverCheck[]

Every obligation a store driver has.

Returns the checks, each of which takes a factory for a driver nobody else is using and throws on failure.

Example

for (const check of driverChecks()) test(check.name, () => check.run(() => memoryDriver()));

errorLines

errorLines: (refusals: readonly ThrownRefusal[]) => string[]

The refusals as the lines that go in errors.txt.

Params

  • refusals: what errorsOf returned

Returns one line per refusal, reason then colon then the fix.

Example

const text = errorLines(refusals).join(String.fromCharCode(10));

errorsOf

errorsOf: (files: readonly string[], program: Program) => ThrownRefusal[]

Every refusal thrown in these files.

Params

  • files: absolute paths to the source files of one package

  • program: a program those files belong to

Returns one entry per distinct reason and fix, sorted by reason then fix. A reason thrown from several places with several remedies appears once per remedy, because that difference is exactly what a reader needs to see in a diff.

Throws not-in-program when a path is not part of the program handed in, reason-not-literal when a token is built at runtime, and fix-missing or fix-not-literal when the remedy is absent or built, which would make the index a description of code rather than of what a reader is told.

Example

const refusals = errorsOf(files, program);

flipSite

flipSite: (text: string, site: Site) => string

The source with one site flipped.

Params

  • text: the source the site was found in

  • site: one of its sites

Returns the same text with that one operator replaced, every other byte unchanged.

Example

flipSite('if (a < b) go();', { line: 1, column: 5, from: ' < ', to: ' <= ' });
// 'if (a <= b) go();'

idFromText

idFromText: (text: string) => Id (from @aweftjs/codec)

The id behind its text form.

Params

  • text: sixteen base64url characters, as idToText writes them

Returns the 12 bytes, as an id.

Throws when the length or the alphabet is wrong. Round tripping through text is lossless, so anything that does not round trip was never one of these ids.

Example

const id = idFromText(slotKey);

idToText

idToText: (id: Id) => string (from @aweftjs/codec)

The textual form of an id: sixteen base64url characters.

Params

  • id: an id. Raw bytes reach one through assertId

Returns the text form, safe in a URL, a log line, or a JSON object key.

Throws a CodecError with reason invalid-id when the bytes are not ID_BYTES long.

listenerChecks

listenerChecks: () => ListenerCheck[]

Every obligation a server listener has.

Returns the checks, each named, each taking a function that makes a fresh listener and says where to reach it.

Throws each check's run throws an assertion when the listener misses that obligation, and no-status-line when a raw upgrade request is closed with nothing written back.

Example

for (const c of listenerChecks()) test(c.name, () => c.run(() => {
  const listener = node({ port: 0 });
  return { listener, url: () => `http://127.0.0.1:${listener.port}` };
}));

loadFixtures

loadFixtures: (dir: string | URL) => Fixture[]

Every fixture in a directory, sorted by filename.

Params

  • dir: the fixtures directory, as a URL or an absolute path ending in a slash

Returns the parsed fixtures, in filename order.

Example

for (const f of loadFixtures(new URL('../../../spec/fixtures/', import.meta.url))) {
  checkFixture(f);
}

loadInvalidFixtures

loadInvalidFixtures: (dir: string | URL) => InvalidFixture[]

Every rejection fixture in a directory, sorted by filename.

Params

  • dir: the invalid-fixtures directory, as a URL or an absolute path ending in a slash

Returns the parsed rejection fixtures, in filename order.

Example

for (const f of loadInvalidFixtures(new URL('../../../spec/fixtures/invalid/', import.meta.url))) {
  checkInvalidFixture(f);
}

loadModule

loadModule: (test: ModuleUnderTest) => Promise<LoadedModule>

Instantiate one module the way the loader would, with its dependencies replaced by the instances the test supplies.

Params

  • test.exports: the module

  • test.imports: one instance per dependency name in exports.deps

  • test.config: merged over exports.defaults

  • test.props: spread into the factory's props

Returns the instance and a stop that unloads it.

Throws dependency-not-stubbed, naming the dependency, when one has no entry in imports: a test that forgot one should not get an undefined import.

Example

const { instance, stop } = await loadModule({
  exports: await import('./modules/posts/Create.ts'),
  imports: { 'auth/Session': { userOf: () => 'u_1' } },
  config: { maxLength: 10 },
});

loadServer

loadServer: (options: Omit<ServerOptions, "listener">) => Promise<LoadedServer>

Boot a server with real modules on a listener with no port.

modelApplier

modelApplier: Applier

The harness's own reading of the specification: plain data, no reactivity.

moduleSpecifiers

moduleSpecifiers: (source: string, fileName?: string) => string[]

Every module specifier a source file names with a literal.

Params

  • source: the file's text

  • fileName: used only for parser diagnostics

Returns the specifiers of static imports and re-exports, dynamic import() calls whose argument is a string or substitution-free template, and direct require() calls, in order of appearance. Comments and ordinary strings are not imports and are not here.

Example

const packages = moduleSpecifiers(text).map(aweftPackageOf).filter((p) => p !== undefined);

parseSecurityTable

parseSecurityTable: (text: string) => SecurityRow[]

The table, from the CSV text: a header line id,level,owner,check, then one row per line. The check column may carry commas; the first three commas split the row.

Params

  • text: the file's text

Returns the rows, in file order. A blank line is skipped.

Example

parseSecurityTable(readFileSync('docs/security/asvs.csv', 'utf8'));

randomBelow

randomBelow: (random: () => number, bound: number) => number

A whole number in [0, bound).

Params

  • random: a stream from randomFrom

  • bound: how many values, at least 1

Returns an index into something of that length.

Throws invalid-bound when the bound is under 1.

Example

const victim = items[randomBelow(random, items.length)];

randomFrom

randomFrom: (seed: number) => () => number

A seeded stream of numbers in [0, 1).

Params

  • seed: any integer. It is folded to 32 bits, so seeds equal modulo 2^32 give one stream, and zero is mapped to 1, since a shift register cannot leave zero

Returns a function giving the next number. Two generators made with one seed produce the same stream, so a failure prints its seed and the run can be repeated exactly.

Example

const random = randomFrom(20260901);
assert.ok(check(list), `failed at seed 20260901`);

recordingDocument

recordingDocument: () => Recording

A light document that records what is done to it.

Returns the document and its ops, one line per operation: insert <li> into <ul> before end, remove <li> from <ul>, text "a" -> "b", attr class="x" on <div>, unattr class on <div>, clear <ul>. Nodes made through the document's factories are recorded, and so is anything cloned from one; nodes from elsewhere are not.

Example

const { document, ops } = recordingDocument();
mount(document.body, h('p', {}, 'hi'));
assert.deepEqual(ops, ['insert "hi" into <p> before end', 'insert <p> into <body> before end']);

refFromJson

refFromJson: (r: RefJson) => Ref

The ref a JSON one names. The inverse of refToJson, and the fixtures rely on it round tripping.

refToJson

refToJson: (ref: Ref) => RefJson

A ref in its JSON form, and back. The pair round trips, which the fixtures depend on.

roomChecks

roomChecks: () => RoomCheck[]

The window half of the escape suite: what a hostile module cannot do from inside a room, whatever runner made the room.

Returns the checks, each named, each taking a function that makes a fresh runner.

Throws each check's run throws room-check-failed when the runner misses that obligation, and the detail says what was expected.

Example

for (const c of roomChecks()) test(c.name, () => c.run(() => inProcess()));

securityChecks

securityChecks: (options: { readonly gate: string | Gate<unknown>; }) => SecurityCheck[]

Every security obligation a server has, each citing the ASVS 5.0 requirements it proves.

Params

  • options.gate: the gate the target runs under, a Gate or the name of a module that is one; the suite composes one rule of its own on top and starts the target under that

Returns the checks, each named, each taking start: the caller's boot, which loads the sources given beside its own store and session battery, starts under the gate given with the handlers given, and answers { fetch, open, server, stop }. loadServer answers that shape, so over the harness a case is one line.

Throws each check's run throws an assertion naming what the target did instead. A target with no session battery fails the session cases at the first sign-up, naming that.

Example

for (const c of securityChecks({ gate: 'auth/Gate' })) {
  test(`${c.requirements.join(' ')}: ${c.name}`, () => c.run((given) => loadServer({
    ...given, sources: [auth, ...given.sources], store: newStore(),
  })));
}

seedFrom

seedFrom: (text: string) => number

A seed derived from the fixture name, so a failing shuffle is the same one next run.

settle

settle: (rounds?: number) => Promise<void>

Yield to the timer queue, once per round, so work that schedules more work gets to run.

A single await drains microtasks and nothing else, which is why a suite that awaits once and asserts sees a tree that is half settled. Ten rounds is what the suites that wrote this by hand all chose.

shuffle

shuffle: <T>(items: readonly T[], seed: number) => T[]

Reorder deterministically, from a seed.

Params

  • items: what to reorder

  • seed: the same seed always gives the same order

Returns a new array. Used to check that the order deltas arrive in changes nothing, so it has to be repeatable: a failure that cannot be run again is a rumour. A shuffle can legitimately come back in the order it went in, so it is never the only reordering a check tries.

slotKeyOf

slotKeyOf: (ref: Ref) => string (from @aweftjs/codec)

The name a slot has in a path, and the key a document files it under.

Params

  • ref: the slot a delta names

Returns an object key as itself, an array position in hex, a map identity in text form.

Throws a CodecError with reason invalid-id when a map slot's key is not an id. This is one mapping with one implementation, because a second copy is a second chance to disagree about what a document's own keys are.

Example

slotKeyOf({ kind: 'object', key: 'title' });   // 'title'

socketPair

socketPair: (readyState?: number) => [PairedSocket, PairedSocket]

Two ends of one socket with no port: what one sends, the other hears on a microtask, and closing either closes both.

surfaceOf

surfaceOf: (indexPath: string, program?: Program) => string[]

The public surface of one package, as text.

Params

  • indexPath: absolute path to the package's src/index.ts

  • program: a program that already contains that file. One is built for it when omitted

Returns one line per export, sorted by name. A value reads value name: type, an interface or type alias reads type name: declaration, and an export re-exported from another package in the stack ends with ` (from

surfaceProgram

surfaceProgram: (indexPaths: readonly string[]) => Program

One program over several entry files, so a whole repo's surfaces share one checker.

Params

  • indexPaths: absolute paths to the src/index.ts of each package

Returns a program those paths can be read from. Building one per package costs a fresh parse of every file they share.

Example

const program = surfaceProgram(['/repo/packages/core/src/index.ts']);

sweepSites

sweepSites: (text: string) => Site[]

Every site in a source text, in file order.

Params

  • text: the source

Returns the sites, one per operator occurrence per line, in line then column order. A line that is or carries a comment has none, and an occurrence inside a string is left out. The operators are matched with their spaces, so <= is one site and never also a < one.

Example

sweepSites('if (a < b && c) return true;');
// [{ line: 1, column: 5, from: ' < ', to: ' <= ' }, { line: 1, column: 9, from: ' && ', to: ' || ' }, ...]

themeTokens

themeTokens: (files: readonly ThemeSource[]) => string[]

Every $name these files define.

Params

  • files: the source files to read

Returns the names, sorted, without their $, each once. Every scale step, every role, every size, every duration and every theme function the package ships.

Example

const names = themeTokens([{ path: 'roles.ts', text }]);

valueFromJson

valueFromJson: (v: ValueJson) => Value

The value a JSON one names. The inverse of valueToJson.

valueToJson

valueToJson: (v: Value) => ValueJson

A value in its JSON form, and back. Both directions are used, so both are exercised.

wordRules

wordRules: () => readonly { readonly word: string; readonly fix: string; }[]

The names in the list, so a report can say what is checked without repeating the patterns.

@aweftjs/testing/browser

AuditNode

interface AuditNode { readonly target: string; readonly html: string; }

One node axe found a violation on.

AuditOptions

interface AuditOptions { readonly root?: string; readonly tags?: readonly string[]; readonly locate?: () => string; }

What audit takes beside the page. All three are optional.

AuditResult

interface AuditResult { readonly violations: readonly AuditViolation[]; readonly passes: number; }

What audit answers: the rules the page broke, and how many it kept.

AuditViolation

interface AuditViolation { readonly rule: string; readonly impact: string | null; readonly wcag: readonly string[]; readonly help: string; readonly helpUrl: string; readonly nodes: readonly AuditNode[]; }

One rule the page broke.

PageLike

interface PageLike { evaluate(source: string): Promise<unknown>; addScriptTag(options: { readonly content: string; }): Promise<unknown>; readonly keyboard: { press(key: string): Promise<void>; }; }

The page object a browser driver hands a test, as much of it as these checks read.

Playwright's Page has these three, and the suite passes one: evaluate takes an expression as source text and answers what it evaluates to, addScriptTag puts a script into the page by its content, and keyboard.press presses one key.

Stop

interface Stop { readonly tag: string; readonly id: string | null; readonly role: string | null; readonly name: string; readonly ring: boolean; }

Where the focus landed after one press.

WalkOptions

interface WalkOptions { readonly limit?: number; }

What walk takes beside the page.

WalkProblem

interface WalkProblem { readonly reason: 'focus-not-visible' | 'focus-stuck' | 'focus-loops' | 'unreachable' | 'never-cycles'; readonly fix: string; readonly target: string; }

One thing the walk found wrong, with the element it is about.

WalkResult

interface WalkResult { readonly stops: readonly Stop[]; readonly problems: readonly WalkProblem[]; }

What walk answers: where the focus went, in order, and what was wrong on the way.

audit

audit: (page: PageLike, options?: AuditOptions) => Promise<AuditResult>

Run axe-core over the page and answer what it found.

Params

  • page: the page a browser driver opened

  • options: root, a selector for the part to audit; tags, the axe tags to run; locate, where the axe-core script is, when the installed one is not the one to use

Returns the violations, each with axe's rule id, its WCAG tags, its help and the nodes, and the count of rules that passed. Nothing is thrown for a violation; the test decides.

Throws axe-not-installed when locate, or the resolver in its place, finds nothing.

Example

const { violations } = await audit(view);
assert.deepEqual(violations, [], violations.map((v) => `${v.rule}: ${v.help}`).join('\n'));

walk

walk: (page: PageLike, options?: WalkOptions) => Promise<WalkResult>

Press Tab through the page, from the top, and answer where the focus went and what was wrong on the way. Whatever had the focus loses it first, so the walk starts before the first control.

Params

  • page: the page a browser driver opened

  • options: limit, presses before the walk gives up

Returns every stop in order, and the problems: a stop with no visible ring, a press that moved nothing, a press that sent the focus back round the page without leaving it, a focusable element the walk never reached, or a walk that never came back round. Nothing is thrown for a problem; the test decides.

Example

const { stops, problems } = await walk(view);
assert.deepEqual(problems, [], problems.map((p) => `${p.reason} at ${p.target}: ${p.fix}`).join('\n'));

@aweftjs/testing/postgres

Load

type Load = (name: 'embedded-postgres' | 'pg') => Promise<unknown>;

How the optional peers are reached, so a test can reach the refusal without uninstalling one.

PoolLike

interface PoolLike { query(text: string, values?: readonly unknown[]): Promise<{ rows: Record<string, unknown>[]; }>; connect(): Promise<{ query(text: string, values?: readonly unknown[]): Promise<{ rows: unknown[]; rowCount: number | null; }>; release(): void; }>; end(): Promise<void>; }

The minimum of pg.Pool this module hands back, typed here so pg stays an optional peer.

It carries connect as well as query because the pool is handed straight to postgresDriver, which takes connections rather than queries; a pool that could only answer query would make the one thing this module is for fail to compile.

Throwaway

interface Throwaway { readonly port: number; pool(): Promise<PoolLike>; stop(): Promise<void>; }

A Postgres that exists for one run: the cluster's port, pools in schemas of their own, and the teardown that ends all of them.

throwaway

throwaway: (load?: Load) => Promise<Throwaway>

Start a Postgres for this run: an empty cluster in a temporary directory on a free port.

Every pool it answers is in a schema of its own, which is how two checks in one file stay apart and how two applications share one database.

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
axe-not-installedInstall axe-core as a devDependency, or say where the script is with options.locate.
dependency-not-stubbedAdd an entry under imports for every name the module lists in deps.
fix-missingGive the refusal a third argument saying what to do about it.
fix-not-literalWrite the fix as a plain string; move anything variable into the detail.
fixture-acceptedRefuse this input, with the reason the fixture names.
fixture-mismatchMake the implementation agree with the fixture, or regenerate with npm run fixtures.
fixture-refusedAccept this case: the fixture is part of the format, so refusing it is the bug.
handshake-refusedOpen with the headers the gate needs, or assert the refusal with assert.rejects.
invalid-boundCheck the collection is not empty before asking for an index into it.
kind-conflictState one kind for this id in every delta of the commit.
missing-initialGive the fixture an initial document, or set its stage to decode.
multiple-attachRemove the other attach edge in the same commit, or point at it with a reference.
no-status-lineAnswer every upgrade request with an HTTP status line, refusals included.
not-in-programAdd the file to the program before reading it.
not-in-programPass this path to surfaceProgram too, or let surfaceOf build its own program.
peer-not-installedInstall embedded-postgres and pg as devDependencies.
reason-not-literalWrite the reason as a plain string; a token built at runtime cannot be branched on.
room-check-failedFix the runner so it meets this obligation, then run the checks again.
rounds-not-positivePass a whole number of rounds, one or more, or nothing for the default.
server-stoppedCall loadServer again; a stopped server is not restarted.
slot-existsSend a replace delta to overwrite the slot, or add under a free key.
slot-missingSend an add delta first, so the slot exists before it is replaced or removed.
unreachableAttach it under the root in the same commit that writes into it.
wrong-reasonThrow the reason the fixture names, so every implementation agrees on why.

Recipes

The programs in the stack's gate that use this package, each a job someone would have.

  • recipes/accessible-page: A page everyone can use, driven by keyboard and audited in both modes, and each guardrail catching one page written wrong: the build refusing an element no one can read, the mount throwing on a nameless button and a page with no language, audit and walk reading what only the rendered page shows