feat(spec-api): typed surface for spec authors

Thin wrappers over a globalThis.__uatu__ runtime that the verifier
provides as host bindings. Spec authors get IntelliSense for
extract/always/Tap/InputText/actions/weighted/taps/swipes, but the
bundle stays small because runtime logic lives in goja.
This commit is contained in:
pj committed 2026-04-17 23:45:36 +07:00
1 parent 5988e93057
commit 4a1f8cb522
5 files changed
+116

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
import type {
AccessibilityElement,
Action,
ActionGenerator,
InputTextAction,
TapAction,
WeightedEntry,
} from "./types.ts";
export function actions(generator: () => Action[]): ActionGenerator {
return globalThis.__uatu__.actions(generator);
}
export function weighted(...entries: WeightedEntry[]): ActionGenerator {
return globalThis.__uatu__.weighted(...entries);
}
export function Tap(parameters: { on: string | AccessibilityElement }): TapAction {
return globalThis.__uatu__.tap(parameters);
}
export function InputText(parameters: { into: string | AccessibilityElement; text: string }): InputTextAction {
return globalThis.__uatu__.inputText(parameters);
}
export const taps: ActionGenerator = new Proxy({} as ActionGenerator, {
get(_target, property) {
return (globalThis.__uatu__.taps as unknown as Record<string | symbol, unknown>)[property];
},
});
export const swipes: ActionGenerator = new Proxy({} as ActionGenerator, {
get(_target, property) {
return (globalThis.__uatu__.swipes as unknown as Record<string | symbol, unknown>)[property];
},
});
+5
View File
@@ -0,0 +1,5 @@
import type { Extracted, State } from "./types.ts";
export function extract<T>(getter: (state: State) => T): Extracted<T> {
return globalThis.__uatu__.extract(getter);
}
+17
View File
@@ -0,0 +1,17 @@
export type {
AccessibilityElement,
AccessibilityTree,
Action,
ActionGenerator,
Extracted,
Formula,
InputTextAction,
Snapshots,
State,
TapAction,
WeightedEntry,
} from "./types.ts";
export { extract } from "./extract.ts";
export { always } from "./ltl.ts";
export { Tap, InputText, actions, weighted, taps, swipes } from "./actions.ts";
+5
View File
@@ -0,0 +1,5 @@
import type { Formula } from "./types.ts";
export function always(predicate: () => boolean): Formula {
return globalThis.__uatu__.always(predicate);
}
+53
View File
@@ -0,0 +1,53 @@
export type Snapshots = Record<string, unknown>;
export interface AccessibilityElement {
id?: string;
text?: string;
bounds?: { left: number; top: number; right: number; bottom: number };
}
export interface AccessibilityTree {
find(selector: string): AccessibilityElement | undefined;
findAll(selector: string): AccessibilityElement[];
}
export interface State {
snapshots: Snapshots;
ax: AccessibilityTree;
}
export interface Extracted<T> {
readonly current: T;
readonly previous: T | undefined;
}
export type TapAction = { kind: "Tap"; on: string | AccessibilityElement };
export type InputTextAction = { kind: "InputText"; into: string | AccessibilityElement; text: string };
export type Action = TapAction | InputTextAction;
export interface ActionGenerator {
readonly __uatuActionGenerator: true;
generate(): Action[];
}
export interface Formula {
readonly __uatuFormula: true;
}
export interface UatuRuntime {
extract: <T>(getter: (state: State) => T) => Extracted<T>;
always: (predicate: () => boolean) => Formula;
actions: (generator: () => Action[]) => ActionGenerator;
weighted: (...entries: WeightedEntry[]) => ActionGenerator;
tap: (parameters: { on: string | AccessibilityElement }) => TapAction;
inputText: (parameters: { into: string | AccessibilityElement; text: string }) => InputTextAction;
taps: ActionGenerator;
swipes: ActionGenerator;
}
export type WeightedEntry = readonly [number, ActionGenerator];
declare global {
// eslint-disable-next-line no-var
var __uatu__: UatuRuntime;
}