/** * TML v0 — Trellis Markup Language runtime. * * Typed source; transpiled to JS on the fly by the dashboard server for the * browser (`/tml-runtime.js`), and imported directly by vitest under node. * * Spec: docs/specs/tml-v0.md. Core (Store / parse / evaluate) is DOM-free so it * unit-tests under node; only `mount` / `applyBindings` touch the DOM. * * @module trellis/ui */ import { EAVStore } from '../core/store/eav-store.js'; import type { VcsOp } from '../vcs/types.js'; export type ResultRow = Record; export interface RefHandle { id: string; read(): unknown; write(value: unknown): void; } export interface TmlDriver { query(q: string): Promise; op(action: string, args: Record): Promise; live(q: string, cb: (rows?: ResultRow[]) => void): () => void; ref(id: string): RefHandle; } export interface TmlBinding { query?: string; each?: { var: string; collection: string; } | null; live?: boolean; ref?: string; op?: { action: string; args: { name: string; expr: string; }[]; } | null; if?: string; text?: string; attrs: Record; } /** Outcome of walking a dotted field path. See `resolvePath`. */ export interface PathResult { /** false = a key along the path was absent from its container. */ ok: boolean; /** Identical to `getPath`'s return value. */ value: unknown; } export interface TmlDiagnostic { code: 'unresolved-path' | 'malformed-each' | 'malformed-op' | 'each-without-query'; /** The attribute the diagnostic came from, e.g. `tml-text`. */ attr: string; /** The offending expression, verbatim. */ expr: string; /** Context, e.g. the scope's top-level keys. Never contains row values. */ detail?: string; } export interface MountOptions { onDiagnostic?: (d: TmlDiagnostic) => void; /** Throw on the first diagnostic instead of reporting and continuing. */ strict?: boolean; } /** * Resolve a dotted field path, distinguishing "key absent" from "key present * and undefined". `value` matches `getPath` exactly; `ok` is the new signal. */ export declare function resolvePath(obj: unknown, path: string): PathResult; /** Resolve a dotted field path against an object, e.g. `lane.id`. */ export declare function getPath(obj: unknown, path: string): unknown; /** * Resolve a TML expression against a scope. * Single field path / literal -> raw value (preserves booleans/numbers). * Multiple parts joined by `+` -> concatenated string. */ export declare function resolveExpr(expr: string, scope: Record): unknown; /** * The field paths an expression reads, ignoring quoted literals. Shares * `splitPlus` with `resolveExpr` so both agree on what a literal is. */ export declare function expressionPaths(expr: string): string[]; /** * Truthiness for `tml-if` only. Empty arrays and empty strings are falsy * (unlike bare JS), so `tml-if="issue.laneIds"` hides when `laneIds === []`. * Does not affect `tml-text` / `tml-attr-*` via `resolveExpr`. */ export declare function isTmlTruthy(v: unknown): boolean; /** Parse a flat attribute map into a structured TML binding. */ export declare function parseTmlAttrs(attrs: Record): TmlBinding; /** * Evaluate a minimal TQL `find ?e where type = 'X' [and …]` against a * LanesSnapshot. Supports parenthesized OR groups and negated OR groups: * `and (status = 'backlog' or status = 'queue')` * `and not (status = 'in_progress' or status = 'paused' or status = 'closed')` */ export declare function evaluateQuery(query: string, snapshot: unknown): ResultRow[]; export declare class Store { snapshot: unknown; private _subs; seed(s: unknown): void; mutate(fn: (s: unknown) => void): void; subscribe(cb: (rows?: ResultRow[]) => void): () => void; private _notify; } export declare class WebDriver implements TmlDriver { base: string; store: Store; private _refs; constructor(opts?: { baseUrl?: string; }); seed(snapshot: unknown): void; query(q: string): Promise; live(_q: string, cb: (rows?: ResultRow[]) => void): () => void; op(action: string, args: Record): Promise; ref(id: string): RefHandle; /** Seed from snapshot, then subscribe to the SSE op stream. Returns the EventSource. */ connect(opts: { snapshotUrl: string; streamUrl: string; }): Promise; } /** * A TML driver that IS a peer. * * `WebDriver` renders from server-built snapshots, which means the server has to * anticipate every projection the page wants — the page cannot ask a question * nobody planned for. That is not local-first (TRL-108 Finding 2), and it forced * `evaluateQuery`: a regex approximation of TQL over two hardcoded collections. * Two implementations of one query language. * * This applies the op stream to a real `EAVStore` via `decompose` and answers * with the real `QueryEngine`. So TML gets whatever TQL can express — traversal, * links, arbitrary types — instead of `Lane` and `Issue`. * * Cost of the whole thing (store + decompose + QueryEngine + hash verify) is * ~5.7 KB gzipped, measured. `decompose` only became bundleable once its * accidental `fs`/`path` imports were removed. * * Not the default yet: SPEC-v1.1 (TRL-110) owns whether peers materialize, and * `WebDriver` remains for consumers that genuinely want a thin client. */ export declare class PeerDriver implements TmlDriver { base: string; store: EAVStore; private _engine; private _subs; private _seen; private _refs; constructor(opts?: { baseUrl?: string; }); /** * Apply one op. Idempotent by hash: the stream replays on reconnect, and a * peer that double-applied would double-project. */ applyOp(op: VcsOp): void; /** Apply a batch, then notify once — a per-op notify would re-render per op. */ applyOps(ops: VcsOp[]): void; query(q: string): Promise; live(_q: string, cb: () => void): () => void; op(action: string, args: Record): Promise; ref(id: string): RefHandle; /** Subscribe to the op stream and materialize it. Returns the EventSource. */ connect(opts: { streamUrl: string; }): unknown; } /** * Resolve text/attr/if/op bindings on `el` and its children against `scope`. * Returns false if the element was removed by a falsy `tml-if` (caller skips it). */ export declare function applyBindings(el: any, scope: Record, driver: TmlDriver): boolean; /** * Static tier: malformed attributes, detectable without data. Runs at mount on * `el` and its descendants. */ export declare function checkStatic(el: any, report: (d: TmlDiagnostic) => void): void; /** * Shape tier: field paths that do not resolve against a real result row. Runs * once, against the first row — a binding well-formed for row 0 is well-formed * for row N. */ export declare function checkShape(el: any, scope: Record, report: (d: TmlDiagnostic) => void): void; /** Mount all `tml-query` subtrees under `root` against `driver`. */ export declare function mount(root: any, driver: TmlDriver, opts?: MountOptions): void; //# sourceMappingURL=tml-runtime.d.ts.map