import { V as Value, aG as FieldOptions, q as FieldMap, aH as Prettify, $ as RowFromFieldMap, v as FieldXdo, l as ExprNode, y as FromFieldMap, aI as ReadonlyMethods, M as MethodArg, af as TextMethod, ag as TypeBrand, z as IntMethod, D as DecimalMethod, E as EmailMethod, U as PasswordMethod, aJ as XanoFileUpload, T as ObjectRef, aK as XanoDbLink, aL as ConstMethodOpts, p as FieldDescriptor, aj as XanoFileRef, al as XanoGeoValue, ai as VectorMethod, a7 as TableRefMethod, c as BrandValue, a0 as StackItemXdo, a1 as Statement, Z as ResponseDef, aa as TestDef, C as CacheXdo, w as FilterXdo, I as InputXdo, _ as ResultItemXdo, N as MockMap, ad as TestXdo } from './response-CVAE2kMj.js'; /** * The single boolean-expression algebra shared by every SDK surface that emits * the engine's `{ expression: [ … ] }` shape — conditionals, `while`, * `precondition`, table `where`, the `array.*` `!compare` predicate, and the * db.query/bulk/addon `where` search. Historically this lived in two divergent * encoders (`conditional.ts`'s narrow `encodeComparison` and `db-search.ts`'s * full-tree `encodeSearch`); consolidating them here keeps the two capability * gaps (grouping/full-ops, filtered operands) from ever re-diverging and breaks * the old `db-search → conditional` import cycle. * * The engine node shapes (persisted form): * statement : { type:"statement", or, group:{expression:[]}, statement:{ op, left, right } } * group : { type:"group", or, group:{expression:[ …children… ]} } * operand : { operand, tag, filters } (+ optional `ignore_empty` on the right) * * Operands pass their tag/filters straight through — a filtered operand * (`withFilters(...)`) is valid inline in every condition/`where` surface * (conditional, while, db.query/addon, …), verified against a live engine * (the old #118 db-search rejection no longer reproduces). */ /** * The comparison operators, INCLUDING the strict pair. * * `===`/`!==` are not spellings of `=`/`!=` — the engine evaluates them with PHP * semantics, `$l === $r` against `$l == $r`, so they differ exactly where type * coercion does (`"1" == 1` holds, `"1" === 1` does not). They used to be * aliased onto the loose forms, which silently downgraded every strict * comparison an author wrote and rewrote a pulled one into a different * predicate. They are their own operators. */ declare const SUPPORTED_OPS: readonly ["=", "!=", "===", "!==", ">", "<", ">=", "<="]; type EngineOp = (typeof SUPPORTED_OPS)[number]; /** * The one JS-style spelling that IS a synonym: the engine's own evaluator runs * `=` and `==` through the same loose branch (`case '=': case '==':`), so * normalizing `==` to `=` changes nothing. Nothing else belongs here. */ declare const OP_ALIASES: { readonly "==": "="; }; type ComparisonOp = EngineOp | keyof typeof OP_ALIASES; /** A minimal single binary comparison: `left op right`. */ interface Comparison { left: Value; op: EngineOp; right: Value; } /** Build a single binary comparison for a conditional/while/predicate `when`. */ declare function expr(left: Value, op: ComparisonOp, right: Value): Comparison; /** * The full engine operator set for a comparison (`op` values from the Xano * engine's operator definitions). `cmp(...)` accepts these; the narrow * `expr(...)` stays limited to `= != > < >= <=`. */ type SearchOp = "=" | "==" | "!=" | "===" | "!==" | "<" | "<=" | ">" | ">=" | "in" | "not in" | "like" | "not like" | "ilike" | "not ilike" | "~" | "!~" | "between" | "not between" | "@>" | "contains" | "not contains" | "includes" | "not includes" | "overlaps" | "not overlaps" | "search"; /** A comparison over the full {@link SearchOp} set, with an optional `ignore_empty`. */ interface SearchComparison { left: Value; op: SearchOp; right: Value; /** Skip this clause when the resolved right value is empty (engine `right.ignore_empty`). */ ignoreEmpty?: boolean; } /** A nested AND/OR group of nodes (`{type:"group"}`). */ interface SearchGroup { /** How the group's children join each other: OR (`true`) or AND (`false`). */ or: boolean; children: SearchNode[]; } /** * One term after the first in a {@link mixed} container, with the join that * attaches it to everything before it. Exactly one key. */ type MixedTerm = { and: SearchNode; or?: never; } | { or: SearchNode; and?: never; }; /** * A container whose terms do NOT all join the same way (`a AND b OR c`). * * ⚠ **Prefer `and()` / `or()` with explicit nesting.** This form exists because * the editor produces it — every condition row after the first carries its own * AND/OR choice, with nothing tying it to its siblings — so real workspaces hold * it and it has to be authorable to survive a round trip. It is not a good way * to write a new condition, for one concrete reason: * * **The same terms do not mean the same thing everywhere they can appear.** A * branch condition (`s.if`, `s.while`, a precondition) is folded strictly left * to right with no precedence, so `a OR b AND c` is `(a OR b) AND c`. A database * query's filter is handed to the engine as one flat chain, where AND binds * tighter than OR, so the same three terms select `a OR (b AND c)`. Nothing in * the stored form records which reading was intended. * * `and(or(a, b), c)` and `or(a, and(b, c))` each say one of those unambiguously, * in any context, and are what you want unless you are reproducing a workspace * exactly. */ interface MixedGroup { /** The first term, then each following term with its own join. */ readonly mixed: readonly [SearchNode, ...MixedTerm[]]; } /** A node in the expression tree: a narrow `expr()`, a full `cmp()`, or a group. */ type SearchNode = Comparison | SearchComparison | SearchGroup | MixedGroup; /** * A boolean condition for a `when`/predicate surface (conditional, while, * precondition, array.* `!compare`, table `where`): one node, or a flat array of * nodes ANDed together. The full Xano expression tree — a single `expr()` stays * the discoverable common case. */ type Condition = SearchNode | SearchNode[]; /** * Build a comparison over the full operator set (`in`, `like`, `ilike`, * `between`, `contains`, `overlaps`, `@>`, `~`, `search`, …). Distinct from the * narrow `expr()`. * * ⚠ **`opts.ignoreEmpty` DROPS the clause when its right value resolves empty — * it does not match zero rows.** On `in`/`not in` those are opposite results: * `cmp(col("owner"), "in", ids)` with an empty `ids` matches nothing, and the * same clause with `{ ignoreEmpty: true }` returns the UNFILTERED table. Never * put it on a clause that scopes rows to a permitted set — an empty permission * list then returns everything (issue #5). It is for an OPTIONAL search filter, * where "the caller left this box blank" really does mean "do not filter". */ declare function cmp(left: Value, op: SearchOp, right: Value, opts?: { ignoreEmpty?: boolean; }): SearchComparison; /** Group nodes joined by AND (`and(a, b, or(c, d))`). */ declare function and(...children: SearchNode[]): SearchGroup; /** Group nodes joined by OR (`or(a, b)`). */ declare function or(...children: SearchNode[]): SearchGroup; /** * A container whose terms carry their own joins — `mixed(a, { or: b }, { and: c })`. * * ⚠ Reach for `and()` / `or()` first; see {@link MixedGroup} for why this one is * ambiguous by construction. It is here so a workspace that already contains a * mixed condition round-trips instead of degrading to `raw()`. */ declare function mixed(first: SearchNode, ...rest: [MixedTerm, ...MixedTerm[]]): MixedGroup; /** A registered object kind: authoring def → stored `xdo` envelope. */ interface ObjectKind { /** Stable kind name (e.g. "function", "table", "query"). */ name: string; /** The `packageExport` payload key this kind lands under (e.g. "function", "dbo"). */ payloadKey: string; /** Encode an authoring def into its flattened importable `xdo` envelope. */ encode(def: Def): Xdo; /** * Derive this object's guid from its **def**, for kinds whose identity is not * `md5(":")`. Only the realtime family needs it: a channel * path is unique per server and a message name per channel, so their guids * are composed from more than the name (see `refs/guid.ts`). Omit it and the * registry falls back to the name derivation every other kind uses. */ guidOf?(def: Def): string; } /** Register an object kind under its `name`. */ declare function registerKind(kind: ObjectKind): void; /** True when a kind name has a registered encoder. */ declare function isRegisteredKind(name: string): boolean; /** Look up a registered kind, throwing a clear error when absent. */ declare function getKind(name: string): ObjectKind; /** Encode an authoring def through its registered kind. */ declare function encodeObject(name: string, def: unknown): Xdo; /** All registered kinds (for export assembly). */ declare function registeredKinds(): ObjectKind[]; /** * Table (database) kind (U6) → payload key `dbo`. Columns reuse the shared * field encoder (KTD-6) with the column context; indexes, views, and * autocomplete have their own small shapes. Validated against the Xano engine's * persisted table shape (the full rich field-type corpus). */ /** A column definition: a field with a name + type. */ interface ColumnDef extends FieldOptions { name: string; type: string; } /** * A table schema is authored either as an explicit `ColumnDef[]` (raw type * strings) or, preferred, as a named map of catalog descriptors * (`{ id: f.int(), email: f.email() }`). */ type SchemaDef = ColumnDef[] | FieldMap; /** * Index kind (per the engine's index schema): `primary`/`btree` (+`btree|unique`) on * columns, `gin` on the internal JSON, `search` (full-text), `gist` (spatial), * `vector`. Open-ended (`string & {}`) since the stored layer accepts variants * (e.g. `gin|unique`) the authoring DSL doesn't enumerate. * * `"unique"` is accepted as an ergonomic shorthand for `"btree|unique"` (the * literal the engine requires); it is normalized on export. See * {@link normalizeIndexType}. */ type IndexType = "primary" | "btree" | "btree|unique" | "unique" | "gin" | "search" | "gist" | "vector" | (string & {}); /** * Per-field index operator: `asc`/`desc` (btree), `jsonb_path_op` (gin), * `gist_geometry_ops_2d` (spatial), or a pgvector distance op (vector indexes). */ type IndexOp = "asc" | "desc" | "jsonb_path_op" | "gist_geometry_ops_2d" | "vector_ip_ops" | "vector_cosine_ops" | "vector_l1_ops" | "vector_l2_ops" | (string & {}); /** Full-text-search index language (per the engine's search-index schema). */ type IndexLang = "simple" | "arabic" | "danish" | "dutch" | "english" | "finnish" | "french" | "german" | "hungarian" | "indonesian" | "irish" | "italian" | "lithuanian" | "nepali" | "norwegian" | "portuguese" | "romanian" | "russian" | "spanish" | "swedish" | "tamil" | "turkish" | (string & {}); /** A database index definition. */ interface IndexDef { type: IndexType; fields: Array<{ name: string; op?: IndexOp; }>; name?: string; lang?: IndexLang; } /** A table view: a saved, filtered/sorted projection of the table. */ interface ViewDef { name: string; /** Stable view id (uuid). Required — the engine persists it verbatim. */ id: string; alias?: string; /** Columns to hide in the view → stored `hiddenCols`. */ hide?: string[]; /** Free-text search query → stored `q`. */ q?: string; /** Filter expression (reuses the conditional comparison shape). */ where?: Condition; /** Sort order, applied in array order. */ sort?: Array<{ name: string; order: "asc" | "desc"; }>; } /** * The column-name union for a table authored with a {@link FieldMap} schema: * the declared column keys plus the auto-injected system columns. Drives * schema-aware statement typing (db `fieldName`/`output`/`sortBy`/`row` keys). */ type SchemaCols = Extract | "id" | "created_at"; /** * The auto-injected system columns as they appear on a **row**, parameterized by * the table's {@link TableDef.idType}: the primary key `id` (a `number` for the * default `int`, a `string` for `uuid`) and the `epochms` `created_at` (always a * number). A `FieldMap`-schema table adds these to {@link RowOf} unless it * declares its own. */ type SystemRow = { id: IdT extends "uuid" ? string : number; created_at: number; }; /** * The **row type** of a `FieldMap`-schema table — the shape a read returns: each * declared column (value types recovered from the field brands, `nullable`/ * `array` applied) plus the auto-injected system columns `id`/`created_at`, * unless the schema declares its own. `IdT` threads the table's * {@link TableDef.idType} through so a `uuid` primary key infers `id: string` * (not `number`); `Sys` threads {@link TableDef.system} through so a * `system:false` table drops the injected columns (matching its narrower runtime * row). The read-side mirror of the request-only * {@link import("../inputs/infer.js").InferInput}. Recovered from a table handle * via {@link InferRow}. * * `Sys` is compared non-distributively (`[Sys] extends [false]`) so only a * literal `false` drops the columns — an unresolved `boolean` keeps them, the * safe default. Note {@link SchemaCols} (the column-name phantom) is unaffected * and still always carries `id`/`created_at`; the two intentionally diverge for * `system:false` (a name may still be referenced even when the read row omits it). * * This is the table's full declared row, not any one endpoint's payload — a query * returns whatever its `response`/`output` selects. `created_at` carries * `access:"private"`, which the engine excludes from a *default, auto-shaped* * read (it's `hidden` in the generated response), so such an endpoint omits it * even though this type lists it; a response that explicitly selects `created_at` * still returns it. Narrow with `Omit, "created_at">` on the * auto-shaped path if you need the payload's exact shape. */ type RowOf = Prettify<([Sys] extends [false] ? Record : Omit, keyof S>) & RowFromFieldMap>; /** * Recover a table's row type from a {@link table} handle: * `InferRow`. Closes the loop the SDK opens with `InferInput` * on the request side — rename or retype a column and every consumer that types * a row against `InferRow` lights up. A table authored with a raw `ColumnDef[]` * schema carries no field brands, so its row is `unknown` (nothing to infer). */ type InferRow = T extends TableDef ? Row : never; /** * A single seed row: a plain JSON record shipped in the deploy package and * inserted on deploy. `Row` is the table's inferred read shape ({@link RowOf}), * with the auto-injected system columns made optional — `id` and `created_at` * carry engine defaults (auto-increment / `now`), so a seed row may omit them; * supplying `id` pins it (the engine preserves it and resets the PK sequence). * A raw-`ColumnDef[]` (unbranded) table falls back to an open record. */ type SeedRow = [Row] extends [never] ? Record : unknown extends Row ? Record : Partial>> & Omit; /** * The AUTHORING shape of one seed row for a {@link FieldMap} schema — a write * payload, not a read row: a column without `required: true` is an OPTIONAL key * (the engine applies its default for an absent column, and `coerceSeedRows` * leaves it absent), while a `required` one must be supplied. Only the injected * system columns are added, and those are optional too. * * Distinct from {@link RowOf}, the READ shape, where every declared column is * present. Using the read shape here demanded every column on every seed row — * stricter than both the runtime validator and the engine (issue #164). */ type SeedRowOf = Prettify<([Sys] extends [false] ? Record : Partial, keyof S>>) & FromFieldMap>; /** * How a table's seed rows are supplied. Either the rows directly, or — the * frontend-safe form — a **deferred source**: a thunk (optionally async, e.g. * `() => import("./seed.json")`) resolved only in the Node deploy pipeline. A * deferred source keeps large or sensitive seed data out of any frontend bundle * that value-imports the table def, and is erased entirely under `import type`. * Prefer the thunk form for anything beyond a handful of inline rows — it costs * no typing (row and column inference survive every form; issue #164). * * A JSON `import()` resolves to a module namespace at runtime, not the array * TypeScript types the specifier as; the deploy path unwraps `.default`, so * `() => import("./seed.json")` works as written. */ type SeedSource = ReadonlyArray> | SeedFileSource | (() => ReadonlyArray>> | Promise>>>); /** * Widen a literal type to its base. `"a" | "b"` → `string`, `1 | 2` → `number`; * everything else (including `null`, `Date` and nested arrays) is preserved. */ type WidenSeedValue = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends ReadonlyArray ? ReadonlyArray> : T; /** * A seed row as a DEFERRED source can actually be typed (issue #209). * * A `.json` module's strings infer as `string`, never as the literal union an * `f.enum` column brands — so `seed: () => import("./rows.json")` matched no * `table()` overload the moment any column was an enum, and the diagnostic was * a fourteen-level `TS2769` whose real cause sat on the last line. Almost every * realistic table has a closed set somewhere, so the recommended form was * broken for most real uses. * * Deferred rows are therefore typed with literals widened to their base type. * Nothing is lost: a file's contents are invisible to the type system anyway, * and membership is enforced at export by `coerceSeedRows`, where the value and * the column's declared options are both in hand — an error naming the table, * the row index, the column and the allowed values, which is a better * diagnostic than the overload wall ever was. * * The INLINE form is untouched and stays strict: an invalid literal is still a * compile error (reported at the `table(` call, since the overload is what * fails to match). * * Homomorphic by construction, so optional and readonly modifiers survive. */ type LooseSeedRow = { [K in keyof Row]: WidenSeedValue; }; /** Brand for {@link seedFile}'s marker, so the deploy path can recognise it structurally. */ declare const SEED_FILE: unique symbol; /** A seed source that names a JSON file by PATH — see {@link seedFile}. */ interface SeedFileSource { readonly [SEED_FILE]: { readonly path: string; readonly base: string; }; } /** * Seed rows from a JSON file, named by path — the form that cannot reach a * frontend bundle. * * ```ts * seed: seedFile("./seed/user.json", import.meta.url) * ``` * * Prefer this over `seed: () => import("./seed.json")` for anything you would * mind publishing. **The thunk does not keep seed values out of a frontend * build**, despite reading as though it should: the `import()` sits in YOUR * module, not in `@xanots/core`, so a bundler sees an ordinary dynamic import * and emits the JSON as a served chunk. Nothing the SDK does to its own code can * prevent that. A frontend that value-imports any def whose module graph reaches * the table then ships the seed to the browser (issue #204). * * A path is a plain string, so there is nothing for a bundler to follow. The * file is read with `node:fs` in the deploy pipeline only. * * `base` is required, and is `import.meta.url` at the call site: `path` resolves * relative to the FILE THAT DECLARES THE TABLE, which is where an author is * looking when they write it — not the CLI's working directory, and not the * workspace entry. Passing it explicitly is what makes that true for a table * defined in a nested module. * * ⚠ Rows are validated at export (column names and coercion, per table schema), * not at author time — a JSON file's contents are not visible to the type * system. Keep secrets out of seed data regardless of form: a seed is throwaway * fixture data for disposable environments. */ declare function seedFile(path: string, base: string | URL): SeedFileSource; /** * @typeParam Cols - phantom column-name union, captured by {@link table} from a * `FieldMap` schema so db statements can type their column-name fields against * it. Defaults to `string` (a table authored with a raw `ColumnDef[]`, or a * bare-name reference, stays loosely typed). Never set at runtime. * @typeParam Row - phantom row-type carrier, captured by {@link table} from a * `FieldMap` schema so `InferRow` can recover the read shape. * Defaults to `unknown` (a raw-schema/bare-name table carries no brands). * Never set at runtime. */ interface TableDef { /** @internal phantom carrier for {@link Cols}; never assigned at runtime. */ readonly __cols?: Cols; /** @internal phantom carrier for {@link Row}; never assigned at runtime. */ readonly __row?: Row; name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; docs?: string; auth?: boolean; install?: boolean; schema: SchemaDef; /** * Auto-prepend the engine's standard system columns — `id` (int, primary key) * and `created_at` (epochms, `default:"now"`, `access:"private"`) — when the * authored schema doesn't already declare them. Default `true`; set `false` * for a table that owns its primary key shape (e.g. an external/imported one). * Columns the author *does* declare are respected and never duplicated. */ system?: boolean; /** * Type of the auto-injected `id` primary key: `int` (auto-increment, the * default) or `uuid`. Ignored when `system:false` or when the author declares * their own `id` column. Only affects the system-column shape — the * `primary(id)` index is unchanged. */ idType?: "int" | "uuid"; /** * Storage mode. `true` stores every authored field as JSON under the internal * `xdo` column (and the engine adds a `gin(xdo)` index); `false` (the default) * gives each field its own real Postgres column and no `gin` index. Both look * identical to read — `xdo` is hidden. Mirrors the workspace-level `use_xdo` * setting (also `false` by default); set per-table to override. Only affects * physical storage + the `gin` index, never the authored schema. */ useXdo?: boolean; /** * Database indexes. The engine's standard set — `primary(id)`, * `btree(created_at desc)`, plus `gin(xdo)` when {@link useXdo} is `true` — * is auto-prepended (alongside the system columns it indexes) unless * `system:false` or you declare an equivalent one (matched by type + covered * fields). Declare extras (unique, composite, …) here; they're kept verbatim * and the standard set rides along de-duped. */ index?: IndexDef[]; autocomplete?: string[]; external?: { source: string; id: string; }; views?: ViewDef[]; /** Workspace tags (stored `tag: [{tag}]`), e.g. `["xano:quick-start"]`. */ tags?: string[]; /** * Seed rows shipped in the deploy package and inserted on deploy (full-replace * import → re-deploy re-seeds cleanly, no duplication). Off the table's * persisted schema — it rides as a separate `content/` archive entry, resolved * and validated **only in the Node deploy path**, never in the browser-safe * bundle. See {@link SeedSource}; prefer a deferred thunk for large data. * * Typed loosely here (not against `Row`) so a `TableDef<_, ConcreteRow>` stays * assignable to `TableDef` — the {@link table} overload types * the authoring input against the real row shape. */ seed?: SeedSource; } interface IndexXdo { name: string; lang: string; type: string; fields: Array<{ name: string; op: string; }>; market_item: { id: number; version: number; guid: string; }; } interface ViewXdo { alias: string; hiddenCols: string[]; id: string; name: string; q: string; expression: ExprNode[]; sort: Array<{ name: string; order: string; }>; } interface TableXdo { name: string; description: string; docs: string; auth: boolean; install: boolean; schema: FieldXdo[]; index: IndexXdo[]; autocomplete: Array<{ name: string; }>; external: { source: string; id: string; }; views: ViewXdo[]; tag: unknown[]; sql_name: string; use_xdo: boolean; market_item: { id: number; version: number; guid: string; }; } declare function encodeIndex(def: IndexDef): IndexXdo; declare function encodeView(def: ViewDef): ViewXdo; declare function encodeColumn(def: ColumnDef): FieldXdo; declare function encodeTable(def: TableDef): TableXdo; declare const tableKind: ObjectKind; /** * Author a database table. When the schema is a {@link FieldMap} * (`{ name: f.text(), … }`), the returned handle captures its column names in * the {@link TableDef} `Cols` type param, so db statements that take the table * can type their column-name fields (`fieldName`, `output`, `sortBy`, `row` * keys) against the real columns. A raw `ColumnDef[]` schema stays loose. */ declare function table(def: Omit & { schema: S; idType?: IdT; system?: Sys; /** Seed rows typed against this table's row shape (system columns optional). */ seed?: SeedSource>; }): TableDef, RowOf>; /** * Raw-schema escape hatch: a table authored with a `ColumnDef[]` schema (no field * brands, so nothing to infer) stays loosely typed. * * This overload is deliberately narrowed to `ColumnDef[]` rather than accepting * any `TableDef`. A `FieldMap` schema would match a `TableDef`-wide signature * too, and TypeScript's overload resolution silently falls through to a later * candidate whenever the generic one does not resolve on the first pass — which * a function-form `seed` triggers. The result was a table whose `Cols` and `Row` * both collapsed with no error reported at the `table()` call (issue #164). With * this overload unable to match a `FieldMap`, the generic signature is the only * candidate and resolves, or reports a real error. */ declare function table(def: Omit & { schema: ColumnDef[]; }): TableDef; type InputOptions = FieldOptions; /** * {@link InputOptions} with `methods` narrowed to the input type's valid method * set `N` (mirrors the field catalog's `MethodOpts`). Types with no * engine-declared methods leave `N = never` (only the `{ name, arg }` escape hatch). */ type InputOpts = Omit & { methods?: MethodArg[]; }; /** {@link InputOpts} made safe to capture under a `const` type parameter (see {@link ReadonlyMethods}). */ type ConstInputOpts = ReadonlyMethods, N>; /** Internal descriptor carried until `encodeInput` runs. */ interface InputDescriptor { type: string; options: InputOptions; } /** * Typed input constructors. Inputs and table columns share one engine schema, so * `input.*` is a full mirror of the `f.*` catalog — every engine-legal input type * (scalars, files, geo, vector, table refs, objects, lists). The file/geo/vector/ * tableRef constructors delegate to `f.*`: the descriptor (`{ type, options }`) is * context-free — the input-vs-column difference is applied later by `encodeInput`'s * `INPUT_CONTEXT` — so the same constructor is correct for both and they can't drift. * `enum`/`object` take their payload positionally; the rest take plain options. */ declare const input: { text: = Record>(options?: O) => InputDescriptor & TypeBrand; int: = Record>(options?: O) => InputDescriptor & TypeBrand; decimal: = Record>(options?: O) => InputDescriptor & TypeBrand; bool: = Record>(options?: O) => InputDescriptor & TypeBrand; email: = Record>(options?: O) => InputDescriptor & TypeBrand; /** * Password input — hashes its value **when the input binds**, before your stack * runs. This breaks the natural signup/login shape and is the single most common * way to ship silently-broken auth: * - **Login:** `s.security.check_password` receives the *already-hashed* submission * (a fresh random salt each request), compares it against the stored hash, and * never matches — so login always fails with a false "invalid password". * - **Signup:** the value is hashed here at bind, so the `f.password` column then sees * an already-hashed `salt.hash` value and stores it as-is (the column's hash-on-write * skips a value already in that shape) — the plaintext never lands in the column. * * Recipe: use {@link input.text} (e.g. `input.text({ methods: ["min:6"] })`) for the * password on **both** signup and login, let the `f.password` *column* hash on write, * and pass the plaintext straight to `check_password`. See the auth recipe in the * README. Reach for `input.password` only when you specifically want bind-time hashing * and are not also comparing it with `check_password` (issue #109). */ password: = Record>(options?: O) => InputDescriptor & TypeBrand; /** * URL input — a `text` field that names the intent "this holds a URL". There * is no native engine `url` type, and this does **not** by itself enforce an * http(s) scheme: a `javascript:`/`data:` URL still type-checks and imports. * When the value is security-relevant (e.g. a link that gets navigated to), * reject bad input at the boundary in the stack with `s.precondition` — see * the "validate input at the boundary" recipe in the README. Carries the same * `TextMethod` options as {@link input.text}. */ url: = Record>(options?: O) => InputDescriptor & TypeBrand; uuid: = Record>(options?: O) => InputDescriptor & TypeBrand; date: = Record>(options?: O) => InputDescriptor & TypeBrand; /** Epoch-millisecond timestamp (stored `epochms`). */ timestamp: = Record>(options?: O) => InputDescriptor & TypeBrand; json: = Record>(options?: O) => InputDescriptor & TypeBrand; /** * Raw file **upload** (stored `file`) — the bytes as they arrive on the * request (multipart, base64, or a fetched URI). Input-only: there is no * `f.file` column, because an upload is not something a table holds. * * It is NOT a stored file resource and cannot be written to a file column * directly. Store it first and write what you get back: * `s.storage.create_image({ as: "img", value: ref("input.avatar") })`, then * write `ref("img")` to an `f.image()` column. Use {@link input.image} and * friends only when the caller already sends a stored file resource. */ file: = Record>(options?: O) => InputDescriptor & TypeBrand; /** * **Database link** — one input that EXPANDS into one input per column of the * linked table (stored `_mvpschema`; XanoScript `dblink`). A table * with three columns turns this single entry into three request inputs, so * read them by their own column names: `inp("email")`, not `inp("user__")`. * The expansion is live — adding a column to the table adds an input here. * * `hidden` is how you drop columns from that expansion (`["created_at", "id"]`) * and is the common case in the wild, not an edge case. `merge: true` is what * makes the engine expand rather than nest, so it is forced and cannot be * unset — a dblink that does not merge is not a dblink. * * `customize` overrides individual expanded columns — `{ email: { required: * true, methods: ["lower"] } }` — where `hidden` drops them wholesale. Both * are read per column by the engine's expansion, and a column named in * `customize` with `hidden: true` is dropped exactly as the outer list does it. * * Input-only: `excludedTypesForDatabase` rules it out as a column, and a * column linking a whole table would be a foreign key — use * {@link f.tableRef} for that. * * By convention the editor names the entry after the table with a trailing * `__` (`user__`), but the name is just the key in your `input:` map and any * name round-trips. */ dbLink = Record>(table: ObjectRef, options?: O): InputDescriptor & TypeBrand; /** Image file input (stored `blob_img`). */ image: = Record>(options?: O) => FieldDescriptor & TypeBrand; /** Video file input (stored `blob_video`). */ video: = Record>(options?: O) => FieldDescriptor & TypeBrand; /** Audio file input (stored `blob_audio`). */ audio: = Record>(options?: O) => FieldDescriptor & TypeBrand; /** Generic file-attachment input (stored `blob`). */ attachment: = Record>(options?: O) => FieldDescriptor & TypeBrand; /** Geo inputs: `input.geo.point()`, `.polygon()`, … (six geometry types). */ geo: { readonly point: = Record>(options?: O) => FieldDescriptor & TypeBrand; readonly multipoint: = Record>(options?: O) => FieldDescriptor & TypeBrand; readonly linestring: = Record>(options?: O) => FieldDescriptor & TypeBrand; readonly multilinestring: = Record>(options?: O) => FieldDescriptor & TypeBrand; readonly polygon: = Record>(options?: O) => FieldDescriptor & TypeBrand; readonly multipolygon: = Record>(options?: O) => FieldDescriptor & TypeBrand; }; /** Vector input; `size` (>= 1) is the embedding dimensionality. */ vector: = Record>(size: number, options?: O) => FieldDescriptor & TypeBrand; /** Table-reference input — see {@link f.tableRef}; pass the table handle or bare name. */ tableRef: & { type?: "int" | "uuid"; } = Record>(table: ObjectRef, options?: O) => FieldDescriptor & TypeBrand<"uuid" extends (O extends { type: infer T; } ? T : never) ? string : number, O>; /** * Enum input. `values` may be empty, mirroring {@link f.enum} — the two * surfaces describe the same engine type, and one accepting what the other * rejects is how they start disagreeing about what a workspace can hold. */ enum, const O extends ConstInputOpts = Record>(values: V, options?: O): InputDescriptor & TypeBrand; /** * Object input (stored `obj`) with typed, named children. `children` is a * field map built from the same `f.*` catalog used for columns, e.g. * `input.object({ name: f.text(), age: f.int() })`. */ object = Record>(children: C, options?: O): InputDescriptor & TypeBrand, O>; /** * List (array) input — wraps an element constructor, mirroring `Array`: * `input.list(input.text())`, `input.list(input.object({ id: f.int() }))`. * The element's own options (e.g. its `methods`) are kept; list-level `options` * (`required`, `nullable`, `description`, …) apply to the list field and win * on conflict. The element's value type is preserved as the array element. */ list = Record>(element: E, options?: O): InputDescriptor & TypeBrand, O & { array: true; }>; }; /** * Small shapes shared across object kinds: the empty middleware block and the * tag encoding (`["a","b"]` → `[{tag:"a"},{tag:"b"}]`). */ interface MiddlewareBlock { pre_customize: boolean; post_customize: boolean; /** Pre/post attachment entries — `mvp:middleware` stack items (see middleware-attach.ts). */ pre: StackItemXdo[]; post: StackItemXdo[]; } interface HistoryBlock { inherit: boolean; enabled: boolean; limit: number; } /** * Middleware **attachment** (U-mw) — the pre/post hooks a host primitive runs * around its own stack. Distinct from the middleware *object* (`middleware.ts`, * the reusable logic unit) and from the inline `middleware.call` statement * (`mvp:workspace_run_middleware`, a stack call). * * An attachment entry is an ordinary stack item whose statement is * **`mvp:middleware`** with `context.middleware.id` = the target middleware's * guid (the engine remaps guid→local id on import). Verified against the live * xdo corpus: each `middleware.pre[]` entry carries the full 12-key * `StackItemXdo` envelope — the engine types `pre[]`/`post[]` the same as the * main `run[]` stack. So entries flow through `encodeStatement` — never a * hand-built minimal literal. * * Inheritance is the engine's job, not * ours: XanoTS only emits each tier's list plus the `pre_customize`/ * `post_customize` override flags. Presence-driven: providing a phase's list * sets that phase's `_customize` flag (override); omitting it leaves the flag * `false` (inherit from the parent tier). An explicit empty list (`pre: []`, or * `middleware.clear()`) is "override with nothing — stop inheriting". */ /** * A single pre/post attachment. Either a bare middleware reference (a * `middleware()` def handle or its name) or a `{ middleware, active }` object * when the entry needs to be authored-but-disabled (`active: false` ⇒ stored * `disabled: true`, so the engine skips it while keeping it in the list). */ type MiddlewareAttachEntry = ObjectRef | { middleware: ObjectRef; active?: boolean; } | Statement; /** * The attachment authoring shape shared by every host primitive * (query/function/task/tool/api-group). Each phase is an ordered list resolved * **independently**: a host can override `pre` while inheriting `post`. */ interface MiddlewareAttach { pre?: MiddlewareAttachEntry[]; post?: MiddlewareAttachEntry[]; } /** Encode one attachment entry into the full stored `StackItemXdo` envelope. */ declare function encodeMiddlewareEntry(entry: MiddlewareAttachEntry): StackItemXdo; /** * Build the object-level {@link MiddlewareBlock} from an authoring * {@link MiddlewareAttach}. Omitted → the empty block (both `_customize:false`, * both lists `[]`) byte-identical to {@link emptyMiddleware}, so a host with no * middleware emits exactly as before. A phase present (even an empty array) * sets that phase's `_customize` flag. */ declare function buildMiddlewareBlock(attach?: MiddlewareAttach): MiddlewareBlock; /** * Encode a bare list of attachment entries into stored stack items. Used by the * workspace tier, whose `{objType}_{phase}` map holds the same entries but with * no per-phase `_customize` flag (workspace is the terminal fallback). */ declare function encodeMiddlewareList(entries?: MiddlewareAttachEntry[]): StackItemXdo[]; /** * An explicit empty override — the readable spelling of `[]`. `pre: clear()` * means "customize this phase, run no middleware" (stop inheriting the parent * tier's chain), as opposed to omitting the phase (inherit). */ declare function clear(): MiddlewareAttachEntry[]; /** * Request-history authoring model — the scalar surface every host shares. * * Xano's request history is an inherited setting (object → container → branch → * workspace). XanoTS emits the *stored* config each tier persists plus the * `inherit` flag the engine's resolver reads; it never re-implements the walk. * * Authoring is a single scalar, matching Xano's own XanoScript ergonomics: * * | Author intent | `HistoryInput` | Stored (object tier) | * | -------------------- | -------------- | ------------------------------------- | * | Inherit (default) | *(omit)* | `{ inherit:true, enabled:, limit:100 }` | * | Off | `false` | `{ inherit:false, enabled:false, limit:100 }` | * | On, default depth | `true` | `{ inherit:false, enabled:true, limit:100 }` | * | On, capture depth N | `100` | `{ inherit:false, enabled:true, limit:N }` | * | On, unlimited depth | `"all"` | `{ inherit:false, enabled:true, limit:-1 }` | * * `limit` caps the number of statement executions captured in a single history * record's stack trace (debugger depth) — NOT record retention. `-1` is * unlimited (`"all"`). Providing any value flips `inherit:false` (customize) so * the authored value round-trips; an inherit block is normalized away. */ /** The scalar authoring surface for request history. Omit to inherit. */ type HistoryInput = boolean | number | "all"; /** * Container-tier prefixes: an API group parents queries, a toolset parents * tools, and a realtime server / channel parents messages. */ type ContainerPrefix = "query" | "tool" | "message"; /** * A container tier's stored history block — `{ inherit, _enabled, * _limit }`. The API group (`app`) uses `query_*`; the toolset envelope * (agent/mcp_server/assistant) uses `tool_*`. */ type ContainerHistoryBlock

= { inherit: boolean; } & Record<`${P}_enabled`, boolean> & Record<`${P}_limit`, number>; /** * Object types the workspace-tier map carries a history pair for. * * `message` is the realtime tier and defaults off, like `function`, `middleware`, * and `trigger`. It belongs here because the engine stores a pair for it at the * workspace tier too — omitting it made the SDK emit a 12-key map against the * engine's 14, so `workspace.history` mismatched on every real workspace and no * workspace could round-trip clean. */ declare const WORKSPACE_HISTORY_TYPES: readonly ["query", "function", "task", "tool", "trigger", "middleware", "message"]; type WorkspaceHistoryType = (typeof WORKSPACE_HISTORY_TYPES)[number]; /** The workspace-tier authoring map: a scalar per object type (all optional). */ type WorkspaceHistoryDef = Partial>; /** The stored 14-key workspace history map (`{objType}_enabled`/`{objType}_limit`, no `inherit`). */ type WorkspaceHistoryXdo = Record<`${WorkspaceHistoryType}_enabled`, boolean> & Record<`${WorkspaceHistoryType}_limit`, number>; /** * Object-tier history block. Omitting `input` yields the kind's inherit default * (see {@link defaultHistory}); any value flips `inherit:false`. */ declare function encodeHistory(objType: string, input?: HistoryInput): HistoryBlock; /** * Container-tier history block (`{ inherit, _enabled, _limit }`). * The omit path emits `inherit:true` plus the tier's own engine default (see * {@link CONTAINER_DEFAULT_ENABLED}) at `limit:100` — on for query (via app) and * tool (via toolset), off for message (via realtime server / channel). */ declare function encodeContainerHistory

(prefix: P, input?: HistoryInput): ContainerHistoryBlock

; /** * Workspace-tier flat map (terminal fallback — no `inherit`). Wholesale: every * object type is emitted; a type absent from `map` falls back to its engine * default (`enabled` per the kind rule, `limit:100`). Matches the 14-key stored * shape in `test/fixtures/misc/workspace.json`. */ declare function buildWorkspaceHistory(map: WorkspaceHistoryDef): WorkspaceHistoryXdo; /** * `defineFunction` + the in-memory `FunctionDef` model (U6). * * The authoring API is a flat declarative factory (KTD-3): data in → JSON out, * no hidden control-flow inference. */ /** * Like {@link QueryDef}, `FunctionDef` is generic over its `input` map `I` so a * consumer can recover the exact, branded input types via * `InferInput`, and over its declared response shape `Res` so * `InferResponse` recovers the read shape (functions share * the response system with queries). Both default so every bare-`FunctionDef` * use works unchanged; `Res` defaults to `never` (undeclared → derivation). */ interface FunctionDef = Record, Res = never, Resp extends ResponseDef = ResponseDef, S extends readonly Statement[] = readonly Statement[]> { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; docs?: string; /** Deploy-target workspace id. Defaults to 0 (binding deferred). */ workspace?: number; input?: I; /** The statement stack, captured as the literal tuple `S` — see * {@link QueryDef.stack}. Enables `InferResponse`'s single-variable trace. */ stack?: S; /** The response assignment — see {@link QueryDef.response}. Captured as `Resp` * so `InferResponse` can auto-derive object-literal keys / trace a variable. */ response?: Resp; /** * Type-only: declare the function's response shape so * `InferResponse` recovers it exactly (the override, taking * precedence over automatic derivation). The runtime value is ignored by the * encoder; only its type is read. See {@link QueryDef.responseShape}. */ responseShape?: Res; /** * Pre/post middleware attachment. Functions have no API-Group tier — an * un-customized phase inherits straight from the workspace. Providing a phase * sets its `_customize` flag; `pre: middleware.clear()` overrides with nothing. */ middleware?: MiddlewareAttach; /** * Request-history capture. Omit to inherit from the workspace (functions have * no container tier). A scalar: `false` off, `true` on at default depth, a * number = capture depth, `"all"` unlimited. Any value stops inheriting. * Functions default OFF. See {@link HistoryInput}. */ history?: HistoryInput; /** Workspace tags (stored `tag: [{tag}]`), e.g. `["xano:quick-start"]`. */ tags?: string[]; /** * Saved UNIT TESTS — named input sets run against this object, with * assertions on the response. The same tests the Xano editor shows. * * Build assertions with the top-level `expect.*` helpers (NOT `s.expect.*`, * which builds workflow-test statements). A statement in this object's stack * can return a mock instead of running, per test, via its `mock` option. * * ⚠ A run uses an EMPTY datasource, so no `table({ seed })` row is visible to * it: every `db` read misses, and an assertion on the first row fails against * a deployment whose endpoint returns those rows over HTTP. Create what the * test needs inside the run — a `defineFunction` fixture the stack calls * first — or `mock` the read. */ tests?: TestDef[]; /** * Response caching, in the same block a query carries — the engine reads a * function's `cache` through the same path (`convertFunctionToConfig` hands it * straight to the runtime config). Omit for the engine's default (inactive). * * Modelled because it was NOT authorable and the encoder hard-coded the * default: a pulled function with caching switched on re-exported with it OFF, * so a redeploy silently turned real caching off. See {@link QueryDef.cache}. */ cache?: Partial; } /** * Validate and return a typed `FunctionDef`, preserving the exact branded `input` * map on the return type so `InferInput` recovers the input * payload type (functions share the input system with queries). */ declare function defineFunction = Record, Res = never, Resp extends ResponseDef = ResponseDef, const S extends readonly Statement[] = readonly []>(def: FunctionDef): FunctionDef; /** A surface that takes a JavaScript body: the lambda statement, or a filter. */ type LambdaSurface = "s.lambda" | "fl.lambda" | "map" | "filter" | "some" | "every" | "find" | "findIndex" | "reduce"; /** * Surface → the identifiers a body may reference. Live-probed * (`scripts/probe-lambda-bindings.ts`) rather than read off any documentation, * and asserted against the recorded probe output in the tests, so the guard, the * types, and the docs cannot disagree with the engine or with each other. * * `console` and `crypto` are globals inside the body rather than destructured * bindings, so they are legal to reference but are not part of the parameter * type. Every other entry is `$`-prefixed, which is what makes the scan below * able to see a violation at all. */ declare const LAMBDA_BINDINGS: Readonly>; /** Globals a body may use that are not `$`-bindings (so the scan never sees them). */ declare const LAMBDA_GLOBALS: readonly string[]; /** * The libraries the engine preloads as GLOBALS in a lambda body — the only * dependency route that works on every instance (issue #265). * * A body is prepared for execution in one of two ways depending on the * instance's executor generation, and the two disagree about `import()`: * * - one transpiles the body and lets the runtime resolve a specifier when the * `import()` actually runs, so `node:*`, `npm:*`, a bare package and a URL * all resolve; * - the other BUNDLES the body before running it, which resolves every * LITERAL specifier ahead of time against the container's filesystem — * where none of them exist. `await import("node:crypto")` comes back as the * text `Could not resolve "node:crypto"`, with HTTP 200. A literal * `require("axios")` fails there for the same reason. * * These names need no specifier, so they are unaffected by which generation * runs the body. Live-probed from `Object.keys(globalThis)` inside a body; the * evidence is `s.lambda.globalThis` in `vendor/lambda-bindings.json`, and * `test/values/lambda.test.ts` fails if this list drifts from it. * * Not exhaustive by design: it lists what an author is likely to reach for. A * body can always read `Object.keys(globalThis)` for its own instance. */ declare const LAMBDA_MODULE_GLOBALS: readonly string[]; /** * Every filter that takes a JavaScript body → which positional slot the body * occupies, and which binding set it runs against. * * The slot is fixed rather than looked up because it is what the guard has to * key on: `filter()` sees positions, not argument names. `reduce`'s body is the * SECOND argument (the accumulator's initial value comes first, and #221's other * half is exactly that slot being invisible); every other lambda filter's is the * first. * * `fl.transform` is deliberately absent. It takes Xano Expression Engine source, * not a JavaScript body, and binds the operand as `$0` rather than `$this` — so * validating it against THIS contract would reject correct code. It has its own * guard, on its own probed contract, in `./expression-arg.ts` (issue #245); a * `return` there is not merely refused but sometimes silently mis-evaluated. * `test/values/filters.test.ts` enumerates the `code`-taking filters from * `FILTER_SPECS` and fails if one is missing here. */ declare const LAMBDA_CODE_FILTERS: Readonly>; /** * The statements that take a JavaScript body → the field carrying it and the * bindings it runs against. One today; a table so a second cannot be added * without deciding its surface. */ declare const LAMBDA_STATEMENTS: Readonly>; /** * Ambient request state, in scope at every surface. * * Typed `any` deliberately: `$var` holds the enclosing function's stack * variables and `$input` its declared inputs, and threading those inferred * shapes in here is a substantial type-level piece of work in its own right. The * value this parameter carries today is the NAMES — that a binding exists, is * spelled this way, and exists at this surface. */ interface AmbientBindings { /** Workspace environment variables and request settings. */ $env: any; /** The enclosing function's inputs, by name. */ $input: any; /** The enclosing function's stack variables, by name — `$var.total`. A stack * variable is NOT also injected as a bare `$total`. */ $var: any; /** The authenticated caller, when the request carries a token. */ $auth: any; } /** Ambient state plus the per-element bindings of an iterating filter. */ interface IteratingBindings extends AmbientBindings { /** The current element. */ $this: any; /** The current element's position, from 0. */ $index: number; /** The whole array the filter is applied to. */ $parent: any; } /** The bindings in scope in a body at `S` — the type of a {@link lam.fn} parameter. */ type LambdaBindings = S extends "reduce" ? IteratingBindings & { /** The accumulator. This is the binding issue #221 guessed as `$acc`. */ $result: any; } : S extends "s.lambda" ? AmbientBindings : S extends "fl.lambda" ? AmbientBindings & { /** The piped value the filter is applied to. */ $this: any; } : IteratingBindings; /** * A lambda body written INLINE at the call site, where the surface is already * known — `fl.map(({ $this }) => $this * 2)`. * * This is the form to reach for. `lam.fn` exists for a body that is shared, * captured, or otherwise built away from where it runs; everywhere else the * call site knows which surface it is, so naming the surface a second time is * something for the SDK to do rather than the author. TypeScript types the * parameter contextually from the position, so `$this` autocompletes inside * `fl.map` and `$result` does not compile there. */ type LambdaBody = (bindings: LambdaBindings, captured: Record) => unknown; /** A JSON value a capture entry can carry into the body. */ type CaptureValue = string | number | boolean | null | { [k: string]: CaptureValue; } | CaptureValue[]; /** * The CHECKING form of {@link CaptureValue}, used wherever a capture is * constrained rather than described. * * `CaptureValue` names the shape for a reader, but it cannot be a constraint on * its own: its object arm is an index signature, and TypeScript hands an * implicit index signature to type ALIASES only — never to an `interface`, which * stays open to declaration merging. So `capture: { band }` compiled when `band` * was a `type` and failed when the identical shape was an `interface`, with the * error naming `CaptureValue` rather than the one keyword that differed, and the * body's second parameter then degrading to the constraint (issue #266). * * A homomorphic mapped type is the way through: `keyof` an interface is the same * as `keyof` the alias, so this accepts both and still rejects what genuinely * cannot cross the boundary as text — functions, `undefined`, `symbol`, `bigint` * — matching the runtime check in {@link capturePrelude}. */ type Capturable = T extends string | number | boolean | null ? T : T extends (...args: never[]) => unknown ? never : T extends readonly (infer U)[] ? readonly Capturable[] : T extends object ? { [K in keyof T]: Capturable; } : never; /** A capture bag: every value {@link Capturable}, whatever it was declared as. */ type CaptureRecord = { [K in keyof T]: Capturable; }; /** Options shared by every `lam.*` form. */ interface LambdaOptions> { /** * Which surface the body will run at, which is what decides the legal * bindings. * * Omit it and the check is DEFERRED to the call site, which knows the answer: * dropping the body into `fl.map(...)` or `s.lambda({...})` validates it there, * against that surface. Naming it here is for a body built away from its call * site — a shared constant, or one that should fail at its own definition * rather than at its use. * * Better still, write the body inline (`fl.map(({ $this }) => …)`), where the * surface is implied and TypeScript types the bindings from the position. */ surface?: LambdaSurface; /** * Values from the enclosing TypeScript scope to carry into the body, emitted * as a `const` prelude. * * Nothing crosses the boundary implicitly: the body is extracted as TEXT and * runs in a different process, so a closed-over `const rate` is simply * undefined at runtime. Rather than guess at free variables (which needs a * JavaScript parser this package deliberately does not have), capture is * explicit and the second parameter of the body destructures it. */ capture?: C; } /** * Reject a body that cannot work at `surface`, before it can reach a live * request. Three failures are unwritable after this: * * 1. an `$identifier` outside the surface's binding set — provably undefined at * runtime, because a stack variable is only ever reachable as `$var.name`; * 2. a top-level `import`/`export`, which is a syntax error in a function body * (a dependency comes from {@link LAMBDA_MODULE_GLOBALS}, which needs no * specifier — see there for why a literal `import()` one is not portable); * 3. an empty body, which stores a statement the engine refuses at import. * * Exported so the statement and filter factories can run the same check on a * plain `c.text(...)` body — an author who never adopts `lam.*` gets the same * answer at the same moment. */ declare function assertLambdaBody(body: string, surface: LambdaSurface, source?: string): void; /** * Author a lambda body as a typed TypeScript function. * * The first parameter destructures the bindings for the surface, so the editor * supplies them and a wrong name is a compile error rather than a wrong value at * runtime. The body is extracted at author time and emitted as the same * `const:text` a hand-written `c.text(...)` produced. * * ```ts * lam.fn(({ $result, $this }) => $result + $this) // reduce * lam.fn(({ $var }) => $var.subtotal * 1.2, { surface: "s.lambda" }) * lam.fn(({ $this }, { capturedRate }) => $this * capturedRate, { surface: "map", capture: { capturedRate: rate } }) * ``` * * Nothing from the enclosing scope crosses implicitly — a closed-over value is * undefined at runtime — so anything the body needs from outside goes in * `capture` and arrives as the second parameter. Give the capture a key nothing * at module scope shares (it need not keep the name of what it carries); a * collision is refused at build time, because the loader renames one of the two * and the prelude is written under the original name. */ declare function fn = Record>(body: (bindings: LambdaBindings, captured: C) => unknown, opts?: LambdaOptions & { surface?: S; }): Value; /** * The escape hatch: a lambda body as text, validated exactly like {@link fn}. * * For a body that genuinely cannot be an authored function — one assembled at * build time, or lifted verbatim out of a pulled workspace. It is guarded, not * extracted, so the guard cannot be sidestepped by choosing this form. */ declare function raw(code: string, opts?: LambdaOptions>): Value; /** * Lambda authoring. `lam.fn` for an inline typed body, `lam.raw` for text, and * `lam.file` (from `@xanots/core/node`) for a body big enough to want its own * type-checked module. All three produce the same `const:text` {@link Value} and * pass the same validation. * * `file` is deliberately absent from the TYPE here so `lam.file` off this entry * is a compile error that names the Node entry; the runtime stub only catches * the calls types didn't. */ declare const lam: { fn: typeof fn; raw: typeof raw; }; /** A bare JS literal a filter argument accepts in place of a {@link Value}. */ type Scalar = string | number | boolean; /** * Marker for a filter declared `` — it returns the ELEMENT of the array it * is given (`first`, `last`, `array_pop`). Never a value; only a fold signal. */ interface ElementResult { readonly __result: "element"; } /** * Marker for a filter declared `[]` — it returns an array of the same * element type it was given (`reverse`, `unique`, `array_slice`). */ interface SameArrayResult { readonly __result: "sameArray"; } /** * Marker for a GROUP-BY — an object keyed by the argument path whose every * value is an ARRAY of the elements it was given (`index_by`). A single * matching item still arrives wrapped in a one-element array, so a lookup reads * `idx[key][0]`. */ interface GroupedArrayResult { readonly __result: "groupedArray"; } /** Every filter name applicable to a value pipeline (authoritative membership). */ declare const FILTER_NAMES: readonly string[]; /** * Each filter's RESULT type, at the type level. * * Generated from the same `result` the specs above carry, so the two cannot * drift. Three entries are markers rather than concrete types, mirroring the * generic results the catalog declares: * * - {@link ElementResult} (``) — the ELEMENT of the array it is given * (`first`, `last`, `array_pop`, …) * - {@link SameArrayResult} (`[]`) — an array of the same element type * (`reverse`, `unique`, `array_slice`, …) * - {@link GroupedArrayResult} (`{ [key: string]: [] }`) — a group-by * keyed by the argument path (`index_by`) * * A filter whose result is `any` — or one upstream declares nothing for — is * absent from this map and folds to `unknown`. That is deliberate: `get`, * `set`, `transform`, and `json_decode` genuinely produce a shape no * declaration could name, and a confident wrong type is worse than an honest * `unknown`. * * Verified against a live engine rather than trusted (`_probe-filter-results`): * one filter per declared category was executed and its actual JSON type * compared with the declaration. */ interface FilterResults { "abs": number; "acos": number; "acosh": number; "add": number; "addslashes": string; "append": SameArrayResult; "array_diff": SameArrayResult; "array_diff_assoc": SameArrayResult; "array_intersect": SameArrayResult; "array_intersect_assoc": SameArrayResult; "array_keys": string[]; "array_merge": SameArrayResult; "array_merge_recursive": SameArrayResult; "array_pop": ElementResult; "array_push": SameArrayResult; "array_remove": SameArrayResult; "array_shift": ElementResult; "array_shuffle": SameArrayResult; "array_slice": SameArrayResult; "array_unshift": SameArrayResult; "asin": number; "asinh": number; "atan": number; "atanh": number; "avg": number; "base64_decode": string; "base64_decode_urlsafe": string; "base64_encode": string; "base64_encode_urlsafe": string; "base_convert": string; "bin2hex": string; "bindec": string; "bitwise_and": number; "bitwise_not": boolean; "bitwise_or": number; "bitwise_xor": number; "capitalize": string; "ceil": number; "concat": string; "contains": boolean; "convert_encoding": string; "cos": number; "count": number; "crypto_jwe_encode": string; "crypto_jws_encode": string; "csv_create": string; "csv_encode": string; "decbin": string; "dechex": string; "decoct": string; "decrypt": string; "deg2rad": number; "detect_encoding": string; "div": number; "empty": boolean; "encrypt": string; "ends_with": boolean; "epochms_add_ms": number; "epochms_add_secs": number; "epochms_date": string; "epochms_from_format": string; "epochms_transform": string; "eq": boolean; "escape": string; "even": boolean; "exp": number; "filter_empty": SameArrayResult; "filter_empty_array": SameArrayResult; "filter_empty_object": SameArrayResult; "filter_empty_text": SameArrayResult; "filter_false": SameArrayResult; "filter_null": SameArrayResult; "filter_zero": SameArrayResult; "first": ElementResult; "flatten": SameArrayResult; "floor": number; "from_utf8": string; "fsort": SameArrayResult; "gt": boolean; "gte": boolean; "has": boolean; "hex2bin": string; "hexdec": string; "hmac_md5": string; "hmac_sha1": string; "hmac_sha256": string; "hmac_sha384": string; "hmac_sha512": string; "icontains": boolean; "iends_with": boolean; "in": boolean; "index_by": GroupedArrayResult; "is_array": boolean; "is_bool": boolean; "is_decimal": boolean; "is_int": boolean; "is_object": boolean; "is_text": boolean; "is_uuid": boolean; "istarts_with": boolean; "join": string; "json_encode": string; "last": ElementResult; "list_encodings": string[]; "ln": number; "log": number; "log10": number; "lower": string; "lt": boolean; "lte": boolean; "ltrim": string; "max": number; "md5": string; "min": number; "mod": number; "mul": number; "ne": boolean; "not": boolean; "num_max": number; "num_min": number; "number_format": string; "octdec": string; "odd": boolean; "pick": ElementResult; "pow": number; "prepend": SameArrayResult; "product": number; "rad2deg": number; "range": number[]; "regex_match": string[]; "regex_match_all": string[]; "regex_quote": string; "regex_replace": string; "regex_test": boolean; "reverse": SameArrayResult; "round": number; "rtrim": string; "safe_array": SameArrayResult; "secureid_decode": number; "secureid_encode": string; "sha1": string; "sha256": string; "sha384": string; "sha512": string; "sin": number; "split": string[]; "sprintf": string; "sql_alias": string; "sql_esc": string; "sqrt": number; "starts_with": boolean; "string_replace": string; "strip_accents": string; "strip_tags": string; "stripos": number; "strlen": number; "strpos": number; "sub": number; "substr": string; "sum": number; "tan": number; "text_escape": string; "text_unescape": string; "to_bool": boolean; "to_decimal": number; "to_epoch_day": number; "to_epoch_hour": number; "to_epoch_minute": number; "to_epoch_ms": number; "to_epoch_sec": number; "to_epochms": number; "to_int": number; "to_text": string; "to_utf8": string; "trim": string; "uid": number; "unique": SameArrayResult; "unpick": ElementResult; "upper": string; "url_addarg": string; "url_decode": string; "url_decode_rfc3986": string; "url_delarg": string; "url_encode": string; "url_encode_rfc3986": string; "url_getarg": string; "url_hasarg": string; "uuid4": string; "yaml_decode": Record; "yaml_encode": string; } /** Typed, discoverable constructors for the value `filters[]` pipeline. */ declare const fl: { readonly abs: () => FilterXdo<"abs">; readonly acos: () => FilterXdo<"acos">; readonly acosh: () => FilterXdo<"acosh">; readonly add: (((value: Scalar | Value) => FilterXdo<"add">) & ((args: { value: Scalar | Value; }) => FilterXdo<"add">)); readonly addslashes: () => FilterXdo<"addslashes">; readonly append: (((value: Scalar | Value, path: Scalar | Value) => FilterXdo<"append">) & ((args: { value: Scalar | Value; path: Scalar | Value; }) => FilterXdo<"append">)); readonly array_diff: (((value: Scalar | Value) => FilterXdo<"array_diff">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_diff">)); readonly array_diff_assoc: (((value: Scalar | Value) => FilterXdo<"array_diff_assoc">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_diff_assoc">)); readonly array_entries: () => FilterXdo<"array_entries">; readonly array_fill: (((start: Scalar | Value, count: Scalar | Value) => FilterXdo<"array_fill">) & ((args: { start: Scalar | Value; count: Scalar | Value; }) => FilterXdo<"array_fill">)); readonly array_fill_keys: (((keys: Scalar | Value) => FilterXdo<"array_fill_keys">) & ((args: { keys: Scalar | Value; }) => FilterXdo<"array_fill_keys">)); readonly array_intersect: (((value: Scalar | Value) => FilterXdo<"array_intersect">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_intersect">)); readonly array_intersect_assoc: (((value: Scalar | Value) => FilterXdo<"array_intersect_assoc">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_intersect_assoc">)); readonly array_keys: () => FilterXdo<"array_keys">; readonly array_merge: (((value: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"array_merge">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_merge">)); readonly array_merge_recursive: (((value: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"array_merge_recursive">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_merge_recursive">)); readonly array_pop: () => FilterXdo<"array_pop">; readonly array_push: (((value: Scalar | Value) => FilterXdo<"array_push">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_push">)); readonly array_remove: (((value: Scalar | Value, path: Scalar | Value, strict?: Scalar | Value) => FilterXdo<"array_remove">) & ((args: { value: Scalar | Value; path: Scalar | Value; strict?: Scalar | Value; }) => FilterXdo<"array_remove">)); readonly array_shift: () => FilterXdo<"array_shift">; readonly array_shuffle: () => FilterXdo<"array_shuffle">; readonly array_slice: (((offset: Scalar | Value, length?: Scalar | Value) => FilterXdo<"array_slice">) & ((args: { offset: Scalar | Value; length?: Scalar | Value; }) => FilterXdo<"array_slice">)); readonly array_unshift: (((value: Scalar | Value) => FilterXdo<"array_unshift">) & ((args: { value: Scalar | Value; }) => FilterXdo<"array_unshift">)); readonly array_values: () => FilterXdo<"array_values">; readonly asin: () => FilterXdo<"asin">; readonly asinh: () => FilterXdo<"asinh">; readonly atan: () => FilterXdo<"atan">; readonly atanh: () => FilterXdo<"atanh">; readonly avg: () => FilterXdo<"avg">; readonly base64_decode: () => FilterXdo<"base64_decode">; readonly base64_decode_urlsafe: () => FilterXdo<"base64_decode_urlsafe">; readonly base64_encode: () => FilterXdo<"base64_encode">; readonly base64_encode_urlsafe: () => FilterXdo<"base64_encode_urlsafe">; readonly base_convert: (((from_base: Scalar | Value, to_base: Scalar | Value) => FilterXdo<"base_convert">) & ((args: { from_base: Scalar | Value; to_base: Scalar | Value; }) => FilterXdo<"base_convert">)); readonly bin2hex: () => FilterXdo<"bin2hex">; readonly bindec: () => FilterXdo<"bindec">; readonly bitwise_and: (((value: Scalar | Value) => FilterXdo<"bitwise_and">) & ((args: { value: Scalar | Value; }) => FilterXdo<"bitwise_and">)); readonly bitwise_not: () => FilterXdo<"bitwise_not">; readonly bitwise_or: (((value: Scalar | Value) => FilterXdo<"bitwise_or">) & ((args: { value: Scalar | Value; }) => FilterXdo<"bitwise_or">)); readonly bitwise_xor: (((value: Scalar | Value) => FilterXdo<"bitwise_xor">) & ((args: { value: Scalar | Value; }) => FilterXdo<"bitwise_xor">)); readonly capitalize: () => FilterXdo<"capitalize">; readonly ceil: () => FilterXdo<"ceil">; readonly concat: (((value: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"concat">) & ((args: { value: Scalar | Value; }) => FilterXdo<"concat">)); readonly contains: (((search: Scalar | Value) => FilterXdo<"contains">) & ((args: { search: Scalar | Value; }) => FilterXdo<"contains">)); readonly convert_encoding: (((to: Scalar | Value, from: Scalar | Value) => FilterXdo<"convert_encoding">) & ((args: { to: Scalar | Value; from: Scalar | Value; }) => FilterXdo<"convert_encoding">)); readonly cos: () => FilterXdo<"cos">; readonly count: () => FilterXdo<"count">; readonly create_object: (((values: Scalar | Value) => FilterXdo<"create_object">) & ((args: { values: Scalar | Value; }) => FilterXdo<"create_object">)); readonly create_object_from_entries: () => FilterXdo<"create_object_from_entries">; readonly crypto_jwe_decode: (((check_claims: Scalar | Value, key: Scalar | Value, key_algorithm: Scalar | Value, content_algorithm: Scalar | Value, timeDrift?: Scalar | Value) => FilterXdo<"crypto_jwe_decode">) & ((args: { check_claims: Scalar | Value; key: Scalar | Value; key_algorithm: Scalar | Value; content_algorithm: Scalar | Value; timeDrift?: Scalar | Value; }) => FilterXdo<"crypto_jwe_decode">)); readonly crypto_jwe_encode: (((headers: Scalar | Value, key: Scalar | Value, key_algorithm: Scalar | Value, content_algorithm: Scalar | Value, ttl?: Scalar | Value) => FilterXdo<"crypto_jwe_encode">) & ((args: { headers: Scalar | Value; key: Scalar | Value; key_algorithm: Scalar | Value; content_algorithm: Scalar | Value; ttl?: Scalar | Value; }) => FilterXdo<"crypto_jwe_encode">)); readonly crypto_jws_decode: (((check_claims: Scalar | Value, key: Scalar | Value, algorithm: Scalar | Value, timeDrift?: Scalar | Value) => FilterXdo<"crypto_jws_decode">) & ((args: { check_claims: Scalar | Value; key: Scalar | Value; algorithm: Scalar | Value; timeDrift?: Scalar | Value; }) => FilterXdo<"crypto_jws_decode">)); readonly crypto_jws_encode: (((headers: Scalar | Value, key: Scalar | Value, algorithm: Scalar | Value, ttl?: Scalar | Value) => FilterXdo<"crypto_jws_encode">) & ((args: { headers: Scalar | Value; key: Scalar | Value; algorithm: Scalar | Value; ttl?: Scalar | Value; }) => FilterXdo<"crypto_jws_encode">)); readonly csv_create: (((rows: Scalar | Value, separator: Scalar | Value, enclosure: Scalar | Value, escape: Scalar | Value) => FilterXdo<"csv_create">) & ((args: { rows: Scalar | Value; separator: Scalar | Value; enclosure: Scalar | Value; escape: Scalar | Value; }) => FilterXdo<"csv_create">)); readonly csv_decode: (((separator: Scalar | Value, enclosure: Scalar | Value, escape: Scalar | Value) => FilterXdo<"csv_decode">) & ((args: { separator: Scalar | Value; enclosure: Scalar | Value; escape: Scalar | Value; }) => FilterXdo<"csv_decode">)); readonly csv_encode: (((separator: Scalar | Value, enclosure: Scalar | Value, escape: Scalar | Value) => FilterXdo<"csv_encode">) & ((args: { separator: Scalar | Value; enclosure: Scalar | Value; escape: Scalar | Value; }) => FilterXdo<"csv_encode">)); readonly csv_parse: (((separator: Scalar | Value, enclosure: Scalar | Value, escape: Scalar | Value) => FilterXdo<"csv_parse">) & ((args: { separator: Scalar | Value; enclosure: Scalar | Value; escape: Scalar | Value; }) => FilterXdo<"csv_parse">)); readonly decbin: () => FilterXdo<"decbin">; readonly dechex: () => FilterXdo<"dechex">; readonly decoct: () => FilterXdo<"decoct">; readonly decrypt: (((algorithm: Scalar | Value, key: Scalar | Value, iv: Scalar | Value) => FilterXdo<"decrypt">) & ((args: { algorithm: Scalar | Value; key: Scalar | Value; iv: Scalar | Value; }) => FilterXdo<"decrypt">)); readonly deg2rad: () => FilterXdo<"deg2rad">; readonly detect_encoding: (((encodings?: Scalar | Value) => FilterXdo<"detect_encoding">) & ((args: { encodings?: Scalar | Value; }) => FilterXdo<"detect_encoding">)); readonly div: (((value: Scalar | Value) => FilterXdo<"div">) & ((args: { value: Scalar | Value; }) => FilterXdo<"div">)); readonly empty: () => FilterXdo<"empty">; readonly encrypt: (((algorithm: Scalar | Value, key: Scalar | Value, iv: Scalar | Value) => FilterXdo<"encrypt">) & ((args: { algorithm: Scalar | Value; key: Scalar | Value; iv: Scalar | Value; }) => FilterXdo<"encrypt">)); readonly ends_with: (((search: Scalar | Value) => FilterXdo<"ends_with">) & ((args: { search: Scalar | Value; }) => FilterXdo<"ends_with">)); readonly epochms_add_ms: (((milliseconds: Scalar | Value) => FilterXdo<"epochms_add_ms">) & ((args: { milliseconds: Scalar | Value; }) => FilterXdo<"epochms_add_ms">)); readonly epochms_add_secs: (((seconds: Scalar | Value) => FilterXdo<"epochms_add_secs">) & ((args: { seconds: Scalar | Value; }) => FilterXdo<"epochms_add_secs">)); readonly epochms_date: (((format: Scalar | Value, timezone?: Scalar | Value) => FilterXdo<"epochms_date">) & ((args: { format: Scalar | Value; timezone?: Scalar | Value; }) => FilterXdo<"epochms_date">)); readonly epochms_from_format: (((format: Scalar | Value, timezone?: Scalar | Value) => FilterXdo<"epochms_from_format">) & ((args: { format: Scalar | Value; timezone?: Scalar | Value; }) => FilterXdo<"epochms_from_format">)); readonly epochms_transform: (((format: Scalar | Value, timezone?: Scalar | Value) => FilterXdo<"epochms_transform">) & ((args: { format: Scalar | Value; timezone?: Scalar | Value; }) => FilterXdo<"epochms_transform">)); readonly eq: (((value: Scalar | Value) => FilterXdo<"eq">) & ((args: { value: Scalar | Value; }) => FilterXdo<"eq">)); readonly escape: () => FilterXdo<"escape">; readonly even: () => FilterXdo<"even">; readonly every: (((code: Scalar | Value | LambdaBody<"every">, timeout?: Scalar | Value) => FilterXdo<"every">) & ((args: { code: Scalar | Value | LambdaBody<"every">; timeout?: Scalar | Value; }) => FilterXdo<"every">)); readonly exp: () => FilterXdo<"exp">; readonly filter: (((code: Scalar | Value | LambdaBody<"filter">, timeout?: Scalar | Value) => FilterXdo<"filter">) & ((args: { code: Scalar | Value | LambdaBody<"filter">; timeout?: Scalar | Value; }) => FilterXdo<"filter">)); readonly filter_empty: (((path?: Scalar | Value) => FilterXdo<"filter_empty">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_empty">)); readonly filter_empty_array: (((path?: Scalar | Value) => FilterXdo<"filter_empty_array">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_empty_array">)); readonly filter_empty_object: (((path?: Scalar | Value) => FilterXdo<"filter_empty_object">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_empty_object">)); readonly filter_empty_text: (((path?: Scalar | Value) => FilterXdo<"filter_empty_text">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_empty_text">)); readonly filter_false: (((path?: Scalar | Value) => FilterXdo<"filter_false">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_false">)); readonly filter_null: (((path?: Scalar | Value) => FilterXdo<"filter_null">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_null">)); readonly filter_zero: (((path?: Scalar | Value) => FilterXdo<"filter_zero">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"filter_zero">)); readonly find: (((code: Scalar | Value | LambdaBody<"find">, timeout?: Scalar | Value) => FilterXdo<"find">) & ((args: { code: Scalar | Value | LambdaBody<"find">; timeout?: Scalar | Value; }) => FilterXdo<"find">)); readonly findIndex: (((code: Scalar | Value | LambdaBody<"findIndex">, timeout?: Scalar | Value) => FilterXdo<"findIndex">) & ((args: { code: Scalar | Value | LambdaBody<"findIndex">; timeout?: Scalar | Value; }) => FilterXdo<"findIndex">)); readonly first: () => FilterXdo<"first">; readonly first_notempty: (((value: Scalar | Value) => FilterXdo<"first_notempty">) & ((args: { value: Scalar | Value; }) => FilterXdo<"first_notempty">)); readonly first_notnull: (((value: Scalar | Value) => FilterXdo<"first_notnull">) & ((args: { value: Scalar | Value; }) => FilterXdo<"first_notnull">)); readonly flatten: () => FilterXdo<"flatten">; readonly floor: () => FilterXdo<"floor">; readonly from_utf8: () => FilterXdo<"from_utf8">; readonly fsort: (((path?: Scalar | Value, type?: "text" | "itext" | "natural" | "inatural" | "number" | Value, asc?: Scalar | Value) => FilterXdo<"fsort">) & ((args: { path?: Scalar | Value; type?: "text" | "itext" | "natural" | "inatural" | "number" | Value; asc?: Scalar | Value; }) => FilterXdo<"fsort">)); readonly get: (((path: Scalar | Value, default_?: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"get">) & ((args: { path: Scalar | Value; default_?: Scalar | Value; }) => FilterXdo<"get">)); readonly gt: (((value: Scalar | Value) => FilterXdo<"gt">) & ((args: { value: Scalar | Value; }) => FilterXdo<"gt">)); readonly gte: (((value: Scalar | Value) => FilterXdo<"gte">) & ((args: { value: Scalar | Value; }) => FilterXdo<"gte">)); readonly has: (((path: Scalar | Value) => FilterXdo<"has">) & ((args: { path: Scalar | Value; }) => FilterXdo<"has">)); readonly hex2bin: () => FilterXdo<"hex2bin">; readonly hexdec: () => FilterXdo<"hexdec">; readonly hmac_md5: (((key: Scalar | Value, raw?: Scalar | Value) => FilterXdo<"hmac_md5">) & ((args: { key: Scalar | Value; raw?: Scalar | Value; }) => FilterXdo<"hmac_md5">)); readonly hmac_sha1: (((key: Scalar | Value, raw?: Scalar | Value) => FilterXdo<"hmac_sha1">) & ((args: { key: Scalar | Value; raw?: Scalar | Value; }) => FilterXdo<"hmac_sha1">)); readonly hmac_sha256: (((key: Scalar | Value, raw?: Scalar | Value) => FilterXdo<"hmac_sha256">) & ((args: { key: Scalar | Value; raw?: Scalar | Value; }) => FilterXdo<"hmac_sha256">)); readonly hmac_sha384: (((key: Scalar | Value, raw?: Scalar | Value) => FilterXdo<"hmac_sha384">) & ((args: { key: Scalar | Value; raw?: Scalar | Value; }) => FilterXdo<"hmac_sha384">)); readonly hmac_sha512: (((key: Scalar | Value, raw?: Scalar | Value) => FilterXdo<"hmac_sha512">) & ((args: { key: Scalar | Value; raw?: Scalar | Value; }) => FilterXdo<"hmac_sha512">)); readonly icontains: (((search: Scalar | Value) => FilterXdo<"icontains">) & ((args: { search: Scalar | Value; }) => FilterXdo<"icontains">)); readonly iends_with: (((search: Scalar | Value) => FilterXdo<"iends_with">) & ((args: { search: Scalar | Value; }) => FilterXdo<"iends_with">)); readonly in: (((search: Scalar | Value) => FilterXdo<"in">) & ((args: { search: Scalar | Value; }) => FilterXdo<"in">)); readonly index_by: (((path: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"index_by">) & ((args: { path: Scalar | Value; }) => FilterXdo<"index_by">)); readonly is_array: () => FilterXdo<"is_array">; readonly is_bool: () => FilterXdo<"is_bool">; readonly is_decimal: () => FilterXdo<"is_decimal">; readonly is_int: () => FilterXdo<"is_int">; readonly is_object: () => FilterXdo<"is_object">; readonly is_text: () => FilterXdo<"is_text">; readonly is_uuid: () => FilterXdo<"is_uuid">; readonly istarts_with: (((search: Scalar | Value) => FilterXdo<"istarts_with">) & ((args: { search: Scalar | Value; }) => FilterXdo<"istarts_with">)); readonly join: (((separator: Scalar | Value) => FilterXdo<"join">) & ((args: { separator: Scalar | Value; }) => FilterXdo<"join">)); readonly json_decode: () => FilterXdo<"json_decode">; readonly json_encode: () => FilterXdo<"json_encode">; readonly jwe_decode: (((arg1: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"jwe_decode">) & ((args: { arg1: Scalar | Value; }) => FilterXdo<"jwe_decode">)); readonly jwe_encode: (((arg1: Scalar | Value, ...rest: (Scalar | Value)[]) => FilterXdo<"jwe_encode">) & ((args: { arg1: Scalar | Value; }) => FilterXdo<"jwe_encode">)); readonly lambda: (((code: Scalar | Value | LambdaBody<"fl.lambda">, timeout?: Scalar | Value) => FilterXdo<"lambda">) & ((args: { code: Scalar | Value | LambdaBody<"fl.lambda">; timeout?: Scalar | Value; }) => FilterXdo<"lambda">)); readonly last: () => FilterXdo<"last">; readonly list_encodings: () => FilterXdo<"list_encodings">; readonly ln: () => FilterXdo<"ln">; readonly log: (((base: Scalar | Value) => FilterXdo<"log">) & ((args: { base: Scalar | Value; }) => FilterXdo<"log">)); readonly log10: () => FilterXdo<"log10">; readonly lower: () => FilterXdo<"lower">; readonly lt: (((value: Scalar | Value) => FilterXdo<"lt">) & ((args: { value: Scalar | Value; }) => FilterXdo<"lt">)); readonly lte: (((value: Scalar | Value) => FilterXdo<"lte">) & ((args: { value: Scalar | Value; }) => FilterXdo<"lte">)); readonly ltrim: (((mask?: Scalar | Value) => FilterXdo<"ltrim">) & ((args: { mask?: Scalar | Value; }) => FilterXdo<"ltrim">)); readonly map: (((code: Scalar | Value | LambdaBody<"map">, timeout?: Scalar | Value) => FilterXdo<"map">) & ((args: { code: Scalar | Value | LambdaBody<"map">; timeout?: Scalar | Value; }) => FilterXdo<"map">)); readonly max: () => FilterXdo<"max">; readonly md5: (((raw?: Scalar | Value) => FilterXdo<"md5">) & ((args: { raw?: Scalar | Value; }) => FilterXdo<"md5">)); readonly min: () => FilterXdo<"min">; readonly mod: (((value: Scalar | Value) => FilterXdo<"mod">) & ((args: { value: Scalar | Value; }) => FilterXdo<"mod">)); readonly mul: (((value: Scalar | Value) => FilterXdo<"mul">) & ((args: { value: Scalar | Value; }) => FilterXdo<"mul">)); readonly ne: (((value: Scalar | Value) => FilterXdo<"ne">) & ((args: { value: Scalar | Value; }) => FilterXdo<"ne">)); readonly not: () => FilterXdo<"not">; readonly null: () => FilterXdo<"null">; readonly num_max: (((value: Scalar | Value) => FilterXdo<"num_max">) & ((args: { value: Scalar | Value; }) => FilterXdo<"num_max">)); readonly num_min: (((value: Scalar | Value) => FilterXdo<"num_min">) & ((args: { value: Scalar | Value; }) => FilterXdo<"num_min">)); readonly number_format: (((decimals: Scalar | Value, decimal_separator: Scalar | Value, thousands_separator: Scalar | Value) => FilterXdo<"number_format">) & ((args: { decimals: Scalar | Value; decimal_separator: Scalar | Value; thousands_separator: Scalar | Value; }) => FilterXdo<"number_format">)); readonly octdec: () => FilterXdo<"octdec">; readonly odd: () => FilterXdo<"odd">; readonly pick: (((keys: Scalar | Value) => FilterXdo<"pick">) & ((args: { keys: Scalar | Value; }) => FilterXdo<"pick">)); readonly pow: (((exp: Scalar | Value) => FilterXdo<"pow">) & ((args: { exp: Scalar | Value; }) => FilterXdo<"pow">)); readonly prepend: (((value: Scalar | Value, path: Scalar | Value) => FilterXdo<"prepend">) & ((args: { value: Scalar | Value; path: Scalar | Value; }) => FilterXdo<"prepend">)); readonly product: () => FilterXdo<"product">; readonly querystring_parse: () => FilterXdo<"querystring_parse">; readonly rad2deg: () => FilterXdo<"rad2deg">; readonly range: (((start: Scalar | Value, stop: Scalar | Value) => FilterXdo<"range">) & ((args: { start: Scalar | Value; stop: Scalar | Value; }) => FilterXdo<"range">)); readonly reduce: (((initial_value: Scalar | Value, code: Scalar | Value | LambdaBody<"reduce">, timeout?: Scalar | Value) => FilterXdo<"reduce">) & ((args: { initial_value: Scalar | Value; code: Scalar | Value | LambdaBody<"reduce">; timeout?: Scalar | Value; }) => FilterXdo<"reduce">)); readonly regex_match: (((subject: Scalar | Value) => FilterXdo<"regex_match">) & ((args: { subject: Scalar | Value; }) => FilterXdo<"regex_match">)); readonly regex_match_all: (((subject: Scalar | Value) => FilterXdo<"regex_match_all">) & ((args: { subject: Scalar | Value; }) => FilterXdo<"regex_match_all">)); readonly regex_quote: (((delimiter?: Scalar | Value) => FilterXdo<"regex_quote">) & ((args: { delimiter?: Scalar | Value; }) => FilterXdo<"regex_quote">)); readonly regex_replace: (((replacement: Scalar | Value, subject: Scalar | Value) => FilterXdo<"regex_replace">) & ((args: { replacement: Scalar | Value; subject: Scalar | Value; }) => FilterXdo<"regex_replace">)); readonly regex_test: (((subject: Scalar | Value) => FilterXdo<"regex_test">) & ((args: { subject: Scalar | Value; }) => FilterXdo<"regex_test">)); readonly reverse: () => FilterXdo<"reverse">; readonly round: (((precision?: Scalar | Value) => FilterXdo<"round">) & ((args: { precision?: Scalar | Value; }) => FilterXdo<"round">)); readonly rtrim: (((mask?: Scalar | Value) => FilterXdo<"rtrim">) & ((args: { mask?: Scalar | Value; }) => FilterXdo<"rtrim">)); readonly safe_array: () => FilterXdo<"safe_array">; readonly secureid_decode: (((salt: Scalar | Value) => FilterXdo<"secureid_decode">) & ((args: { salt: Scalar | Value; }) => FilterXdo<"secureid_decode">)); readonly secureid_encode: (((salt: Scalar | Value) => FilterXdo<"secureid_encode">) & ((args: { salt: Scalar | Value; }) => FilterXdo<"secureid_encode">)); readonly set: (((path: Scalar | Value, value: Scalar | Value) => FilterXdo<"set">) & ((args: { path: Scalar | Value; value: Scalar | Value; }) => FilterXdo<"set">)); readonly set_conditional: (((path: Scalar | Value, value: Scalar | Value, conditional: Scalar | Value) => FilterXdo<"set_conditional">) & ((args: { path: Scalar | Value; value: Scalar | Value; conditional: Scalar | Value; }) => FilterXdo<"set_conditional">)); readonly set_ifnotempty: (((path: Scalar | Value, value: Scalar | Value) => FilterXdo<"set_ifnotempty">) & ((args: { path: Scalar | Value; value: Scalar | Value; }) => FilterXdo<"set_ifnotempty">)); readonly set_ifnotnull: (((path: Scalar | Value, value: Scalar | Value) => FilterXdo<"set_ifnotnull">) & ((args: { path: Scalar | Value; value: Scalar | Value; }) => FilterXdo<"set_ifnotnull">)); readonly sha1: (((raw?: Scalar | Value) => FilterXdo<"sha1">) & ((args: { raw?: Scalar | Value; }) => FilterXdo<"sha1">)); readonly sha256: (((raw?: Scalar | Value) => FilterXdo<"sha256">) & ((args: { raw?: Scalar | Value; }) => FilterXdo<"sha256">)); readonly sha384: (((raw?: Scalar | Value) => FilterXdo<"sha384">) & ((args: { raw?: Scalar | Value; }) => FilterXdo<"sha384">)); readonly sha512: (((raw?: Scalar | Value) => FilterXdo<"sha512">) & ((args: { raw?: Scalar | Value; }) => FilterXdo<"sha512">)); readonly sin: () => FilterXdo<"sin">; readonly some: (((code: Scalar | Value | LambdaBody<"some">, timeout?: Scalar | Value) => FilterXdo<"some">) & ((args: { code: Scalar | Value | LambdaBody<"some">; timeout?: Scalar | Value; }) => FilterXdo<"some">)); readonly sort: (...args: (Scalar | Value)[]) => FilterXdo<"sort">; readonly split: (((separator: Scalar | Value) => FilterXdo<"split">) & ((args: { separator: Scalar | Value; }) => FilterXdo<"split">)); readonly sprintf: (...args: (Scalar | Value)[]) => FilterXdo<"sprintf">; readonly sql_alias: () => FilterXdo<"sql_alias">; readonly sql_esc: () => FilterXdo<"sql_esc">; readonly sqrt: () => FilterXdo<"sqrt">; readonly starts_with: (((search: Scalar | Value) => FilterXdo<"starts_with">) & ((args: { search: Scalar | Value; }) => FilterXdo<"starts_with">)); readonly string_replace: (((search: Scalar | Value, replacement: Scalar | Value) => FilterXdo<"string_replace">) & ((args: { search: Scalar | Value; replacement: Scalar | Value; }) => FilterXdo<"string_replace">)); readonly strip_accents: () => FilterXdo<"strip_accents">; readonly strip_tags: (((exclude?: Scalar | Value) => FilterXdo<"strip_tags">) & ((args: { exclude?: Scalar | Value; }) => FilterXdo<"strip_tags">)); readonly stripos: (((search: Scalar | Value) => FilterXdo<"stripos">) & ((args: { search: Scalar | Value; }) => FilterXdo<"stripos">)); readonly strlen: () => FilterXdo<"strlen">; readonly strpos: (((search: Scalar | Value) => FilterXdo<"strpos">) & ((args: { search: Scalar | Value; }) => FilterXdo<"strpos">)); readonly sub: (((value: Scalar | Value) => FilterXdo<"sub">) & ((args: { value: Scalar | Value; }) => FilterXdo<"sub">)); readonly substr: (((start: Scalar | Value, length: Scalar | Value) => FilterXdo<"substr">) & ((args: { start: Scalar | Value; length: Scalar | Value; }) => FilterXdo<"substr">)); readonly sum: () => FilterXdo<"sum">; readonly tan: () => FilterXdo<"tan">; readonly text_escape: () => FilterXdo<"text_escape">; readonly text_unescape: () => FilterXdo<"text_unescape">; readonly to_bool: () => FilterXdo<"to_bool">; readonly to_decimal: () => FilterXdo<"to_decimal">; readonly to_epoch_day: (((timezone?: Scalar | Value) => FilterXdo<"to_epoch_day">) & ((args: { timezone?: Scalar | Value; }) => FilterXdo<"to_epoch_day">)); readonly to_epoch_hour: (((timezone?: Scalar | Value) => FilterXdo<"to_epoch_hour">) & ((args: { timezone?: Scalar | Value; }) => FilterXdo<"to_epoch_hour">)); readonly to_epoch_minute: (((timezone?: Scalar | Value) => FilterXdo<"to_epoch_minute">) & ((args: { timezone?: Scalar | Value; }) => FilterXdo<"to_epoch_minute">)); readonly to_epoch_ms: (((timezone?: Scalar | Value) => FilterXdo<"to_epoch_ms">) & ((args: { timezone?: Scalar | Value; }) => FilterXdo<"to_epoch_ms">)); readonly to_epoch_sec: (((timezone?: Scalar | Value) => FilterXdo<"to_epoch_sec">) & ((args: { timezone?: Scalar | Value; }) => FilterXdo<"to_epoch_sec">)); readonly to_epochms: (((timezone?: Scalar | Value) => FilterXdo<"to_epochms">) & ((args: { timezone?: Scalar | Value; }) => FilterXdo<"to_epochms">)); readonly to_expr: () => FilterXdo<"to_expr">; readonly to_int: () => FilterXdo<"to_int">; readonly to_text: () => FilterXdo<"to_text">; readonly to_utf8: () => FilterXdo<"to_utf8">; readonly transform: (((expression: Scalar | Value) => FilterXdo<"transform">) & ((args: { expression: Scalar | Value; }) => FilterXdo<"transform">)); readonly trim: (((mask?: Scalar | Value) => FilterXdo<"trim">) & ((args: { mask?: Scalar | Value; }) => FilterXdo<"trim">)); readonly uid: () => FilterXdo<"uid">; readonly unique: (((path?: Scalar | Value) => FilterXdo<"unique">) & ((args: { path?: Scalar | Value; }) => FilterXdo<"unique">)); readonly unpick: (((keys: Scalar | Value) => FilterXdo<"unpick">) & ((args: { keys: Scalar | Value; }) => FilterXdo<"unpick">)); readonly unset: (((path: Scalar | Value) => FilterXdo<"unset">) & ((args: { path: Scalar | Value; }) => FilterXdo<"unset">)); readonly upper: () => FilterXdo<"upper">; readonly url_addarg: (((key: Scalar | Value, value: Scalar | Value, encoding_rfc3986?: Scalar | Value) => FilterXdo<"url_addarg">) & ((args: { key: Scalar | Value; value: Scalar | Value; encoding_rfc3986?: Scalar | Value; }) => FilterXdo<"url_addarg">)); readonly url_decode: () => FilterXdo<"url_decode">; readonly url_decode_rfc3986: () => FilterXdo<"url_decode_rfc3986">; readonly url_delarg: (((key: Scalar | Value) => FilterXdo<"url_delarg">) & ((args: { key: Scalar | Value; }) => FilterXdo<"url_delarg">)); readonly url_encode: () => FilterXdo<"url_encode">; readonly url_encode_rfc3986: () => FilterXdo<"url_encode_rfc3986">; readonly url_getarg: (((key: Scalar | Value, default_?: Scalar | Value) => FilterXdo<"url_getarg">) & ((args: { key: Scalar | Value; default_?: Scalar | Value; }) => FilterXdo<"url_getarg">)); readonly url_hasarg: (((key: Scalar | Value) => FilterXdo<"url_hasarg">) & ((args: { key: Scalar | Value; }) => FilterXdo<"url_hasarg">)); readonly url_parse: () => FilterXdo<"url_parse">; readonly uuid4: () => FilterXdo<"uuid4">; readonly xml_decode: () => FilterXdo<"xml_decode">; readonly yaml_decode: () => FilterXdo<"yaml_decode">; readonly yaml_encode: () => FilterXdo<"yaml_encode">; }; /** * Filters the engine resolves inside a **db-query expression** — an `eval` * pipeline, a `sort` term, or a `where`/search operand — and nowhere else. * * There are two filter registries, and they are not the same set. `fl.*` (see * `generated/filters.generated.ts`) is the RUNTIME pipeline: it evaluates a * value in the request. The names below evaluate in **SQL**, compiled into the * statement the database runs, so they are reachable only from a query's own * expression surfaces — and, being absent from the runtime catalog, every one of * them used to be reported by `findUnresolvableFilters` as "will 500 at runtime" * (issue #30). That warning steered authors off the only spelling that works. * * The distinction is worth stating because it is what makes vector search * possible at all: `f.vector` columns and their pgvector indexes had no query * surface in the typed API, and the documented `direct_query` escape hatch needs * a physical table name that is reassigned on every import. The distance filters * here close that — the ranking happens in the database, over the index, instead * of pulling candidate rows into the request to score them. * * Live-verified on a deployed ephemeral: an `eval` of * `vector_cos_distance` over a `f.vector(3)` column, sorted by the eval's alias, * returned rows ordered by cosine distance (0, 0.0061, 1); the same filter on a * `where` operand filtered on distance. Both are exercised by * `examples/sandbox`. * * Browser-safe: a plain string list, no imports. */ /** * Vector distance/similarity over an `f.vector` column. Each takes ONE argument * — the query vector, as a `decimal[]` value (`inp("q")`, `c.array([…])`) — and * yields a `decimal` to sort or compare on. Pair each with the matching index * `op` so the index is usable: `vector_cos_distance` ↔ `vector_cosine_ops`, * `vector_l2_distance` ↔ `vector_l2_ops`, `vector_l1_distance` ↔ `vector_l1_ops`, * `vector_inner_product`/`vector_negative_inner_product` ↔ `vector_ip_ops`. * * Distances sort ASCENDING (nearest first); `vector_cos_similarity` is the * inverse, so sort it descending. */ declare const VECTOR_FILTERS: readonly ["vector_distance", "vector_cos_distance", "vector_cos_similarity", "vector_inner_product", "vector_negative_inner_product", "vector_l1_distance", "vector_l2_distance"]; /** * Every filter resolvable in a query expression but NOT in a value pipeline: * the vector family, the geo predicates (`distance`/`within`/`covers`), the * full-text `search_rank`, and the SQL-side spellings of the length/coalesce/ * timestamp helpers (`between_filter` is the SQL `BETWEEN`, distinct from the * runtime `between`). * * This list is the union of the two registries' difference, not a curated * subset: a name missing from it is reported as unresolvable, which is the false * warning this exists to prevent. */ declare const QUERY_EXPRESSION_FILTERS: readonly ["vector_distance", "vector_cos_distance", "vector_cos_similarity", "vector_inner_product", "vector_negative_inner_product", "vector_l1_distance", "vector_l2_distance", "covers", "distance", "within", "search_rank", "unaccent", "array_length", "at_timezone", "between_filter", "coalesce", "length", "time", "to_timestamp", "epochms_add_day", "epochms_add_hour", "epochms_add_minute", "epochms_add_month", "epochms_add_sec", "epochms_add_year", "epochms_sub_day", "epochms_sub_hour", "epochms_sub_minute", "epochms_sub_month", "epochms_sub_sec", "epochms_sub_year", "epochms_day", "epochms_dow", "epochms_doy", "epochms_epoch_day", "epochms_epoch_hour", "epochms_epoch_minute", "epochms_epoch_sec", "epochms_hour", "epochms_minute", "epochms_month", "epochms_week", "epochms_year", "count_distinct", "median", "to_list", "to_list_asc", "to_list_desc", "to_distinct_list", "to_distinct_list_asc", "to_distinct_list_desc"]; /** * A filter name a db-query expression resolves. Typed as the known set plus * `string` so the runtime catalog (`fl.*` names, e.g. `count`/`sum` in an * aggregate) and anything this list has not caught up with stay authorable — * autocomplete without a closed door. */ type QueryFilterName = (typeof QUERY_EXPRESSION_FILTERS)[number] | (string & {}); /** Membership test for {@link QUERY_EXPRESSION_FILTERS}. */ declare const isQueryExpressionFilter: (name: string) => boolean; /** * A selectable output path: a column of the bound table, a dotted path (into an * object column, or into a joined table), or a field of the paging envelope. * * The bound table's schema is not the set of valid roots. A real query also * selects from joined tables (`photo.id`, `merchant.id`) and, when it is paged, * from the envelope wrapped around the rows (`itemsReceived`, `curPage`, * `items.title`) — none of which any table declares. Typed as bare columns, the * union rejected valid queries and a tree pulled from one did not compile. * * The bare-name arm stays CLOSED, so a typo is still an error. Only the two * forms that were provably wrong are open: see {@link QualifiedCol} for the * dotted root, and {@link PagingEnvelopeField} for the envelope. */ type OutputPath = QualifiedCol | PagingEnvelopeField; /** * A column of the bound table, or a column of any OTHER table reached by a * dotted path. * * A dot at the root means the root is a table, not a column of this one: * `photo.id` reads the joined `photo`, and `comments.id` qualifies the bound * table by its own `tableAlias`, which is what Xano's editor writes. The SDK's * own docs say so — "joined columns are addressable by dotted path in * `where`/`sort`/`eval`" — and `SortDirective.sortBy` is documented as "the * column (or dot-path)". Both were typed as a bare column anyway, so 45 real * selections and sorts across 11 workspaces did not compile. * * The bare arm stays closed: a typo like `"emial"` is still an error, which is * where the union earns its keep. Nothing is given up by opening the dotted form, * because the engine has no other meaning for a dot at the root. * * ⚠ `comments.id` is only the bound table's own alias WHEN THE QUERY DECLARES ONE * (`tableAlias`) — that is why Xano's editor can write it and a hand-authored * query cannot. Without `tableAlias`, qualifying the bound table by its NAME does * not resolve, and the query fails at runtime (issue #213); `db.query` checks the * `where`/`sort`/`eval` paths for it at export. Bare is the form to reach for. */ type QualifiedCol = C | `${string}.${string}`; /** * The fields a PAGED read adds around the rows. Selectable like a column and * declared by no table, so a paged query's `output` names them directly. */ type PagingEnvelopeField = "itemsReceived" | "itemsTotal" | "curPage" | "nextPage" | "prevPage" | "pageTotal" | "offset" | "perPage"; /** The root segment of a dotted output path — the column it selects from. */ type OutputRoot

= P extends `${infer Head}.${string}` ? Head : P; /** * Shared db-search authoring primitives — the `where`/`sort` surface used by * both `s.db.query` (`./db.ts`) and a table-bound `addon()` * (`../../kinds/addon.ts`). Extracted here so the addon kind can reuse the exact * same builders without importing `db.ts` (which imports the addon kind — a * cycle). * * The boolean-expression algebra (`cmp`/`and`/`or`, the node types, the tree * walk) now lives in {@link ../expression.js}; this module keeps the db-specific * pieces — `where`/`additionalWhere` merge, sort, eval — and supplies the * filter-rejecting operand encoder (#118) to the shared walk. */ /** Sort direction for a {@link SortDirective} — the engine's `orderBy` values. */ type SortDir = "asc" | "desc" | "rand"; /** * One sort directive: order the returned rows by `sortBy`, ascending, descending, * or random. `dir` maps to the engine's `orderBy`; the encoded element is the * `mvp_sort` shape `{ sortBy, orderBy }`. Each caller places that element * differently — `db.query` under `context.return.list.sort` (via * the engine's context-to-config conversion), an `addon()` at top-level `context.sort` — so * this doc stays placement-neutral; see each caller's own doc for where it lands. */ interface SortDirective { /** * The column to sort by, or a dotted path qualifying one — a joined table's * column (its `bind` alias), or the bound table's own `tableAlias` * (`"comments.id"`), which is the form Xano's editor writes. A column of the * bound table is BARE unless the query sets `tableAlias`; see * {@link QualifiedCol}. */ sortBy: QualifiedCol; /** Direction (`"asc"` | `"desc"` | `"rand"`); defaults to ascending. */ dir?: SortDir; } /** * A `db.query`/addon filter. Author it as a comparison (or several, ANDed) with * `expr(col("status"), "=", c.text("published"))` or, for the full operator set, * `cmp(col("tags"), "overlaps", inp("t"))`. Compose nested boolean logic with * `and(...)` / `or(...)`. A raw `Value` stays the escape hatch for a pre-built * clause. Encoded into the engine's operand-based `{expression:[…]}` search shape. */ type DbWhere = Value | SearchNode | SearchNode[]; /** One step of an eval filter pipeline (`{ name, arg, disabled? }`) — engine `mvp_filter`. */ interface DbEvalFilter { /** * The filter to apply. This pipeline is compiled into SQL, so it resolves * against the query-expression registry — the `fl.*` runtime names PLUS the * SQL-only ones in {@link QueryFilterName} (vector distance, geo, aggregates, * `search_rank`). Open to any string: the two registries are the engine's, and * an unknown name is reported at export rather than blocked here. * * Vector similarity search is this, plus a sort on the eval's alias: * ```ts * s.db.query({ * table: chunk, * eval: [{ name: "embedding", as: "distance", * filters: [{ name: "vector_cos_distance", arg: [inp("q")] }] }], * sort: [{ sortBy: "distance", dir: "asc" }], // nearest first * }) * ``` * The ranking runs in the database over the `f.vector` column's index — not by * reading candidate rows into the request to score them. */ name: QueryFilterName; /** Filter args as tagged values (encoded `{value,tag,filters}`). */ arg?: Value[]; /** Skip this step (kept in the stored pipeline as `disabled:true`). */ disabled?: boolean; } /** * A computed output column (`context.eval[]`): source column/path `name`, output * alias `as`, and an optional `filters` pipeline. The `as` grafts onto the * returned row as an `unknown`-typed key. Also used for aggregate `group`/`eval`. */ interface DbEval { /** Source column or dotted path (e.g. `"book.name"`). */ name: string; /** Output alias — the row key this eval lands under. */ as: string; /** Optional filter pipeline applied to the value. */ filters?: DbEvalFilter[]; } /** * The keys a set of `eval` (or aggregate `group`/`eval`) columns graft onto a * row. Each entry's `as` alias becomes a key valued `unknown` — a filter * pipeline's output isn't statically knowable. An absent set contributes none. */ type EvalFields = E extends readonly [infer H, ...infer Rest] ? (H extends { as: infer S extends string; } ? { [K in S]: unknown; } : object) & EvalFields : object; /** * The row an aggregate query/addon yields — keyed by every `group` and `eval` * alias (values `unknown`). Reuses {@link EvalFields}; absent group/eval → no keys. */ type AggregateRow = AG extends { group?: infer G; eval?: infer EV; } ? Prettify & EvalFields> : Record; /** * An auth-table reference: the auth `table()` def (marked `table({ auth: true })`), * its bare name, a raw numeric `dbo.id` escape hatch, or `false`/`null`/omitted * for no auth. * * `false` and `null` are ONE state, not two: the engine gates on `!empty($auth)`, * so every falsy spelling means the endpoint is public. */ type AuthRef = false | null | TableDef | string | number; /** * Resolve an {@link AuthRef} to what the engine stores: `false` (no auth), a raw * numeric `dbo.id` (escape hatch), or the auth table's guid. `hostLabel`/`host` * name the referencing object for error messages (e.g. `"query"` / the query * name, or `"toolset tool"` / the tool ref). */ declare function resolveAuthRef(hostLabel: string, host: string, auth: AuthRef | undefined): false | number | string; /** * Toolset family. A `tool` is its own kind (`mvp_tool`, payload key `tool`) — * function-like (input/run/result) plus `instructions`/`middleware`. The two AI * primitives that persist as `obj_type=toolset` — **MCP servers** * (`mcp-server.ts`, `type:"mcp"`) and **agents** (`agent.ts`, `type:"agent"`) — * are their own root kinds; both build on the shared {@link encodeToolsetBase} * envelope exported here (name/description/instructions/docs/enabled/canonical/ * spec/tags/tool-refs). Verified against the Xano engine's stored mcp_server and * agent formats. * * Notes from that verification (see the PR for #85/#87): * - Xano's MCP server has **no** server-level `authentication` field — auth is * per-tool (`tool[].auth`, a stored `json`, engine default `false`). * - Toolset-level middleware is **not** an engine feature: neither transform * reads a `middleware` block, and the tiers that resolve a middleware chain * host only query/function/task/**tool**. The stored empty `middleware` * skeleton is an inert default, emitted here for shape parity but never * authorable. */ /** * Generic over its input map `I`, branded stack tuple `S`, literal response * `Resp`, and declared `Res` — the same carriers `QueryDef` holds, so * `InferInput`/`InferResponse` work identically here (issue #119). All default, * so a bare `ToolDef` is unchanged. * * A tool's response is the value an AGENT reads back, which makes it as * client-facing as an endpoint's. */ interface ToolDef = Record, Res = never, Resp extends ResponseDef = ResponseDef, S extends readonly Statement[] = readonly Statement[]> { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; instructions?: string; docs?: string; enabled?: boolean; toolsetId?: number; tags?: string[]; /** * Request-history capture. Omit to inherit (toolset → workspace). A scalar: * `false` off, `true` on at default depth, a number = capture depth, `"all"` * unlimited. Any value stops inheriting. See {@link HistoryInput}. */ history?: HistoryInput; input?: I; /** * The tool's statement stack. Captured as the literal tuple `S` (via `tool()`'s * `const` inference) so `InferResponse` can trace a response ref back to the * statement that bound it; a dynamically-built `Statement[]` widens it and the * trace degrades — declare `responseShape` there. */ stack?: S; /** What the tool returns to the caller. Captured as the literal `Resp` so * `InferResponse` can derive its keys and trace each member. */ response?: Resp; /** * Type-only: declare the tool's response shape so `InferResponse` * recovers it exactly, overriding automatic derivation. The runtime value is * ignored by `encodeTool`; only its type is read. */ responseShape?: Res; /** * Pre/post middleware attachment (per-tool — the `tool_pre`/`tool_post` * workspace keys). Providing a phase sets its `_customize` flag; an * un-customized phase inherits from the workspace. `pre: middleware.clear()` * overrides with nothing. */ middleware?: MiddlewareAttach; } interface ToolXdo { name: string; description: string; instructions: string; docs: string; enabled: boolean; output: unknown[]; middleware: MiddlewareBlock; tag: Array<{ tag: string; }>; history: { inherit: boolean; enabled: boolean; limit: number; }; toolset: { id: number; }; input: InputXdo[]; result: ResultItemXdo[]; run: StackItemXdo[]; test: unknown[]; } /** * Any tool def, whatever its inputs/stack/response — the parameter type every * consumer that only READS a def wants. `Res` is widened to `unknown` rather * than left at the `never` default, which would reject a def that declares * `responseShape`; the same widening `encodeQuery` uses. */ type AnyToolDef$1 = ToolDef, unknown>; declare function encodeTool(def: AnyToolDef$1): ToolXdo; declare const toolKind: ObjectKind; /** * Authoring factory for a `tool` — a function-like operation a toolset * references. The exact input map, stack tuple, and response are preserved on * the return type, so `InferInput`/`InferResponse` recover them (issue #119). */ declare function tool, Res = never, Resp extends ResponseDef = ResponseDef, const S extends readonly Statement[] = readonly []>(def: ToolDef): ToolDef; /** * A tool reference within a toolset. * * Prefer `tool` — a `tool()` def handle (or its name). It resolves to the * tool's guid at export, the same cross-object-reference mechanism the call * family uses (`s.tool.call`), so the toolset and the tool's payload `guid` * agree and a sync import remaps both together. `id` (a raw numeric engine id) * remains as an escape hatch for adopting an existing engine-side toolset. */ interface ToolsetToolRef { /** The tool to expose: a `tool()` def handle or its name (resolved to the tool's guid at export). */ tool?: ObjectRef; /** Raw numeric engine id — escape hatch; prefer `tool`. */ id?: number; enabled?: boolean; /** * Per-tool auth — Xano's **only** MCP auth surface (there is no server-level * gate). Works exactly like a query's `auth`: name an auth **table** (a * `table({ auth: true })` def or its name) and it resolves to that table's * guid at export (the engine's `dbo` id↔guid remap); a raw numeric `dbo.id` * is the escape hatch, and `false`/omitted means no auth (the engine default). */ auth?: AuthRef; } /** * One entry of a toolset's `tools`: a bare `tool()` handle (or its name) for the * common case, or a {@link ToolsetToolRef} wrapper when `enabled`/`auth` are * needed. * * The bare form exists because every other collection in the SDK takes handles * directly (`registerTools([saveNote])`, `bind: [{ table: users }]`), so * `tools: [saveNote]` is the spelling authors reach for by analogy — and the * wrapper's fields are all optional, so TypeScript's weak-type check let a bare * handle through and the export emitted `id: 0`, a null reference (issue #7). * Normalizing here removes the failure mode rather than documenting around it. */ type ToolsetToolEntry = ToolsetToolRef | ToolDef | string; /** Encoded tool reference: `id` carries the resolved guid; `auth` the resolved auth-table guid / dbo.id / `false`. */ interface ToolsetToolXdo { id: number | string; enabled: boolean; auth: false | number | string; } /** Resolve a list of {@link ToolsetToolEntry}s to their stored `tool[]` entries. */ declare function encodeToolRefs(tools?: ToolsetToolEntry[]): ToolsetToolXdo[]; /** * Fields shared by every toolset-family primitive (MCP server + agent). The * type-specific encoders add `type` and, for agents, `agent_settings`. * `instructions` is a stored column for both but only authorable on MCP servers * (Xano's `Agent` transform has no `instructions` field), so `AgentDef` simply * omits it and it stays `""`. */ interface ToolsetBaseDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; instructions?: string; docs?: string; enabled?: boolean; canonical?: string; spec?: string; tags?: string[]; /** * Toolset-level request-history default — the container tier its tools inherit * from (stored `tool_enabled`/`tool_limit`). Omit to inherit from the * workspace. A scalar: `false` off, `true` on at default depth, a number = * capture depth, `"all"` unlimited. See {@link HistoryInput}. */ history?: HistoryInput; /** * The tools this toolset exposes. Each entry is a bare `tool()` handle (or its * name), or a `{ tool, enabled?, auth? }` wrapper when a tool needs per-tool * auth or to be disabled. See {@link ToolsetToolEntry}. */ tools?: ToolsetToolEntry[]; } /** The shared toolset envelope — everything except the type discriminator and agent-only `agent_settings`. */ interface ToolsetBaseXdo { name: string; description: string; instructions: string; docs: string; enabled: boolean; canonical: string; spec: string; middleware: MiddlewareBlock; history: ContainerHistoryBlock<"tool">; tag: Array<{ tag: string; }>; tool: ToolsetToolXdo[]; } /** * Resolve a toolset's `canonical` URL token — the public token an MCP server's * endpoint URL (or an agent's addressable identity) is built from. Mirrors * `query`'s `resolveCanonical` exactly, in priority order: * 1. an explicit `{ canonical }` override; * 2. the def's non-empty in-code `canonical`; * 3. the canonical minted-and-frozen in `xano.lock` under `toolset:` * (toolsets carry a mintable canonical, like api groups — see * `CANONICAL_PAYLOAD_KEYS` in `lock/lock.ts`), read via the seeded override * store (populated by `seedLockOverrides`). * * We deliberately do NOT mint here: a canonical is unique per Xano *instance * across all workspaces*, so the only safe place to generate one is * `export --lock` (random, collision-checked, then frozen so every later export * and every client agrees). When nothing resolves we throw with the fix. */ declare function resolveToolsetCanonical(def: { name: string; canonical?: string; }, override?: string): string; /** * Build the shared toolset envelope. Assumes `def.name` is set — each kind's * encoder validates `name` first so it can throw a kind-specific message. * `middleware` is the inert empty skeleton (toolset-level middleware is not an * engine feature — see the module header); `spec` is a stored column the * XanoScript transform ignores, kept for DBO shape parity. */ /** * `label` names the kind the author actually wrote. Agents and MCP servers are * one stored object discriminated by `type`, so they share this encoder — but * "toolset" is not a word either author typed, and an error naming it points at * a factory that does not exist in their file. */ declare function encodeToolsetBase(def: ToolsetBaseDef, label?: string): ToolsetBaseXdo; /** The LLM provider — the `agent_settings.type` value and the `configs` key. */ type LlmProvider = "xano-free" | "openai" | "anthropic" | "google-genai"; /** * Fields common to every provider's LLM settings. String fields accept Twig * placeholders (`{{ $args.x }}` for run inputs, `{{ $env.X }}` for env vars) — * see the module header for the full templating contract. */ interface LlmCommon { /** The agent's system prompt (`agent_settings.system_prompt`). Templatable. */ systemPrompt?: string; /** Max reasoning/tool steps (`agent_settings.max_steps`). Defaults to 5. */ maxSteps?: number; /** * Forward-compat escape hatch: extra keys merged into `configs.` * last (for engine fields added after this typing). Prefer the typed fields. */ extraConfig?: Record; } /** * A numeric provider setting, as PERSISTED. * * These read as numbers and were declared `number`, but the editor's controls * are `json`-typed, so the corpus stores every one of them both ways — * `temperature` 12 times as an int and 18 as a string, `thinkingBudget` 12 and * 10, `thinking.budgetTokens` always as a string — with `""` the spelling an * untouched control leaves behind. * * The value round-trips verbatim either way (`normalize` already canonicalizes * the two numeric spellings to one), so all the narrower type bought was a * generated `temperature: ""` that would not compile. * * Widened rather than elided at `""`: dropping it would make the encoder write * the SDK default instead, and whether the engine reads a blank as that default * or as zero is a semantic claim nothing here can settle. Carrying the bytes * through unchanged needs no such claim. */ type LlmNumber = number | string; /** Anthropic provider settings → `configs.anthropic`. */ interface AnthropicProvider extends LlmCommon { type: "anthropic"; apiKey?: string; model?: string; temperature?: LlmNumber; /** Include reasoning in the response (stored `sendReasoning`). Defaults to true. */ sendReasoning?: boolean; /** Extended-thinking token budget — presence enables `thinking` (`thinking.budgetTokens`). */ thinkingTokens?: LlmNumber; baseURL?: string; headers?: string; } /** OpenAI provider settings → `configs.openai`. */ interface OpenAiProvider extends LlmCommon { type: "openai"; apiKey?: string; model?: string; temperature?: LlmNumber; /** `configs.openai.reasoningEffort` (e.g. "low" | "medium" | "high"). Defaults to "medium". */ reasoningEffort?: string; baseURL?: string; headers?: string; organization?: string; project?: string; /** `configs.openai.compatibility` (e.g. "strict"). Defaults to "strict". */ compatibility?: string; } /** Google GenAI provider settings → `configs.google-genai`. */ interface GoogleGenAiProvider extends LlmCommon { type: "google-genai"; apiKey?: string; model?: string; temperature?: LlmNumber; /** Stored `useSearchGrounding`. */ searchGrounding?: boolean; /** Stored `thinkingConfig.thinkingBudget`. */ thinkingBudget?: LlmNumber; /** Stored `thinkingConfig.includeThoughts`. */ includeThoughts?: boolean; baseURL?: string; headers?: string; safetySettings?: string; /** Stored `dynamicRetrievalConfig` (note: the engine's XanoScript field is misspelled `dynamic_retrival`). */ dynamicRetrieval?: string; } /** Xano Free provider settings → `configs.xano-free` (a Google-GenAI wrapper with no `apiKey`/`model`). */ interface XanoFreeProvider extends LlmCommon { type: "xano-free"; temperature?: LlmNumber; searchGrounding?: boolean; thinkingBudget?: LlmNumber; includeThoughts?: boolean; baseURL?: string; headers?: string; safetySettings?: string; dynamicRetrieval?: string; } /** * The run prompt, in exactly one of its two spellings (issue #6). * * The engine stores ONE prompt behind a `prompt_type` discriminator: either a * `prompt` string or a `prompt_messages` template, never both. Authoring both * used to compile, and `messages` silently won — the `prompt` was written as * `""` into the payload and the author kept believing their instruction was * live. That is the worst shape a settings object can have, because prompts are * routinely assembled from merged fragments where no human reads the result. * * A union rather than two optional keys, so the contradiction is a compile * error at the call site and not a runtime surprise. Neither key is also valid * — an agent whose run prompt comes entirely from `systemPrompt` plus its * inputs is a normal agent. */ type LlmPrompt = { /** A single prompt string (`prompt_type:"prompt"`). Templatable. Excludes `messages`. */ prompt?: string; messages?: never; } | { /** A messages template (`prompt_type:"messages"`). Templatable. Excludes `prompt`. */ messages?: string; prompt?: never; }; /** * Each provider's settings, carrying the prompt XOR. Intersected per provider * rather than only on the union so the exported per-provider types stay usable * on their own — `const llm: AnthropicLlm = { type: "anthropic", prompt: "…" }` * must still compile. */ type AnthropicLlm = AnthropicProvider & LlmPrompt; type OpenAiLlm = OpenAiProvider & LlmPrompt; type GoogleGenAiLlm = GoogleGenAiProvider & LlmPrompt; type XanoFreeLlm = XanoFreeProvider & LlmPrompt; /** Typed LLM settings, discriminated by provider `type`. */ type LlmSettings = AnthropicLlm | OpenAiLlm | GoogleGenAiLlm | XanoFreeLlm; /** * Structured-output authoring. `schema` is a record of named fields authored with * the `input.*` catalog — exactly like a `defineFunction`/`query` `input:` map. The * stored `structuredOutputsSchema` is the same wire shape as function inputs, so * `encodeInput` produces it verbatim (no parallel encoder). e.g. * `output: { schema: { priority: input.enum(["low","high"]), summary: input.text() } }`. * The item shape is byte-verified against a captured live-engine golden (issue #122). */ interface AgentOutput { schema: Record; /** Whether structured output is enabled (`structuredOutputs`). Defaults to true. */ enabled?: boolean; } /** * The `.result` completion type for a run of agent `A` — derived from its * declared `output.schema` when structured outputs are on, else `string`. * * `A` is whatever `s.ai.agent.run({ agent })` was handed: an {@link AgentHandle} * (or {@link AgentDef}) carries a precise, branded `output.schema`, so this reads * the shape the agent already declares once — no second `resultShape` witness at * the call site (issue #124.1). A bare name/ref carries no schema → `string`. * * The schema is a *response* shape (the object the model returns), so every * declared field is treated as present — {@link RowFromFieldMap}, not the * request-payload `FromFieldMap` — with `nullable`/`array` still applied. An * explicit `enabled: false` disables structured outputs, so the result is * `string` again. */ type AgentResultOf = A extends { output?: infer O; } ? O extends { schema: infer S; enabled?: infer E; } ? [E] extends [false] ? string : { -readonly [K in keyof RowFromFieldMap]: RowFromFieldMap[K]; } : string : string; /** * Agent authoring def. Note: no `instructions`/`spec` — Xano's `Agent` * transform has neither. */ interface AgentDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; docs?: string; enabled?: boolean; canonical?: string; tags?: string[]; /** * Request-history default for this agent's tools (the container tier — stored * `tool_enabled`/`tool_limit`). Omit to inherit from the workspace. A scalar: * `false` off, `true` on at default depth, a number = capture depth, `"all"` * unlimited. See {@link HistoryInput}. */ history?: HistoryInput; /** * The tools this agent can call. Each entry is a bare `tool()` handle (or its * name), or a `{ tool, enabled?, auth? }` wrapper. See {@link ToolsetToolEntry}. */ tools?: ToolsetToolEntry[]; /** The typed LLM settings (provider + model + generation config). */ llm: LlmSettings; /** Optional structured output schema. */ output?: AgentOutput; } /** The stored `agent_settings` block — the real engine shape (see module header). */ interface AgentSettingsXdo { type: LlmProvider; system_prompt: string; max_steps: number; prompt_type: "prompt" | "messages"; prompt: string; prompt_messages: string; structuredOutputs: boolean; structuredOutputsSchema: InputXdo[]; configs: Record>; } interface AgentXdo extends ToolsetBaseXdo { type: "agent"; agent_settings: AgentSettingsXdo; } declare function encodeAgent(def: AgentDef): AgentXdo; declare const agentKind: ObjectKind; /** * An `agent()` handle: the def plus a `getCanonical()` accessor. Unlike * {@link McpServerHandle} there is **no** `getUrl()`/`getPath()` — an agent has * no public HTTP endpoint (it is invoked in-stack via `s.ai.agent.run`, never * addressed by an external client). `getCanonical()` gives a pinned `canonical` * a client-side payoff without fabricating a URL. The accessor is dropped by * `JSON.stringify` and ignored by `encodeAgent`, so serialization is unaffected. */ type AgentHandle = D & { /** * The agent's resolved `canonical` token — from the def's `canonical` (or * `opts.canonical`, or the value frozen in `xano.lock` under `toolset:`); * throws if none resolves. */ getCanonical(opts?: { canonical?: string; }): string; }; /** * Author an AI agent — an LLM orchestrator over a set of tools. Returns an * {@link AgentHandle} (the def plus `getCanonical()`). * * Generic over the concrete def `D` so the handle preserves the exact, * branded `output.schema`. That lets `s.ai.agent.run({ agent })` read the * completion shape straight off the handle via {@link AgentResultOf} — the * structured-output type is declared once here, not re-stated as a `resultShape` * witness at every call site (issue #124.1). */ declare function agent(def: D): AgentHandle; /** * Addon kind (U8) → payload key `addon`. An addon is a single table-bound db * query (an `input` block + an `output` selection + a `context` that carries the * dbo binding and `return`), *not* a statement stack — the engine runs it * straight off `context`. The MVP models the common shape; * rich db-bound contexts pass through verbatim. Validated against the Xano * engine's persisted addon shape (whose xdo schema has no `run`/stack). * * `addon()` optionally accepts a typed `table` handle and an `output` column * list; when given, it auto-fills the `context.dbo` binding (the guid the engine * matches on) and brands the returned handle with the addon's **graft shape** — * `Pick, output>`, wrapped per {@link AddonDef.cardinality} — so * a `db.query`/`db.get` attaching the addon can type the grafted row field * instead of falling back to `unknown` (issues #62, #63). */ /** An addon's `output` selection: a typed column-name list, or the raw customize block. */ type AddonOutput = readonly string[] | { customize?: boolean; items?: unknown[]; }; /** * An addon's result cardinality — the query `return.type` the engine reads to * shape the graft: * * - `"single"` — one object (Xano's Single toggle; `listable:false`). * - `"list"` — an array (the default; absent `return` coerces to `list`). * - `"count"` — an `int` count. * - `"exists"` — a `bool`. * - `"aggregate"` — a grouped aggregation; its shape depends on the `group`/`eval` * config (supplied via a raw `context.return`), so the graft stays `unknown`. */ type AddonCardinality = "single" | "list" | "count" | "exists" | "aggregate"; /** * The graft shape an attached addon lands on each row. For a table-bound addon * with an `output` list it's `Pick, Out>`, wrapped per the * cardinality: an object (`"single"`) or an array (`"list"`). `"count"` grafts a * `number` and `"exists"` a `boolean` (the `output`/`table` are irrelevant to * those); `"aggregate"` grafts an array keyed by the declared `group`/`eval` * aliases (values `unknown`), or `unknown` when neither is declared. Falls back to * `unknown` when the addon carries no typed `table` + `output` (a bare-name/raw-context addon the SDK can't shape). * Mirrors the engine's per-return-type graft. */ type AddonGraft = Card extends "count" ? number : Card extends "exists" ? boolean : Card extends "aggregate" ? [Grp, Ev] extends [readonly [], readonly []] ? unknown : Prettify>[] : [Out] extends [readonly []] ? unknown : InferRow extends infer Row ? [Row] extends [never] ? unknown : Row extends object ? Card extends "single" ? Prettify>> : Prettify>>[] : unknown : unknown; /** * An addon definition. An addon is a single table-bound db query, not a * statement stack — the engine executes it straight off its `context` (dbo * binding + `return` + search/sort/eval), which is exactly what `table`, * `cardinality`, and a raw `context` build here (the engine reads * `context`; the fetched `run: [mvp:dbo_view]` is a server-derived artifact and * is not part of the stored addon schema). Set `table` + * `output` to get a typed graft on attach; `cardinality:"single"` grafts a * single object (Xano's Single toggle) instead of the default array. * * @typeParam Graft - phantom carrier for the addon's graft shape, captured by * {@link addon} from `table`/`output`/`cardinality`. Read by a db op's * response typing. Defaults to `unknown`; never assigned at runtime. */ interface AddonDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; tags?: string[]; input?: Record; /** * Bind the addon to a table — auto-fills `context.dbo` with the table's guid. * * ⚠ **Do not author `null`.** It is a BROKEN state in Xano, not a neutral one: * the addon stores the engine's empty `{as:"", id:""}` binding, is bound to no * table, and returns nothing wherever it is attached. It exists on this type so * `codegen` can represent a broken object faithfully rather than leak a raw * `context.dbo` blob — a pulled `table: null` is a defect to fix in the pulled * workspace, not a shape to copy. * * It is what an addon degrades to when the table it referenced is deleted, and * also where a freshly-created one starts. The engine clears the id rather than * recording a tombstone, so those two are the same bytes: `null` means * "unbound", never "was deleted". * * Omitting `table` entirely is a third, distinct state: no `dbo` is written. */ table?: ObjectRef | null; /** * SQL alias for the bound table (`context.dbo.as`), used to qualify columns in * `where`/`sort` (e.g. `col("merchant.id")`). Absent unless set. * * Same surface, and same reasoning, as a db statement's `tableAlias`: `as` is a * SQL alias appended to the table's own derived name (`FROM users as u`), never * a replacement for it, and it is per-object data rather than a function of the * table — so it is authored, not derived. Xano's editor writes it on every addon * it creates, usually restating the table name (a no-op the engine drops), but * measurably not always: it sanitizes non-identifier characters (a `quick-update` * table aliased `quick_update`) and it keeps a stale alias after the table is * renamed. Deriving it would corrupt both. */ tableAlias?: string; /** The addon's filter — the predicate binding it to the parent row, e.g. `expr(col("id"), "=", inp("user_id"))`. Same `where` surface as `s.db.query`; encodes `context.search`. */ where?: DbWhere; /** Sort the returned rows (`[{ sortBy, dir }]`). Same surface as `s.db.query`; encodes `context.sort`. */ sort?: SortDirective[]; /** Optional binding context (e.g. `{ dbo: { id, as }, bind: […] }`); passed through. An explicit `dbo`/`search`/`sort`/`return` here wins over the `table`/`where`/`sort`/`cardinality` auto-fill. */ context?: Record; /** Output selection — a column-name list (typed, drives the graft shape) or the raw `{ customize, items }` block. */ output?: AddonOutput; /** Result cardinality (query `return.type`): `"single"` → object, `"list"` (default) → array, `"count"` → number, `"exists"` → boolean, `"aggregate"` → grouped rows. Encodes `context.return.type` (omitted for the `"list"` default). */ cardinality?: AddonCardinality; /** Aggregate group-by columns (with `cardinality:"aggregate"`) → `context.return.aggregate.group`. Each `as` grafts onto the aggregate row. */ group?: DbEval[]; /** Aggregate/eval columns (with `cardinality:"aggregate"`) → `context.return.aggregate.eval`. Each `as` grafts onto the aggregate row. */ eval?: DbEval[]; /** @internal phantom carrier for {@link Graft}; never assigned at runtime. */ readonly __graft?: Graft; } interface AddonXdo { name: string; description: string; context: Record; output: { customize: boolean; items: unknown[]; }; tag: Array<{ tag: string; }>; input: InputXdo[]; } declare function encodeAddon(def: AddonDef): AddonXdo; declare const addonKind: ObjectKind; /** The authoring args for {@link addon}, generic over the table/output/cardinality/group/eval that drive the graft shape. */ interface AddonArgs extends Omit { table?: Tbl; output?: Out | { customize?: boolean; items?: unknown[]; }; cardinality?: Card; group?: Grp; eval?: Ev; } /** * Author an addon. Pass a typed `table` handle + `output` column list to get a * typed graft when the addon is attached to a `db.query`/`db.get`, and a * `cardinality` to shape it: `"single"` (object), `"list"` (array, default), * `"count"` (number), `"exists"` (boolean), or `"aggregate"` (grouped rows). For * `"aggregate"`, pass `group`/`eval` (`{ name, as, filters? }`) and the graft is * typed from their aliases. The returned handle is registered with * `.registerAddons([...])` and attached via * `db.query({ addon: [{ addon: handle, as, input }] })`. */ declare function addon(def: AddonArgs): AddonDef>; /** * Schema-DSL interpreter (U9). Turns a declarative statement schema's * `transform` rules into a runtime encoder, so the ~169 declarative statements * are driven by data (a `StatementSpec`) rather than hand-written per statement. * * A `StatementSpec` is a flat list of `FieldRule`s — one per authored field the * statement consumes — mirroring the engine's `transform.args` / `transform.blocks` * entries. Each rule says where one authored field lands in the stored statement: * - `route: { kind: "as" }` → the statement's top-level `as`. * - `route: { kind: "context-plain" }` → a plain string written at `context.`. * - `route: { kind: "context-spread" }` → a Value's `{value,tag,filters}` spread * directly into `context` (the `!assign context` rule). * - `route: { kind: "context-nest" }` → a Value's `{value,tag,filters}` nested * under `context.` (the `!assign context.` rule). * * Rules carry optionality + an optional string `default` (the schema's `?=X`). * `output` (whether the stored item carries `output:{filters:[]}`) is NOT * derivable from the transform schema — it is engine statement-class metadata * (e.g. `uuid4` has an `as` but no `output`; `return` has a value block but no * `output`). The codegen pins it from the persisted golden fixture (KTD-4), * never guessing. * * Validated against real persisted fixtures (math_add, bitwise_and, object_keys, * array_push, array_pop). The codegen pipeline (scripts/codegen.ts) populates the * spec catalog from the Xano engine's schema definitions; uninterpretable schemas are * logged, never guessed. */ /** * What kind of authored value a field consumes: * - `string` — a plain `static:text` arg. * - `boolean` — a plain `static:bool` arg, authored as a real boolean. * - `value` — a `!kinds assign` tagged Value. * - `comparison` — a `Condition` expression tree (the `!compare` directive). */ type FieldType = "string" | "boolean" | "value" | "comparison"; /** Where one authored field lands in the stored statement. */ type Route = { kind: "as"; } | { kind: "context-plain"; path: string; } | { kind: "context-spread"; } | { kind: "context-nest"; path: string; } | { kind: "context-compare"; path: string; } | { kind: "input"; name: string; }; /** * Per-statement envelope shape, pinned from the persisted fixture (KTD-4) — the * engine-class metadata that isn't in the transform schema. "Full" statements * (api_request, db ops, file ops) carry richer `input[]` entries and extra * top-level keys; lean statements (math, array, object) carry none of these. */ interface EnvelopeProfile { /** `input[]` entries carry `ignore/expand/children` (vs the lean `{name,value,tag,filters}`). */ inputFull?: boolean; /** Always emit `as` (default `""`) even when no rule sets it. */ emitAs?: boolean; /** Emit `description:""`. */ description?: boolean; /** Emit `settings_registry:[]`. */ settingsRegistry?: boolean; /** Emit `addon:[]`. */ addon?: boolean; /** `output` is the rich `{customize:false,filters:[],items:[]}` (vs lean `{filters:[]}`). */ richOutput?: boolean; } /** One authored field → its routing into the stored statement. */ interface FieldRule { /** Authored field name (e.g. "as", "name", "value", "filename"). */ field: string; /** String arg vs Value(assign) — determines how `default`/missing is handled. */ type: FieldType; /** Optional in authoring (the schema's trailing `?`). */ optional: boolean; /** Literal default for string fields when not provided (the schema's `?=X`). */ default?: string; /** * The field's closed set of legal values, harvested from the engine's runtime * input schema ({@link ./input-schema.ts}) and attached by * {@link ./enums.ts attachEnums}. Absent on all but the ~36 constrained * fields. Drives three things: the generated factory's literal-union * signature, the bare-literal shorthand, and the encode-time guard in * {@link encodeFromSpec}. */ enum?: string[]; /** Routing target. */ route: Route; } interface StatementSpec { /** Stored statement name, e.g. "mvp:math_add". */ name: string; /** Marks the engine's argNameIsVar family (informational; does not affect encoding). */ argNameIsVar?: boolean; /** Ordered field rules. */ rules: FieldRule[]; /** Emit `output` when true (pinned from the golden fixture; shape per `envelope.richOutput`). */ output?: boolean; /** Per-statement envelope shape pinned from the fixture; absent = lean. */ envelope?: EnvelopeProfile; } /** * Authored `output` envelope shaping (the frontend's "Output" tab). Any of the * three stored members may be set; omitted members keep their empty default. * `filters` attaches a filter chain to the result variable; `customize`/`items` * drive response field-mapping. Merged over the spec's default `output` shape. * * Prefer `asFilters` for the filter chain — it is typed, reaches every statement * rather than the subset declaring an `output`, and guards the no-binding case. * `filters` here remains the escape hatch for a chain the typed surface cannot * express; setting both throws. */ interface OutputAuthored { filters?: unknown[]; customize?: boolean; items?: unknown[]; } /** * Authored inputs for a spec-driven statement, keyed by field name. Besides the * spec's rule fields, five reserved envelope keys are honored: `disabled`, * `description`, `mock`, and `asFilters` ({@link StatementAnnotations} — * accepted on every statement) and `output` (an {@link OutputAuthored} shaping * the result envelope, only where the statement carries one). No engine * statement routes a rule field by any of those five names, so they are * unambiguous. */ type Authored = Record; /** Encode authored inputs into a `Statement` using a spec's field rules. */ declare function encodeFromSpec(spec: StatementSpec, authored: Authored): Statement; /** Register a spec on the statement registry; returns its factory. */ declare function registerSpec(spec: StatementSpec): (authored: Authored) => Statement; declare const mathAdd: (varName: string, value: Value) => Statement; declare const mathSub: (varName: string, value: Value) => Statement; declare const mathMul: (varName: string, value: Value) => Statement; declare const mathDiv: (varName: string, value: Value) => Statement; declare const bitwiseAnd: (varName: string, value: Value) => Statement; declare const bitwiseOr: (varName: string, value: Value) => Statement; declare const bitwiseXor: (varName: string, value: Value) => Statement; declare const textAppend: (varName: string, value: Value) => Statement; declare const textPrepend: (varName: string, value: Value) => Statement; declare const objectKeys: (as: string, value: Value) => Statement; declare const objectValues: (as: string, value: Value) => Statement; declare const objectEntries: (as: string, value: Value) => Statement; /** Names of all statements produced by the generated catalog. */ declare const GENERATED_STATEMENT_NAMES: string[]; /** * Microservice (`microservice`) — a container workload deployed alongside the * workspace, addressed from a stack by `s.microservice.request`. * * Two mutually exclusive shapes, selected by {@link MicroserviceDef.kind}: * * - **`builtin`** — declarative: `configs`, `volumes`, a `deployment` of one or * more containers, and `ingresses`. This is the default. * - **`helm`** — bring-your-own chart: a `chart` reference and its values. A * helm microservice carries no deployment blocks, and a builtin one carries * no chart; the engine serializes them as mutually exclusive groups. * * **EARLY, AND EXPECTED TO CHANGE.** This models a young platform surface, and * every `export()` of a workspace declaring a microservice says so — the docs * are read before writing, which is not where an author is when it matters. * * Two of the blocks the engine declares (`configs` and `volumes`) cannot be * authored at all: an import carrying either is refused, and the deploy fatals * after provisioning has begun. They stay typed and round-trip so a pulled * workspace holding one still decodes, but they are `@deprecated` and * `export()` REFUSES a populated one — a type that compiles and then fatals at * deploy is a recommendation this SDK will not make. What deploys instead is * container-level: `env` for a value the workload reads, * {@link MicroserviceContainer.volumes} for storage. * * ### Two fields carry secrets, and codegen carries them * * `registryAuth.dockerconfigjson` is a docker registry credential, and * `chart.values` is Helm values documented upstream as possibly holding * secrets. A live capture confirmed both ride the workspace export, so * `xanots codegen` writes both into the generated tree VERBATIM. * * That is deliberate: dropping them would mean a pulled microservice could not * be redeployed, which is worse than the alternative for the thing this surface * exists to do. But it means **a bundle or generated tree holding a * private-registry microservice contains a live credential.** * * ### What "out of band" can and cannot mean here * * Both fields are stored strings the engine keeps exactly as given. There is no * deploy-time indirection for either — no `env()` form, no template the tenant * resolves — so the tempting spelling is the wrong one: * * ```ts * // WRONG. `process.env` resolves at EXPORT time: the literal credential is * // written into the bundle, and into git with it. * microservice({ name: "app", registryAuth: { dockerconfigjson: process.env.REGISTRY_JSON! } }); * ``` * * So there are two honest options, and both are about where the bytes live * rather than about hiding them: * * 1. **Leave `registryAuth` unset** and give the workload a public image, or * attach the pull credential to the microservice outside this workspace * entirely. Nothing then carries a credential. * 2. **Accept that the tree is secret-bearing.** Keep the compiled bundle and * any pulled tree out of git, or rotate the credential once it lands there. * * The mapped secret surface, when what you need is a value your STACK reads, is * `workspaceConfig({ env })` — deploy sets those on the tenant and `env("NAME")` * reads one back without the value appearing in a stack literal. It does not * feed registry auth; it is the pattern to reach for everywhere else. * * Export reports every non-empty one it writes, and so does the decoder, so it * can never happen quietly. */ /** A container port mapping. Both sides are TEXT, as the engine stores them. */ interface ContainerPort { /** The port the service exposes. */ servicePort: string; /** The port inside the container. Defaults to `servicePort` when omitted. */ containerPort?: string; } /** A container's CPU/RAM request, in Kubernetes units (`"50m"`, `"256Mi"`). */ interface ContainerResources { cpu?: string; ram?: string; } /** One `name=value` pair in a container's environment. */ interface ContainerEnv { name: string; value?: string; } /** A volume mounted into a container. */ interface ContainerVolume { name: string; type?: string; persistent?: Record; emptyDir?: Record; config?: Record; } /** * One container in a {@link MicroserviceDeployment}. * * The container `name` is FREE-FORM: it does not have to match the * microservice's own name, and nothing about addressing depends on it. What a * stack reaches with `s.microservice.request` is the MICROSERVICE name (plus a * `servicePort`), whichever containers happen to sit behind it — so a * multi-container workload names each one for what it is. The name a * microservice's `ingresses[].paths[].service` refers to is likewise the * microservice, not the container. */ interface MicroserviceContainer { /** Free-form — see the note above; it need not match the microservice name. */ name: string; image?: string; /** Names a `registryAuth` pull secret when the image is private. */ pullSecret?: string; type?: string; /** Entrypoint, one element per argv token. */ command?: string[]; /** Arguments to the entrypoint, one element per argv token. */ args?: string[]; env?: ContainerEnv[]; ports?: ContainerPort[]; resources?: ContainerResources; volumes?: ContainerVolume[]; } /** The `builtin` workload: how many replicas of which containers. */ interface MicroserviceDeployment { /** Defaults to 1. */ replicas?: number; /** Defaults to `"Recreate"`. */ strategy?: string; docker?: string; containers?: MicroserviceContainer[]; } /** One route into the microservice. */ interface MicroserviceIngress { name: string; domain?: string; /** Path → container-service mappings. */ paths?: Array<{ service?: string; path?: string; }>; } /** A named config value attached to the microservice. */ interface MicroserviceConfig { name: string; type?: string; value?: string; } /** A persistent volume claim owned by the microservice. */ interface MicroserviceVolume { name: string; size?: string; class?: string; } /** A bring-your-own Helm chart (`kind: "helm"`). */ interface MicroserviceChart { /** e.g. `oci://registry/repo/chart`, a `.tgz` URL, or `repo/chart`. */ ref?: string; version?: string; /** * Chart values, as YAML. Stored and carried VERBATIM — the engine resolves * nothing here, so a secret written into these values is a secret in the * bundle. See the note on {@link MicroserviceDef}. */ values?: string; } /** Private-registry pull credentials. See the note on {@link MicroserviceDef}. */ interface MicroserviceRegistryAuth { /** Registry host, e.g. `index.docker.io`. */ server?: string; /** The credential flow that assembled the pull secret. */ type?: "userpass" | "gcp_sa" | "aws_ecr" | ""; /** * The assembled docker credential. Stored and carried VERBATIM, and reported * at export when non-empty. There is no deploy-time indirection: a * `process.env` read here resolves at export and bakes the literal into the * bundle. See the out-of-band note on {@link MicroserviceDef}. */ dockerconfigjson?: string; } interface MicroserviceDef { name: string; /** * Pin identity explicitly. Omitted, the guid is derived from the name — set it * to adopt an object that already exists in a workspace, or to survive a * rename. `xanots codegen` always emits the engine's own guid, because * re-deriving one would be a silent identity rewrite. */ guid?: string; description?: string; /** * `builtin` (declarative containers) or `helm` (bring-your-own chart). * Defaults to `builtin`. */ kind?: "builtin" | "helm"; /** * Whether tenant releases deploy this automatically. `manual` still ships with * the release but is not auto-deployed there. Defaults to `auto`. */ tenantDeploy?: "auto" | "manual"; /** `builtin` only. */ deployment?: MicroserviceDeployment; /** `builtin` only. */ ingresses?: MicroserviceIngress[]; /** * @deprecated NOT DEPLOYABLE. The engine refuses an import carrying this, so * `export()` refuses it too rather than letting a build fatal mid-deploy. * Put a value the workload reads in a container's `env` instead * (`deployment.containers[].env`). Typed and carried only so a pulled * workspace holding one still decodes. */ configs?: MicroserviceConfig[]; /** * @deprecated NOT DEPLOYABLE. The engine refuses an import carrying this, so * `export()` refuses it too rather than letting a build fatal mid-deploy. * Declare storage on the container itself ({@link MicroserviceContainer.volumes} * — `emptyDir`, `persistent`, or `config`). Typed and carried only so a pulled * workspace holding one still decodes. */ volumes?: MicroserviceVolume[]; /** `helm` only. */ chart?: MicroserviceChart; /** * Private-registry pull credentials, carried into the bundle VERBATIM. Leave * unset unless the bundle is allowed to hold a live credential — see the * out-of-band note on {@link MicroserviceDef}. */ registryAuth?: MicroserviceRegistryAuth; } /** * Every `servicePort` this microservice's containers declare, de-duplicated and * in declaration order. * * This is the same list the Xano dashboard flattens to build the host dropdown * on a microservice-request statement (one entry per container port), so it is * exactly the set of ports `s.microservice.request` can legitimately address. * Returns `[]` for a `helm` microservice and for a builtin whose containers * expose nothing — neither declares ports, so neither constrains the caller. */ declare function declaredServicePorts(def: MicroserviceDef): string[]; /** The persisted envelope, exactly as the engine stores it. */ interface MicroserviceXdo { name: string; description: string; kind: string; tenant_deploy: string; configs: unknown[]; volumes: unknown[]; ingresses: unknown[]; deployment: Record; chart: Record; registry_auth: Record; } declare function encodeMicroservice(def: MicroserviceDef): MicroserviceXdo; /** * Author a microservice. See the module docstring for the two shapes. * * The `const` generic preserves the literal `servicePort` strings so * `s.microservice.request` can type-check a `port` against the ports this * microservice actually exposes. `D extends MicroserviceDef` keeps the result * assignable anywhere a `MicroserviceDef` is expected. Same shape `agent()` * already uses. * * One consequence: the returned def is READ-ONLY to the type checker, so * mutating it after authoring is now an error. That is the right way round — * validation runs here, once, and a post-hoc mutation would slip past it. */ declare function microservice(def: D): D; declare const microserviceKind: ObjectKind; /** * AUTO-GENERATED by scripts/codegen.ts — DO NOT EDIT BY HAND. * * Typed, namespaced factories for the declarative statement catalog (U9): every * generated statement is reachable + autocomplete-discoverable as * `generated..({…})` (e.g. `generated.math.add`, * `generated.db.get`). The unified public surface is `s` in ../s.ts, which * merges these with the hand-authored control-flow specials. Regenerate with * `npm run codegen`. */ declare const generated: { ai: { external: { mcp: { server_details: (a?: { as?: string; url?: Value; bearer_token?: Value; connection_type?: "sse" | "stream" | Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; tool: { list: (a?: { as?: string; url?: Value; bearer_token?: Value; connection_type?: "sse" | "stream" | Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; run: (a?: { as?: string; url?: Value; bearer_token?: Value; connection_type?: "sse" | "stream" | Value; tool?: Value; args?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; }; }; }; api: { request: (a?: { as?: string; url?: Value; method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" | Value; params?: Value; headers?: Value; timeout?: Value; follow_location?: Value; verify_host?: Value; verify_peer?: Value; ca_certificate?: Value; certificate?: Value; certificate_pass?: Value; private_key?: Value; private_key_pass?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; stream: (a: { value: Value; disabled?: boolean; description?: string; }) => Statement; }; array: { difference: (a?: { as?: string; expr?: Value; value?: Value; by?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; every: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; filter: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; filter_count: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; find: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; find_index: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; group_by: (a?: { as?: string; expr?: Value; by?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; has: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; intersection: (a?: { as?: string; expr?: Value; value?: Value; by?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; merge: (a?: { name?: string; value?: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; partition: (a?: { expr?: Value; as?: string; if?: Condition; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; pop: (a?: { name?: string; as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; push: (a: { name?: string; value: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; shift: (a?: { name?: string; as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; unshift: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; }; await: (a?: { as?: string; ids?: Value; timeout?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; cloud: { algolia: { request: (a: { as?: string; application_id: Value; api_key: Value; url: Value; method?: "POST" | "GET" | "DELETE" | "PUT" | Value; payload: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; }; aws: { opensearch: { document: (a: { as?: string; auth_type?: "IAM" | "master" | Value; key_id?: Value; access_key?: Value; region?: Value; base_url: Value; method?: "GET" | "POST" | "PUT" | "DELETE" | Value; index?: Value; doc_id?: Value; doc?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; query: (a?: { as?: string; auth_type?: "IAM" | "master" | Value; key_id?: Value; access_key?: Value; region?: Value; base_url?: Value; index?: Value; payload?: Value; size?: Value; from?: Value; included_fields?: Value; return_type?: "search" | "count" | Value; expression?: Value; sort?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; request: (a?: { as?: string; auth_type?: "IAM" | "master" | Value; key_id?: Value; access_key?: Value; region?: Value; method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" | Value; url?: Value; query?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; s3: { delete_file: (a: { as?: string; bucket: Value; region: Value; key: Value; secret: Value; file_key: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; get_file_info: (a: { as?: string; bucket: Value; region: Value; key: Value; secret: Value; file_key: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; list_directory: (a: { as?: string; bucket: Value; region: Value; key: Value; secret: Value; prefix?: Value; next_page_token?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; read_file: (a: { as?: string; bucket: Value; region: Value; key: Value; secret: Value; file_key: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; sign_url: (a: { as?: string; bucket: Value; region: Value; key: Value; secret: Value; file_key: Value; ttl?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; upload_file: (a: { as?: string; bucket: Value; region: Value; key: Value; secret: Value; file_key?: Value; file: Value; metadata?: Value; object_lock_mode?: "compliance" | "governance" | Value; object_lock_retain_until?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; }; azure: { storage: { delete_file: (a: { as?: string; account_name: Value; account_key: Value; container_name: Value; filePath: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; get_file_info: (a: { as?: string; account_name: Value; account_key: Value; container_name: Value; filePath: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; list_directory: (a: { as?: string; account_name: Value; account_key: Value; container_name: Value; path?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; read_file: (a: { as?: string; account_name: Value; account_key: Value; container_name: Value; filePath: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; sign_url: (a: { as?: string; account_name: Value; account_key: Value; container_name: Value; path: Value; ttl?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; upload_file: (a: { as?: string; account_name: Value; account_key: Value; container_name: Value; filePath: Value; file: Value; metadata?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; }; elasticsearch: { document: (a: { as?: string; auth_type?: "Basic" | "Bearer" | "API Key" | Value; key_id: Value; access_key: Value; base_url: Value; index: Value; method?: "GET" | "POST" | "PUT" | "DELETE" | Value; doc_id: Value; doc: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; query: (a?: { as?: string; auth_type?: "Basic" | "Bearer" | "API Key" | Value; key_id?: Value; access_key?: Value; base_url?: Value; index?: Value; payload?: Value; size?: Value; from?: Value; included_fields?: Value; return_type?: "search" | "count" | Value; expression?: Value; sort?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; request: (a?: { as?: string; auth_type?: "Basic" | "Bearer" | "API Key" | Value; key_id?: Value; access_key?: Value; method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | Value; url?: Value; payload?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; google: { storage: { delete_file: (a: { as?: string; service_account: Value; bucket: Value; filePath: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; get_file_info: (a: { as?: string; service_account: Value; bucket: Value; filePath: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; list_directory: (a: { as?: string; service_account: Value; bucket: Value; path: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; read_file: (a: { as?: string; service_account: Value; bucket: Value; filePath: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; sign_url: (a: { as?: string; service_account: Value; bucket: Value; filePath: Value; method?: "GET" | "POST" | Value; ttl?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; upload_file: (a: { as?: string; service_account: Value; bucket: Value; filePath: Value; file: Value; metadata?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; }; }; datadog: { log: (a?: { message?: Value; status?: "debug" | "info" | "notice" | "warn" | "error" | "critical" | "alert" | "emergency" | Value; attributes?: Value; service?: Value; source?: Value; env?: Value; hostname?: Value; tags?: Value; timestamp?: Value; connection?: Value; disabled?: boolean; description?: string; }) => Statement; log_bulk: (a: { entries: Value; connection?: Value; disabled?: boolean; description?: string; }) => Statement; metric: (a: { metric?: Value; value: Value; type?: "count" | "gauge" | "rate" | "histogram" | "distribution" | Value; tags?: Value; service?: Value; source?: Value; env?: Value; hostname?: Value; timestamp?: Value; connection?: Value; disabled?: boolean; description?: string; }) => Statement; metric_bulk: (a: { entries: Value; connection?: Value; disabled?: boolean; description?: string; }) => Statement; }; db: { set_datasource: (a: { value: Value; workspace_id?: Value; disabled?: boolean; description?: string; }) => Statement; }; debug: { log: (a: { value: Value; disabled?: boolean; description?: string; }) => Statement; stop: (a: { value: Value; disabled?: boolean; description?: string; }) => Statement; }; expect: { to_be_defined: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_empty: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_false: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_greater_than: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_in_the_future: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_in_the_past: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_less_than: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_null: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_true: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_be_within: (a?: { expr?: Value; min?: Value; max?: Value; disabled?: boolean; description?: string; }) => Statement; to_contain: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_end_with: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_equal: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_match: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_not_be_defined: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_not_be_null: (a?: { expr?: Value; disabled?: boolean; description?: string; }) => Statement; to_not_equal: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; to_start_with: (a?: { expr?: Value; value?: Value; disabled?: boolean; description?: string; }) => Statement; }; lambda: (a?: { as?: string; code?: Value | LambdaBody<"s.lambda">; timeout?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; math: { add: (a: { name?: string; value: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; bitwise: { and: (a: { name?: string; value: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; or: (a: { name?: string; value: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; xor: (a: { name?: string; value: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; }; div: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; mod: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; mul: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; sub: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; }; microservice: { request: (a: { as?: string; host: Value; path: Value; method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" | Value; params?: Value; headers: Value; timeout?: Value; follow_location?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; object: { entries: (a?: { as?: string; value?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; keys: (a?: { as?: string; value?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; values: (a?: { as?: string; value?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; }; precondition: (a?: { expr?: Condition; error_type?: string; error?: Value; payload?: Value; disabled?: boolean; description?: string; }) => Statement; realtime: { get_session: (a?: { as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; redis: { count: (a: { as?: string; key: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; decr: (a: { as?: string; key: Value; by?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; del: (a: { key: Value; disabled?: boolean; description?: string; }) => Statement; get: (a: { as?: string; key: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; has: (a: { as?: string; key: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; incr: (a: { as?: string; key: Value; by?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; keys: (a: { as?: string; search: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; pop: (a: { as?: string; key: Value; count?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; push: (a: { as?: string; key: Value; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; range: (a: { as?: string; key: Value; start?: Value; stop?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; ratelimit: (a: { as?: string; key: Value; max?: Value; ttl?: Value; error?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; remove: (a: { as?: string; key: Value; value: Value; count?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; set: (a: { as?: string; key: Value; data: Value; ttl?: Value; create_only?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; shift: (a: { as?: string; key: Value; count?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; unshift: (a: { as?: string; key: Value; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; security: { check_password: (a?: { as?: string; text_password?: Value; hash_password?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; create_curve_key: (a?: { as?: string; curve?: "P-256" | "P-384" | "P-521" | Value; format?: "object" | "base64" | Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; create_password: (a?: { as?: string; character_count?: Value; require_lowercase?: Value; require_uppercase?: Value; require_digit?: Value; require_symbol?: Value; symbol_whitelist?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; create_rsa_key: (a?: { as?: string; bits?: Value; format?: "object" | "base64" | Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; create_secret_key: (a?: { as?: string; bits?: Value; format?: "object" | "base64" | Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; create_uuid: (a?: { as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; decrypt: (a?: { as?: string; data?: Value; algorithm?: "aes-128-cbc" | "aes-192-cbc" | "aes-256-cbc" | "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm" | Value; key?: Value; iv?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; encrypt: (a?: { as?: string; data?: Value; algorithm?: "aes-128-cbc" | "aes-192-cbc" | "aes-256-cbc" | "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm" | Value; key?: Value; iv?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; jwe_decode: (a?: { as?: string; token?: Value; key?: Value; check_claims?: Value; key_algorithm?: "A128KW" | "A192KW" | "A256KW" | "A128GCMKW" | "A192GCMKW" | "A256GCMKW" | "ECDH-ES+A128KW" | "ECDH-ES+A192KW" | "ECDH-ES+A256KW" | Value; content_algorithm?: "A128GCM" | "A192GCM" | "A256GCM" | "A128CBC-HS256" | "A192CBC-HS384" | "A256CBC-HS512" | Value; timeDrift?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; jwe_encode: (a?: { as?: string; headers?: Value; claims?: Value; key?: Value; key_algorithm?: "A128KW" | "A192KW" | "A256KW" | "A128GCMKW" | "A192GCMKW" | "A256GCMKW" | "ECDH-ES+A128KW" | "ECDH-ES+A192KW" | "ECDH-ES+A256KW" | Value; content_algorithm?: "A128GCM" | "A192GCM" | "A256GCM" | "A128CBC-HS256" | "A192CBC-HS384" | "A256CBC-HS512" | Value; ttl?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; jws_decode: (a?: { as?: string; token?: Value; key?: Value; check_claims?: Value; signature_algorithm?: "PS256" | "PS384" | "PS512" | "RS256" | "RS384" | "RS512" | "HS256" | "HS384" | "HS512" | "ES256" | "ES384" | "ES512" | Value; timeDrift?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; jws_encode: (a?: { as?: string; headers?: Value; claims?: Value; key?: Value; signature_algorithm?: "PS256" | "PS384" | "PS512" | "RS256" | "RS384" | "RS512" | "HS256" | "HS384" | "HS512" | "ES256" | "ES384" | "ES512" | Value; ttl?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; random_bytes: (a?: { as?: string; length?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; random_number: (a?: { as?: string; min?: Value; max?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; storage: { create_attachment: (a: { as?: string; value: Value; access?: string; filename?: Value; include_meta?: boolean; type?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; create_audio: (a: { as?: string; value: Value; access?: string; filename?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; create_file_resource: (a: { as?: string; filename: Value; filedata: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; create_image: (a: { as?: string; value: Value; access?: string; filename?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; create_video: (a: { as?: string; value: Value; access?: string; filename?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; delete_file: (a: { pathname: Value; disabled?: boolean; description?: string; output?: OutputAuthored; }) => Statement; read_file_resource: (a: { as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; sign_private_url: (a: { as?: string; pathname: Value; ttl?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; stream: { from_csv: (a: { as?: string; value: Value; separator?: Value; enclosure?: Value; escape_char?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; output?: OutputAuthored; }) => Statement; from_jsonl: (a: { as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; from_request: (a?: { as?: string; url?: Value; method?: Value; params?: Value; headers?: Value; timeout?: Value; follow_location?: Value; verify_host?: Value; verify_peer?: Value; ca_certificate?: Value; certificate?: Value; certificate_pass?: Value; private_key?: Value; private_key_pass?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; text: { append: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; contains: (a: { name?: string; as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; ends_with: (a: { name?: string; as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; icontains: (a: { name?: string; as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; iends_with: (a: { name?: string; as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; istarts_with: (a: { name?: string; as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; ltrim: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; prepend: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; rtrim: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; starts_with: (a: { name?: string; as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; trim: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; }; throw: (a: { name?: string; value: Value; disabled?: boolean; description?: string; }) => Statement; util: { geo_distance: (a?: { as?: string; latitude_1?: Value; longitude_1?: Value; latitude_2?: Value; longitude_2?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; get_all_input: (a?: { as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; get_env: (a?: { as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; get_vars: (a?: { as?: string; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; ip_lookup: (a: { as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; send_email: (a?: { as?: string; service_provider?: "resend" | "xano" | Value; api_key?: Value; subject?: Value; message?: Value; to?: Value; bcc?: Value; cc?: Value; from?: Value; reply_to?: Value; scheduled_at?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; set_header: (a: { value: Value; duplicates?: string; disabled?: boolean; description?: string; }) => Statement; sleep: (a: { value: Value; disabled?: boolean; description?: string; }) => Statement; template_engine: (a: { as?: string; value: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; webflow: { request: (a?: { as?: string; path?: Value; method?: Value; params?: Value; headers?: Value; timeout?: Value; follow_location?: Value; verify_host?: Value; verify_peer?: Value; ca_certificate?: Value; certificate?: Value; certificate_pass?: Value; private_key?: Value; private_key_pass?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; zip: { add_to_archive: (a: { file: Value; filename: Value; zip: Value; password?: Value; password_encryption?: Value; disabled?: boolean; description?: string; }) => Statement; create_archive: (a: { as?: string; filename: Value; password?: Value; password_encryption?: "standard" | "AES-128" | "AES-192" | "AES-256" | Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; delete_from_archive: (a: { filename: Value; zip: Value; password?: Value; disabled?: boolean; description?: string; }) => Statement; extract: (a: { as?: string; zip: Value; password?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; view_contents: (a: { as?: string; zip: Value; password?: Value; disabled?: boolean; description?: string; asFilters?: FilterXdo[]; }) => Statement; }; }; /** * Implied-input catalog per trigger `obj_type` (U1). Xano trigger inputs are * **fixed by type** — they are generated by `mvp:trigger_update_defaults` and * cannot be edited in the UI. This module is the XanoTS mirror of that * generator: `impliedInputs(objType)` returns the exact input array Xano stores * for a trigger of that type. * * Source of truth (mirror, do not "fix"): the Xano engine's trigger * default-input generator (database / toolset / workspace / realtime, plus the * error-trigger input schema). * * Fields are built through the canonical `input.*` / `f.*` constructors + shared * `encodeInput`, so they carry the same stored shape and defaults as every other * XanoTS input (`customize:{}`, `_xsid:""`, numeric `market_item`) and cannot * drift from the field encoder. The shipped `test/fixtures/triggers/*.json` carry * the older `customize:""` parser-generation format; conformance compares * structurally, normalizing that legacy drift and the engine-assigned `_xsid`. */ /** * The stored trigger `obj_type`s. `agent`/`mcpServer` both map to `toolset`. * * `workspace_realtime_channel` is the LEGACY realtime channel trigger and stays * as-is; `realtime_server` and `channel` are the current realtime lifecycle * types (server connect/disconnect, channel join/leave). They are distinct * obj_types, not a replacement — v1 and v2 coexist. */ type TriggerInputObjType = "database" | "toolset" | "workspace" | "workspace_realtime_channel" | "realtime_server" | "channel" | "error"; /** * Typed input handle `t` for trigger stacks (U2, refined for database in U4). * * A trigger's inputs are fixed by type (see `trigger-inputs.ts`). Rather than * make authors guess `inp("new")` as an untyped string, the `trigger.*` * factories pass a typed handle `t` to `stack: (t) => [...]` (and `response: * (t) => ...` on response-bearing types). Each member is a {@link FieldAccessor}: * it is a {@link Value} referencing the whole input **and** callable for typed * column/child access — `t.new("email")` → `inp("new.email")` (KTD-2). This * mirrors the `auth("id")` callable-value precedent and keeps the * `{value,tag,filters}` shape so it composes with `withFilters`. * * Runtime member names are sourced from `impliedInputs(objType)` so the handle * can never drift from the injected input array. */ /** * A trigger input reference: usable whole (as a {@link Value}) or called to * reference a child/column by name — `t.new("email")` → `inp("new.email")`. * `Cols` types the callable's accepted paths (immediate child names, plus a * dotted `child.rest` escape for deeper nesting). A `json`/untyped field uses * `Record`, whose key set widens to `string` (any path). */ type FieldAccessor> = Value & ((path: K | `${K}.${string}`) => Value); /** Realtime channel trigger inputs. */ interface RealtimeInputs { /** The channel action (`"message"` | `"join"`). */ action: Value; /** The channel name. */ channel: Value; /** The connecting client — `permissions` gates the realtime row/table access. */ client: FieldAccessor<{ extras: unknown; permissions: { dbo_id: number; row_id: string; }; }>; /** Connection options. */ options: FieldAccessor<{ authenticated: boolean; channel: string; }>; /** The message payload (nullable). */ payload: Value; } /** The connecting client, shared by both realtime lifecycle trigger types. */ type RealtimeClient = FieldAccessor<{ extras: unknown; permissions: { dbo_id: number; row_id: string; }; }>; /** Realtime server lifecycle trigger inputs (connect / disconnect). */ interface RealtimeServerTriggerInputs { /** The connection action (`"connect"` | `"disconnect"`). */ action: Value; /** The realtime server being connected to. */ realtime_server: Value; /** The connecting client — `permissions` gates its realtime row/table access. */ client: RealtimeClient; } /** Realtime channel lifecycle trigger inputs (join / leave). */ interface RealtimeChannelTriggerInputs { /** The membership action (`"join"` | `"leave"`). */ action: Value; /** The channel path the client addressed. */ channel: Value; /** The joining client — `permissions` gates its realtime row/table access. */ client: RealtimeClient; } /** Toolset trigger inputs (MCP server / agent). */ interface ToolsetInputs { /** The toolset being connected to. */ toolset: FieldAccessor<{ id: number; name: string; instructions: string; }>; /** The list of tools on the MCP server (a list value). */ tools: Value; } /** Workspace lifecycle trigger inputs. */ interface WorkspaceInputs { /** The branch the action targets. */ to_branch: FieldAccessor<{ id: number; label: string; }>; /** The source branch (for merges). */ from_branch: FieldAccessor<{ id: number; label: string; }>; /** The lifecycle action (`"branch_live"` | `"branch_merge"` | `"branch_new"`). */ action: Value; } /** Error trigger inputs — the error signature schema. */ interface ErrorInputs { /** Which event fired (`"new"` | `"regression"` | `"fixed"`). */ event: Value; /** The error signature row id. */ id: Value; /** Stable signature hash. */ signature: Value; /** Error code + message from the failing run. */ error: FieldAccessor<{ code: string; message: string; }>; /** Where the error originated (null on `"fixed"`). */ caller: FieldAccessor<{ type: string; id: number; name: string | null; }>; /** The failing statement (null on `"fixed"`). */ statement: FieldAccessor<{ name: string; xsid: string; }>; /** The user who marked the error fixed (only on `"fixed"`). */ actor: FieldAccessor<{ id: number; name: string | null; }>; /** Occurrence counts. */ count: FieldAccessor<{ total: number; last_hour: number; }>; /** ISO-8601 first occurrence. */ first_seen: Value; /** ISO-8601 most recent occurrence. */ last_seen: Value; /** ISO-8601 fixed-at (nullable). */ fixed_at: Value; } interface RealtimeServerDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; /** * Whether the server accepts connections. Defaults to **false** — a realtime * server is off until explicitly enabled. An enabled server with no active * channel still refuses the handshake, so ship at least one * `realtimeChannel()` with it. */ enabled?: boolean; /** * The public URL token this server is addressed by. Omit and it is minted and * frozen in `xano.lock` at `xanots export --lock`; set it explicitly to pin * one. Never generated at encode time — a canonical must be unique per Xano * instance across all workspaces. */ canonical?: string; /** * Server-level message-history default — the container tier this server's * channels' messages inherit (stored `message_enabled`/`message_limit`). Omit * to inherit from the branch/workspace. Defaults **off**: message history is a * hot path. A scalar: `false` off, `true` on at default depth, a number = * capture depth, `"all"` unlimited. See {@link HistoryInput}. */ history?: HistoryInput; /** Workspace tags (stored `tag: [{tag}]`). */ tags?: string[]; } /** * The stored envelope. Note there is deliberately no `docs` key: unlike * `app`/`query`/`toolset`, the realtime objects do not persist one — the * XanoScript kind accepts `docs` but the stored record has no such field, so * emitting it would add a key the engine never wrote. */ interface RealtimeServerXdo { name: string; description: string; canonical: string; enabled: boolean; history: ContainerHistoryBlock<"message">; tag: Array<{ tag: string; }>; } /** * Resolve a realtime server's `canonical` URL token, in priority order: * an explicit override, the def's own non-empty `canonical`, then the value * minted-and-frozen in `xano.lock` under `realtime_server:`. * * Deliberately never mints: a canonical must be unique per Xano *instance * across all workspaces*, so the only safe place to generate one is * `export --lock` (random, collision-checked, then frozen so every later export * and every client agrees). Mirrors the api-group and toolset resolvers. */ declare function resolveRealtimeServerCanonical(def: { name: string; canonical?: string; }, override?: string): string; declare function encodeRealtimeServer(def: RealtimeServerDef): RealtimeServerXdo; declare const realtimeServerKind: ObjectKind; /** Options for {@link RealtimeServerHandle.getPath}/`getUrl`/`getCanonical`. */ interface RealtimeUrlOptions { /** Override the resolved `canonical` URL token (bypasses the def/lock lookup). */ canonical?: string; /** * Address a TENANT's isolated database instead of the instance's own * workspace. Rides as a `:` prefix on the socket path; the * websocket tier splits on the FIRST `:`, applies the tenant's database, and * only THEN resolves the canonical — so a bare canonical on a tenant host is * looked up in the instance workspace instead, which either misses or serves a * different workspace's channels. Omit for a normal (non-tenant) instance. * * This colon form is PECULIAR TO THE SOCKET: the tenant is glued to the * canonical inside ONE path segment, whereas every other tenant-addressed URL * gives it a segment of its own — the HTTP half of the same client is * `https:///tenant//api:/…`. Neither is derivable * from the other, and no request header is required for either. That both * halves must name it matters because **tokens are tenant-scoped**: a realtime * token carries the audience * `:` rather than the bare license, so one minted through the * instance workspace is rejected by a tenant's realtime server (and vice * versa). Authenticate and dial through the same tenant. * * OFTEN YOU CAN OMIT THIS: `getUrl` LIFTS the tenant out of a base URL that * already names one (`https:///tenant/` — what `xanots sandbox * details` prints and what deploy injects as `window.XANO_HOST`), rewriting it * to the socket's colon form. Pass it explicitly when the base URL does NOT * name the tenant — notably a tenant on its own domain, where HTTP resolves * the tenant from the hostname but the socket cannot (the connection hash is * the websocket tier's only signal). */ tenant?: string; } /** * A `realtimeServer()` handle: the def plus `getCanonical()` and the websocket * connection accessors `getPath()`/`getUrl()`, so a client derives its socket * URL from the def instead of hardcoding it (the same derive-don't-hardcode * contract `query.getPath()` and `mcpServer().getUrl()` give). The accessors are * dropped by `JSON.stringify` and ignored by `encodeRealtimeServer`, so * serialization is unaffected (mirrors {@link McpServerHandle}). * * WIRE PROTOCOL (what to do once the socket is open), for reference: * - Auth is a bearer token carried as the websocket SUBPROTOCOL * (`new WebSocket(url, token)` — `Sec-WebSocket-Protocol`), not a query * param. No token = an anonymous client, admitted only by channels with * `anonymousClients: true`. * - The server builds its context during the handshake; a frame sent * immediately after `open` can be refused as not-ready. Wait out a short * settle window before the first frame — there is no explicit ready frame. * - KEEP IT ALIVE. An idle connection is reaped after ~10 minutes, so a * LISTEN-ONLY client (a feed, a dashboard — anything that subscribes and * rarely publishes) must send something periodically or it is disconnected. * `{ action: "ping" }` answers `{ action: "pong" }` and exists for exactly * this; any frame resets the clock. * - Client frames are JSON `{ action, channel, type?, payload?, options?, id? }` * where `action` is `join` | `leave` | `broadcast` | `ack` | `ping` | * `presence`, `channel` is the resolved channel path * (`realtimeChannel().getChannel()`) and `type` is the `realtimeMessage()` * name. You must `join` before you may `broadcast`. `options` carries * `{ socketId?, client_id?, channel? }` — `socketId` addresses another client * directly and needs `publish.direct`, `client_id` is the at-least-once cursor * handle (below), and `options.channel` wins over a top-level `channel`. * - Server frames carry `action`: `join` (ack — `{ joined: true, params }`, plus * `cursor`/`resumed` on an `at_least_once` channel), `message`, `replay`, * `broadcast` (a RECEIPT to the sender: `delivered_local` counts recipients on * the answering node only, NOT the channel, plus `id` on an at-least-once * channel and `dropped: true` when the handler returned null), * `presence_full`/`presence_join`/`presence_leave`, * `conversation_start`/`conversation_end` (replayed transcript frames are * flagged `conversation: true` so they are distinguishable from live * traffic), `pong`, `ack`, and `error`. * - `replay` and the `conversation_*` frames answer DIFFERENT questions and can * both be on: the transcript is the SHARED "what was said before I arrived" * (replayed as ordinary `message` frames), while `replay` is the PER-CLIENT * "what I missed while disconnected", resumed from this client's own cursor. * - AT-LEAST-ONCE IS A CLIENT CONTRACT. On such a channel, ack what you receive * with `{ action: "ack", channel, id }`; the server confirms with * `{ action: "ack", channel, payload: { cursor } }`, and the next reconnect * replays only what follows that cursor. An ANONYMOUS client must also send a * durable `options.client_id` in its JOIN frame — without one it has no cursor, * its acks are ignored silently, and it degrades to at-most-once. An * authenticated client is keyed by identity and needs no `client_id`. * - `error` carries `payload.message`, plus `payload.code` / * `payload.limit` / `payload.retry_after` when rate limited (`rate_limited` is * the only code — the rest are message-only, so do not switch on `code`). An * `error` is a per-frame refusal, NOT a disconnect — EXCEPT for a failed * handshake and a refused `connect` trigger, which each push an `error` and * then close with code 4401. * - PRESENCE frames (only on a `presence: true` channel) carry a roster: * `presence_full` → `payload.members` (an ARRAY — the whole roster, INCLUDING * the receiving client), `presence_join`/`presence_leave` → `payload.member` * (a single entry). A member is * `{ id, dbo_id, authenticated, extras, joined_at }` — `id` is the auth row id * as a string (`""` for an anonymous client), `dbo_id` the auth table's id * (`0` when anonymous), `extras` the connection's extras object, `joined_at` * epoch SECONDS. Render the roster from `presence_full` and apply the deltas; * the count is members, not connections — the roster is refcounted per * identity, so a second tab of the same user fires no second `presence_join`. * Order on join is: `join` ack → `presence_full` → (others see * `presence_join`) → conversation replay. A joined client can re-request the * snapshot at any time by sending `{ action: "presence", channel }`; it * answers with `presence_full`, or an `error` if you never joined. The full * join order, with everything optional included, is: `join` ack → * `presence_full` → (others see `presence_join`) → conversation replay → * `replay` frames. */ type RealtimeServerHandle = RealtimeServerDef & { /** The server's resolved `canonical` token; throws if none resolves. */ getCanonical(opts?: { canonical?: string; }): string; /** * The websocket connection PATH — `/ws/`, or * `/ws/:` when `tenant` is given — ready to prepend a host * to. The `canonical` is resolved from the def's `canonical` (or * `opts.canonical`, or the value frozen in `xano.lock`); it throws if none * resolves. */ getPath(opts?: RealtimeUrlOptions): string; /** * The absolute websocket URL — `baseUrl` + {@link getPath}, with the scheme * normalized to `ws`/`wss` (`https://x.xano.io` → `wss://x.xano.io/ws/…`), so * the instance base URL you already have can be passed straight in. * * A remote host must end up `wss://`: instances do not serve plain websockets * and browsers block a `ws://` socket from an https page — both surface as an * opaque 1006 close with no reason. Pass the instance base URL (`https://…`, * or `wss://…`); this is the only form `getUrl` builds. * * A base URL that already names a tenant (`https:///tenant/` — * `xanots sandbox details`' `baseUrl`, and the injected `window.XANO_HOST`) * has that tenant LIFTED into the socket's own form, so * `getUrl(window.XANO_HOST)` alone reaches the right database: * `https://h/tenant/ab-cd` → `wss://h/ws/ab-cd:`. Passing a * DIFFERENT `{ tenant }` alongside such a base URL throws rather than picks a * winner. NOT IDEMPOTENT BY DESIGN: a `baseUrl` that already carries a * `/ws/<…>` path (an earlier `getUrl()`/`socketUrl()` result) THROWS — * resolving twice would append a second `/ws/` and drop the tenant. * A tenant served on its own domain has nothing to lift — HTTP * resolves that tenant by hostname, but the websocket tier only ever reads the * connection hash — so pass `{ tenant }` explicitly there. * * IN A BROWSER BUNDLE, prefer the generated manifest: importing this def for * its `getUrl()` pulls the SDK runtime in with it (the same ~289 kB floor a * query def costs — the factory CALLS that build the def run at module load). * `xanots paths --emit xano/routes.gen.ts` writes the identical * address, tenant lift included, as `socketUrl("", baseUrl)` in a file * that imports nothing. * * `/ws` is the INSTANCE INGRESS's routing segment, stripped before the * websocket tier sees the path — the tier reads whatever remains, whole, as * the connection hash. That matters in exactly one case: a direct dial at a * local dev websocket port bypasses the ingress, so it wants the hash ALONE * (`ws://127.0.0.1:/`) and `getUrl`'s `/ws/` segment would be * read as part of the hash and fail to resolve. Build that dev URL by hand * from {@link getCanonical}; every deployed host takes `getUrl`. */ getUrl(baseUrl: string, opts?: RealtimeUrlOptions): string; }; /** * Author a realtime server — the container that owns realtime channels. * Returns a {@link RealtimeServerHandle}: the def plus `getCanonical()` and * `getPath()`/`getUrl()`. * * Pass the handle (not a bare name) to `realtimeChannel({ server })` so the * channel and the server agree on identity even when the server pins an * explicit `guid`. */ declare function realtimeServer(def: RealtimeServerDef): RealtimeServerHandle; /** * Shared `{param}` path handling for the two kinds whose `name` IS a path: an * API `query` (`"blog/{slug}"`) and a `realtimeChannel` (`"rooms/{room_id}"`). * * A path param is two declarations that must agree — the `{param}` marker in the * name, and an input of the same name that the engine binds the URL segment to. * Nothing in the persisted object links them, so an orphan marker deploys as a * permanently-broken route. These helpers make the pair a checked contract and * give both kinds one interpolation routine, so their rules cannot drift. * * Grammar, taken from the engine's router rather than invented here: * path := any literal text with `{name}` markers embedded anywhere * name := any run of characters other than `/` `{` `}` — unique within one path * * The router scans the whole action string for `{...}` markers, substitutes a * capture group for each (`[^/]+`, narrowed to `\d+` for an `int` input and * `\d+\.?\d*` for a `decimal`), then matches the request path against the * result. So a marker does NOT have to be a whole segment — `"blog/post-{slug}"` * routes fine — and a name is not restricted to an identifier. * * This was previously specified as "a whole segment naming `[A-Za-z_]\w*`", * stricter than the engine on both counts, and it rejected routes XANO ITSELF * generates: a table named `1table` gets the CRUD route `"1table/{1table_id}"`, * whose param starts with a digit. Two workspaces in a 187-workspace live sweep * failed verification on that rule alone, and the error text held the mistake up * as an example ("not blog/post-{slug}"). */ /** * The `{param}` names in a path literal, as a union of string literal types — * `PathParams<"blog/{slug}/review/{review_id}">` is `"slug" | "review_id"`, and * a static path yields `never`. Lets an accessor type its params argument from * the authored `name`, so a wrong or missing key is a compile error rather than * a runtime throw at request time. */ type PathParams = S extends `${string}{${infer P}}${infer Rest}` ? P | PathParams : never; /** Whether a path literal `S` carries no `{param}` segments. */ type IsStaticPath = [PathParams] extends [never] ? true : false; /** The exactly-keyed params record for a path literal `S`. */ type PathParamValues = Record, string | number>; /** * Realtime channel (`channel`) — the middle tier of * `realtime_server -> channel -> message`, the realtime analogue of an API * group. * * A channel is addressed by a PATH, not a plain name: `"rooms"` is one channel, * `"rooms/{room_id}"` is one channel per room whose `room_id` is bound and * validated at join time exactly as a query's path parameters are. Both may * coexist under one server, and a literal segment beats a parameter when a join * is matched — so `"rooms/lobby"` and `"rooms/{room_id}"` are two distinct * channels, not a conflict. * * Matching is STRICT, and each rule is a join that fails rather than a path that * gets fixed up: segment counts must be EQUAL, so `"rooms/{room_id}"` does not * match `"rooms/42/edit"` (which is what lets `"org/{org_id}/room/{room_id}"` * exist separately); literal segments are CASE-SENSITIVE; and an empty segment * is rejected rather than collapsed, so a leading, trailing, or doubled `/` * matches nothing. This is why `getChannel()` throws on an empty param or one * containing `/` — the alternative is a join refused for a reason the error * message never mentions. * * The `input` map here types the channel PATH parameters. It is NOT the message * payload — that is `realtimeMessage({ input })`. Both reach the stack. * * A channel path is unique only WITHIN its server, so `server` is required and * is part of the channel's identity (see `realtimeChannelSeedName`). */ /** A reference to the owning realtime server: its `realtimeServer()` handle, or its name. */ type RealtimeServerRef = string | (Pick & { guid?: string; }); /** Who may publish to a channel. */ type ChannelPublishWho = "nobody" | "anyone" | "authenticated"; /** * Delivery guarantee. `at_least_once` changes the TRANSPORT — a briefly * disconnected client must not miss messages, which fire-and-forget pub/sub * cannot provide — so it is a meaningfully heavier setting than a flag. */ type ChannelDeliveryGuarantee = "at_most_once" | "at_least_once"; interface ChannelPublishDef { /** Who may publish. Defaults to `"nobody"` — nobody can publish until you set it. */ who?: ChannelPublishWho; /** * Whether a client may address ANOTHER CLIENT directly — a broadcast frame * carrying `options.socketId`. Defaults to `false`, which refuses such a * frame outright. * * This is a SECOND gate, checked BEFORE `who`: a direct frame must satisfy * both. Leaving it off does not restrict ordinary channel publishing. */ direct?: boolean; } /** * The client-visible TRANSCRIPT — "I just joined `rooms/42`, send me what was * already said". PUSHED to every joiner automatically; there is deliberately no * separate replay toggle and no fetch for the client to make. * * `limit` IS REQUIRED IN PRACTICE: `{ enabled: true }` alone records nothing and * replays nothing, silently. See `limit`. * * Distinct from `history` (EXECUTION history, for the debugger) and from * `delivery.guarantee`'s replay, which answers a different question — the * transcript is the SHARED "what was said before I arrived", while at-least-once * replay is the PER-CLIENT "what I missed while disconnected". Both may be on. */ interface ChannelConversationDef { /** * Retain and replay a transcript. Defaults to `false`. Not sufficient on its * own — pair it with `limit`. */ enabled?: boolean; /** * Messages retained, newest-capped. `0` (the default) retains NONE, which * makes `enabled: true` a no-op — the transcript is never written and never * replayed. Set it to the number of messages a joiner should see. */ limit?: number; /** * Idle expiry for the WHOLE transcript, in seconds. `0` (the default) means no * expiry. * * Not a per-message age cap: every write refreshes the clock, so an active * channel's transcript never ages out, and when it does expire the entire * transcript is dropped at once rather than decaying oldest-first. * * On an `at_least_once` channel this same value ALSO bounds the durable replay * window — and there it does behave as a per-message age cut, taking priority * over `limit`. See `ChannelDeliveryDef.guarantee`. */ ttl?: number; } interface ChannelDeliveryDef { /** * Defaults to `"at_most_once"`. * * `"at_least_once"` IS A CLIENT CONTRACT, not just a channel setting. The * client must acknowledge what it receives (`{ action: "ack", channel, id }`), * and an ANONYMOUS client must also send a durable `options.client_id` in its * join frame — without one it has no cursor, its acks are ignored, and it * silently degrades to at-most-once. An authenticated client is keyed by its * identity and needs no `client_id`. * * The replay window is sized by `conversation.ttl`, else `conversation.limit`, * else 1000 messages — even on a channel with no transcript enabled. */ guarantee?: ChannelDeliveryGuarantee; /** * Run the channel's `deliver` trigger once PER RECIPIENT — a stack on the hot * path, on every node that holds a recipient. Off by default; the cost is * real, and it is per recipient per message. * * Independent of `guarantee`. A no-op unless the channel actually declares a * `deliver` trigger — see `realtimeChannelTrigger({ actions: { deliver } })`, * whose return value decides per recipient. */ perRecipient?: boolean; } interface ChannelRateLimitDef { /** * `0` (the default) means unlimited. Counted per publishing client per * channel, and checked BEFORE the handler runs, so a throttled frame costs no * stack execution. The sender is refused with a `rate_limited` error carrying * `limit` and `retry_after`. * * A cost guardrail, not a security control: an ANONYMOUS client is bucketed * per connection, so reconnecting resets its budget, and the limiter fails * OPEN if its backing store is unavailable. */ messagesPerMinute?: number; } interface RealtimeChannelDef = Record, N extends string = string> { /** * The channel PATH, e.g. `"rooms"` or `"rooms/{room_id}"`. A `{param}` * segment makes the channel dynamic and MUST have a matching `input` entry * whose type is a SCALAR and not a list, or `realtimeChannel()` throws — the * same contract a query's URL path params carry. A `{param}` is always a whole * A `{param}` may be a whole segment (`rooms/{room_id}`) or embedded in one * (`rooms/room-{room_id}`, which fills to `rooms/room-7`); there are no * wildcards or patterns. * * `required: true` is NOT part of that contract and is not checked: path * matching is strict on segment count, so the segment is always present at * join time and an optional scalar declaration binds it fine. * * Captured as a literal so `getChannel(params)` types its keys from it. */ name: N; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `|`. */ guid?: string; /** The owning realtime server — a `realtimeServer()` handle, or its name. Required. */ server: RealtimeServerRef; description?: string; /** Whether the channel accepts joins. Defaults to `true`. */ active?: boolean; /** * Typed PATH parameters, bound at join time. Joining `"rooms/42"` against * `"rooms/{room_id}"` yields `room_id = 42`, coerced and validated by the * declared type. Distinct from a message's `input`, which types the payload. */ input?: I; /** * Admit clients with no auth token. Defaults to `false`. * * Anonymous access is gated TWICE, at both tiers: the server admits the * connection, then the channel admits the join. Setting this alone is not * enough if the owning `realtimeServer` refuses anonymous connections. */ anonymousClients?: boolean; /** Track and expose channel membership. Defaults to `false`. */ presence?: boolean; publish?: ChannelPublishDef; conversation?: ChannelConversationDef; delivery?: ChannelDeliveryDef; rateLimit?: ChannelRateLimitDef; /** * Channel-level message-history default — the container tier this channel's * messages inherit (stored `message_enabled`/`message_limit`). Omit to * inherit from the server, then branch/workspace. Defaults **off**. */ history?: HistoryInput; /** Workspace tags (stored `tag: [{tag}]`). */ tags?: string[]; } interface RealtimeChannelXdo { name: string; description: string; active: boolean; /** The owning server — `id` carries the resolved guid (the engine remaps it on import). */ server: { id: number | string; }; input: InputXdo[]; anonymous_clients: boolean; presence: boolean; publish: { who: ChannelPublishWho; direct: boolean; }; conversation: { enabled: boolean; limit: number; ttl: number; }; delivery: { guarantee: ChannelDeliveryGuarantee; per_recipient: boolean; }; rate_limit: { messages_per_minute: number; }; history: ContainerHistoryBlock<"message">; tag: Array<{ tag: string; }>; } declare function encodeRealtimeChannel(def: RealtimeChannelDef): RealtimeChannelXdo; declare const realtimeChannelKind: ObjectKind; /** The `{param}` segment names in a channel path, in order (`[]` for a static path). */ declare function channelPathParams(path: string): string[]; /** * A `realtimeChannel()` handle: the def plus `getChannel()`, which resolves the * path a client actually joins. The accessor is dropped by `JSON.stringify` and * ignored by `encodeRealtimeChannel`, so serialization is unaffected. */ type RealtimeChannelHandle = Record, N extends string = string> = RealtimeChannelDef & { /** * The concrete channel path to put in a frame's `channel` field — * `{param}` segments filled from `params` (`"rooms/{room_id}"` + * `{ room_id: 42 }` → `"rooms/42"`). A static path needs no argument, and a * parameterized one REQUIRES the params, typed to exactly its segments. * Throws on a missing, empty, or unknown param, and on a value containing * `/` (which would fabricate a path segment and silently join a different * channel). * * IN A BROWSER BUNDLE, prefer the generated manifest's `channelPath()` * (`xanots paths --emit`): identical output and the same * compile-time param checking, in a file that imports no SDK runtime. */ getChannel: IsStaticPath extends true ? (params?: Record) => string : (params: PathParamValues) => string; }; /** * Author a realtime channel — a joinable path on a realtime server that owns * message handlers. Returns a {@link RealtimeChannelHandle}: the def plus * `getChannel()`. * * Pass the returned handle (not a bare path) to `realtimeMessage({ channel })`: * the handle carries the owning server, so the message resolves both refs * without repeating it. */ declare function realtimeChannel, const N extends string = string>(def: RealtimeChannelDef): RealtimeChannelHandle; /** The guid a channel def resolves to — composed from its server and its path. */ declare function realtimeChannelGuid(def: Pick): string; /** * Trigger kinds (U4). All 6 trigger types share ONE stored envelope * discriminated by `obj_type` + a per-type `meta` block — * confirmed against the Xano engine's stored trigger shape. The canonical `meta` * carries all four action groups (database / toolset / workspace / * workspace_realtime_channel); each trigger type populates its own group and * leaves the others at their skeleton defaults. * * Response-bearing types (realtime, mcp_server, agent) emit `result[]`; * config-only types (table, workspace, error) do not. * * **Implied inputs (U1-U4).** A trigger's inputs are fixed by type — Xano * generates them in `mvp:trigger_update_defaults` and they cannot be edited. * XanoTS injects the exact per-type input array (`impliedInputs`) at encode * time and exposes those inputs to the stack through a typed handle `t`: * `stack: (t) => [...]`. There is no user-supplied `input` field — the implied * inputs are the only inputs. For a database trigger bound to a `table()` * handle, `t.new` / `t.old` are typed against the table's row, with nullability * keyed on the enabled actions (delete → `new` is null, insert → `old` is null). */ /** The stored trigger `obj_type` (identical set to {@link TriggerInputObjType}). */ type TriggerObjType = TriggerInputObjType; interface DatabaseActions { delete?: boolean; insert?: boolean; truncate?: boolean; update?: boolean; } interface WorkspaceActions { branch_live?: boolean; branch_merge?: boolean; branch_new?: boolean; } interface RealtimeActions { message?: boolean; join?: boolean; } /** * Realtime SERVER lifecycle actions (obj_type=realtime_server). * * `connect` GATES the connection — the stack's return admits or denies it. A * denial closes the socket (code 4401) after an `error` frame, before the * connection is ever ready, so it is a real front door and not just an observer. * Same return shape as a channel `join`: `{ allowed: c.bool(true) }` or any truthy value * admits, and an EMPTY OR FALSY return DENIES — including a gating trigger with * no `response`, which returns nothing and so refuses every client. A CRASH also * DENIES: the transport seeds a deny decision before running the stack and keeps * it when the stack throws, because a gate that cannot answer must not admit. * * So both failure modes lock the door rather than open it, and the risk to plan * for is a self-inflicted lockout — an unguarded drill into a `db.get` that * bound `null` raises, and every client is refused. The one thing that admits * without asking is NOT declaring the action at all: gating is opt-in, so a * server with no `connect` trigger accepts every connection. * * `disconnect` is OBSERVATIONAL: its return is ignored and a throw is swallowed, * because the connection is already gone and cleanup must always complete. * * Both are server-scoped, so `s.realtime.get_session` works but carries no * channel path and no bound params. */ interface RealtimeServerActions { connect?: boolean; disconnect?: boolean; } /** * Realtime CHANNEL lifecycle actions (obj_type=channel). Independent booleans, * not a mode — one trigger may carry any combination. * * The three do NOT share a posture, and the difference decides what a stack * should return: * - `join` GATES the join, and runs BEFORE the client becomes a member, so a * denial means it never receives a fan-out. Return `{ allowed: c.bool(true) }` (an * optional `reason` surfaces in the client's error frame) or any other truthy * value to admit. AN EMPTY OR FALSY RETURN DENIES — a stack that just falls * through, or a gating trigger with no `response`, refuses the join. A CRASH * DENIES TOO (the gate is seeded with a deny and keeps it when the stack * throws), which is the INVERSE of a normal message: a message whose stack * crashes still delivers, so one workspace bug cannot black-hole a channel. * Note the asymmetry with `deliver` below, which is a gate that fails OPEN. * * `join` and `leave` both bind the channel's typed path params as INPUTS, so * `inp("room_id")` resolves inside them and the gate can decide per room. * (`s.realtime.get_session` carries the same values under `params`; either * reads them.) A SERVER `connect`/`disconnect` trigger has no channel and so * no params — that is the one place a path param cannot be read. * - `leave` is OBSERVATIONAL (return ignored, throws swallowed). * - `deliver` GATES delivery PER RECIPIENT — it runs once for each client the * message is about to reach. It is the heaviest of the three by a wide * margin (a stack per recipient per message) and needs `delivery.perRecipient` * on the channel to run at all. * * ITS RETURN VALUES DO NOT READ LIKE A FILTER. Only an explicit **null** * drops the message for that recipient. An **object** replaces that * recipient's payload. ANYTHING ELSE — including `false`, `0`, and `""` — * DELIVERS THE MESSAGE UNCHANGED, as does a crash. So `return false` from a * yes/no redaction check sends the message it was meant to suppress; return * null instead. * * The delivered payload arrives NESTED under `payload`, so read * `inp("payload").`. And the two identities differ: `t.client` is the * SENDER, while `s.realtime.get_session` describes the RECIPIENT this run is * for — per-viewer redaction needs both, and reaching for the wrong one is * silent. */ interface RealtimeChannelActions { join?: boolean; leave?: boolean; deliver?: boolean; } /** The row type a database trigger references, or a `json` floor when no * `table()` handle is bound (a raw numeric `objId` carries no field brands). */ type TriggerRow = [InferRow] extends [never] ? Record : InferRow; /** `new` is present when an insert or update action is enabled. */ type HasNew = A extends { insert: true; } ? true : A extends { update: true; } ? true : false; /** `old` is present when an update or delete action is enabled. */ type HasOld = A extends { update: true; } ? true : A extends { delete: true; } ? true : false; /** * The typed handle passed to a database trigger's `stack`. `action`/`datasource` * are always present; `new`/`old` are typed row accessors when their action is * enabled and `null` otherwise (delete → `new` null, insert → `old` null, * update → both, truncate → neither). A multi-action trigger offers both — the * runtime value can still be empty for the op that didn't fire, discriminated * via `t.action`. */ type DatabaseInputs = { /** The database op (`"insert"` | `"update"` | `"delete"` | `"truncate"`). */ action: Value; /** The data source label the change occurred on. */ datasource: Value; } & (HasNew extends true ? { new: FieldAccessor; } : { new: null; }) & (HasOld extends true ? { old: FieldAccessor; } : { old: null; }); /** * Internal trigger def produced by the `*Trigger` root factories. * * Generic over the branded stack tuple `S`, the literal response `Resp`, and a * declared `Res` (the `responseShape` override), so `InferResponse` resolves a * response-bearing trigger the way it resolves any other kind. All default, so * the bare `satisfies TriggerDef` form codegen emits is unchanged. * * The carriers arrive here through the factories' BUILDER CALLBACKS rather than * from plain fields — `stack: (t) => [...]` and `response: (t) => ({...})` are * invoked at factory time, and a `const` type parameter infers the callback's * return as a tuple exactly as it would a directly-passed array. Without that, * the resolved stack reached the returned def as a widened `Statement[]` and * every ref in the response bottomed out. */ interface TriggerDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; objType: TriggerObjType; /** * The bound object id. For a database trigger this is the target table — pass * a `table()` handle/name via the `table` factory arg instead and it resolves * to the table's portable guid (the engine remaps guid→local id on import, * exactly like a query's `app` binding). A raw numeric id is the escape hatch. */ objId?: number | string; description?: string; active?: boolean; /** * The resolved statement stack (the `stack` callback is invoked at factory * time). Captured as the literal tuple `S` so `InferResponse` can trace a * response ref back to the statement that bound it; a helper typed * `Statement[]` spread into the callback's return widens it and the trace * degrades — declare `responseShape` there. */ stack?: S; /** The resolved response (response-bearing types only), captured as the * literal `Resp` so `InferResponse` can derive its keys and trace each member. */ response?: Resp; /** * Type-only: declare the trigger's response shape so `InferResponse` recovers it exactly, overriding automatic derivation. The runtime * value is ignored by `encodeTrigger`; only its type is read. */ responseShape?: Res; /** Whether this type emits a `result[]` (response-bearing). */ hasResult: boolean; /** The per-type meta block (already populated for this type). */ meta: Record; /** * Request-history capture. Omit to inherit from the workspace (triggers have * no container tier). A scalar: `false` off, `true` on at default depth, a * number = capture depth, `"all"` unlimited. Any value stops inheriting. * Triggers default OFF. See {@link HistoryInput}. */ history?: HistoryInput; /** Workspace tags (stored `tag: [{tag}]`), e.g. `["xano:quick-start"]`. */ tags?: string[]; } interface TriggerXdo { name: string; active: boolean; description: string; /** Numeric local id, or the bound object's guid (the portable form). */ obj_id: number | string; obj_type: TriggerObjType; history: { inherit: boolean; enabled: boolean; limit: number; }; output: unknown[]; meta: Record; tag: unknown[]; input: InputXdo[]; run: StackItemXdo[]; result?: ResultItemXdo[]; } /** * Any trigger def, whatever its stack/response — the parameter type every * consumer that only READS a def wants. `Res` is widened to `unknown` rather * than left at the `never` default, which would reject a def that declares * `responseShape`; the same widening `encodeQuery` uses. */ type AnyTriggerDef$1 = TriggerDef; declare function encodeTrigger(def: AnyTriggerDef$1): TriggerXdo; /** Fields common to every trigger factory. Note: no `input` — trigger inputs * are implied by type and cannot be user-supplied. */ interface CommonArgs { name: string; /** Explicit Xano `guid` (defaults to a guid derived from `name`). */ guid?: string; description?: string; active?: boolean; objId?: number; /** Workspace tags (stored `tag: [{tag}]`), e.g. `["xano:quick-start"]`. */ tags?: string[]; } /** * Database table trigger (obj_type=database; XanoScript authoring term `table`). * Config-only (no response). The `stack` callback receives `t` with * `t.new`/`t.old` (typed against the bound `table` row) plus * `t.action`/`t.datasource`. */ declare function tableTrigger(args: CommonArgs & { table?: T; datasources?: string[]; actions?: A; /** * Fire only for row changes matching this condition — Xano's "custom filter". * * Reference the changed row through the SQL-side pseudo-tables with `col()`: * `col("NEW.status")` is the row after the change, `col("OLD.status")` before * it. These are Postgres trigger operands, not the `t.new`/`t.old` stack * handle, because the condition is evaluated by the DATABASE before any stack * runs — which is also why a filtered trigger installs as a dynamic trigger * rather than a static one. * * Three rules the engine enforces, checked here instead of at deploy: * `truncate` admits no filter at all (there is no row to test), `insert` may * not read `OLD.*`, and `delete` may not read `NEW.*`. */ search?: Condition; stack?: (t: DatabaseInputs, A>) => Statement[]; }): TriggerDef; /** * LEGACY realtime trigger (obj_type=workspace_realtime_channel). Response-bearing. * * @deprecated Superseded. This fires against Xano's older workspace-global realtime * config, which is a DIFFERENT object from the current `channel` despite the shared * vocabulary — the two generations coexist and mixing them fails at runtime rather * than at compile. * * It is still exported and still supported because `xanots codegen` has to bring * back a workspace that holds one. Do not author it in new code: * - its `join` action is now `realtimeChannelTrigger({ actions: { join: true } })` * - its `message` action is now a `realtimeMessage()` handler, because a message is * an authored unit with its own typed payload and stack rather than a trigger * action * * Withheld from the object-kind catalog and named only in `llms/legacy.md`. */ declare function realtimeTrigger(args: CommonArgs & { actions?: RealtimeActions; stack?: (t: RealtimeInputs) => S; response?: (t: RealtimeInputs) => Resp; responseShape?: Res; }): TriggerDef; /** * Realtime server lifecycle trigger (obj_type=realtime_server) — fires when a * client connects to or disconnects from a realtime server. Response-bearing. * * Bind the target with `realtimeServer` (a `realtimeServer()` handle or its * name); it resolves to the server's guid at export, so the binding survives a * `--reset` deploy. A raw numeric `objId` stays the escape hatch. * * Like every other trigger type, its `meta` carries the WHOLE six-group skeleton * with only its own group's flags set. An earlier version of this comment claimed * the realtime types stored a single-group `meta`; a live engine capture disproved * it — the engine emits all six groups for every type. */ declare function realtimeServerTrigger(args: CommonArgs & { realtimeServer?: ObjectRef; actions?: RealtimeServerActions; stack?: (t: RealtimeServerTriggerInputs) => S; response?: (t: RealtimeServerTriggerInputs) => Resp; responseShape?: Res; }): TriggerDef; /** * Realtime channel lifecycle trigger (obj_type=channel) — fires when a client * joins or leaves a channel, or when a message is about to be delivered to one. * Response-bearing. * * Bind the target with `channel` (a `realtimeChannel()` handle, which also * carries its server) — a bare channel path is NOT accepted here, because a * path is unique only within a server and would bind ambiguously. * * The three actions have three different postures — see * {@link RealtimeChannelActions}. `deliver` is the one worth reading about before * enabling: it runs once per RECIPIENT per message, and its return decides that * recipient's copy of the payload. */ declare function realtimeChannelTrigger(args: CommonArgs & { channel?: RealtimeChannelDef; actions?: RealtimeChannelActions; stack?: (t: RealtimeChannelTriggerInputs) => S; response?: (t: RealtimeChannelTriggerInputs) => Resp; responseShape?: Res; }): TriggerDef; /** * MCP server trigger (obj_type=toolset, connection action). Response-bearing. * Bind the target MCP server with `mcpServer` (a `mcpServer()` def handle or * its name) — it resolves to the toolset guid at export and survives a * `--reset` deploy. A raw numeric `objId` stays the escape hatch. */ declare function mcpServerTrigger(args: CommonArgs & { mcpServer?: ObjectRef; stack?: (t: ToolsetInputs) => S; response?: (t: ToolsetInputs) => Resp; responseShape?: Res; }): TriggerDef; /** * Agent trigger (obj_type=toolset, connection action). Response-bearing. * Bind the target agent with `agent` (an `agent()` def handle or its name), * resolved to the toolset guid at export; `objId` is the raw escape hatch. */ declare function agentTrigger(args: CommonArgs & { agent?: ObjectRef; stack?: (t: ToolsetInputs) => S; response?: (t: ToolsetInputs) => Resp; responseShape?: Res; }): TriggerDef; /** Workspace lifecycle trigger (obj_type=workspace). Config-only. */ declare function workspaceTrigger(args: CommonArgs & { actions?: WorkspaceActions; stack?: (t: WorkspaceInputs) => Statement[]; }): TriggerDef; /** * Error trigger (obj_type=error). Config-only, and the one type with no action * flags of its own — there is no `error` group in the meta skeleton, because an * error trigger fires on the error events its INPUT schema describes rather than * on a set of toggles. * * It still writes the full {@link baseMeta} skeleton, like every other type. This * used to be `{}`, which contradicted both that rule and the one shipped fixture * (which stores two of the groups). All three spellings are inert: the engine * reads every group as `?? false`, so an absent group and an all-off group are * the same state, and `normalize` treats them as one. With no live capture to * settle which one Xano writes, matching the SDK's own rule is the spelling that * leaves no contradiction to trip over — not an engine-verified correction. */ declare function errorTrigger(args: CommonArgs & { stack?: (t: ErrorInputs) => Statement[]; }): TriggerDef; declare const triggerKind: ObjectKind; /** * MCP server authoring def — the shared toolset envelope, plus the optional LLM * block. * * `agent` and `mcpServer` are two authoring surfaces over ONE stored * `mvp_toolset` row, distinguished only by `type`, so an MCP server can hold the * same `agent_settings` an agent does — and real ones do. `llm` stays optional * and is written only when authored, so an MCP server that does not set it * emits exactly the bytes it always has. */ type McpServerDef = ToolsetBaseDef & { /** Typed LLM settings (provider + model + generation config), when this server carries them. */ llm?: LlmSettings; /** Optional structured output schema, paired with {@link llm}. */ output?: AgentOutput; }; /** Options for {@link McpServerHandle.getPath}/`getUrl`. */ interface McpPathOptions { /** Override the resolved `canonical` URL token (bypasses the def/lock lookup). */ canonical?: string; /** * The URL-embedded auth token path segment. Defaults to `"mcp"` — the literal * placeholder the endpoint treats as "no URL token", meaning auth is passed via * the `Authorization: Bearer …` header instead. Pass a token to embed auth in * the path. */ token?: string; } /** * An `mcpServer()` handle: the def plus URL accessors. It stays a plain data * descriptor with two added methods — dropped by `JSON.stringify` and ignored by * `encodeMcpServer`, so serialization and conformance are unaffected (mirrors * `QueryHandle`). */ type McpServerHandle = McpServerDef & { /** * The MCP server's **Streamable HTTP** endpoint path — * `/x2/mcp///stream` — ready to prepend a host and point a * client at. The `canonical` is resolved from the def's `canonical` (or * `opts.canonical`, or the value frozen in `xano.lock`); it throws if none * resolves. `token` defaults to `"mcp"` (no URL auth). * * Streamable HTTP only — the SDK does not surface the legacy HTTP+SSE * transport (deprecated in the MCP spec). */ getPath(opts?: McpPathOptions): string; /** * The absolute endpoint URL — `baseUrl` (trimmed, trailing slash dropped) + * {@link getPath}. NOT IDEMPOTENT BY DESIGN: a `baseUrl` that already carries * an `/x2/mcp/<…>/stream` path (an earlier `getUrl()` result) THROWS, as does * an empty one — resolve ONCE from the instance base URL and pass that result * to the client. */ getUrl(baseUrl: string, opts?: McpPathOptions): string; }; interface McpServerXdo extends ToolsetBaseXdo { type: "mcp"; /** Present only when the def authored an {@link McpServerDef.llm}. */ agent_settings?: AgentSettingsXdo; } declare function encodeMcpServer(def: McpServerDef): McpServerXdo; declare const mcpServerKind: ObjectKind; /** * Author an MCP server — a collection of tools exposed over the MCP protocol. * Returns an {@link McpServerHandle}: the def plus `getPath()`/`getUrl()`, so a * frontend or external client derives the endpoint URL from the def instead of * hardcoding it (the same derive-don't-hardcode contract `query.getPath()` gives * API endpoints). */ declare function mcpServer(def: McpServerDef): McpServerHandle; /** The three CORS modes the engine stores. */ declare const CORS_MODES: readonly ["default", "custom", "disabled"]; type CorsMode = (typeof CORS_MODES)[number]; /** * Group-level CORS policy. * * ⚠ Every field except `mode` applies ONLY under `mode: "custom"`. Under * `"default"` the engine serves a fixed permissive policy (any origin, * `allow-headers: *`, `allow-credentials: true`, `max-age: 86400`) and IGNORES * the rest of this block, so setting `maxAge`/`allowCredentials`/`allowHeaders` * without switching to `"custom"` changes nothing. */ interface CorsConfig { /** * `"default"` (the default) — fixed permissive policy, rest of the block * ignored. `"custom"` — this block is applied. `"disabled"` — no CORS headers * at all, so browsers reject every cross-origin call. */ mode?: CorsMode; /** * Allowed origins, matched EXACTLY (scheme + host + port, no wildcard or * subdomain expansion) against the request's `Origin` under `mode: "custom"`. * A request whose origin is not listed gets NO CORS headers at all — the * browser then reports a missing `Access-Control-Allow-Origin`. `"*"` is not * special here: it is compared as a literal origin and matches nothing. To * allow any origin, use `mode: "default"`. */ allowOrigins?: string[]; /** Allowed request headers under `mode: "custom"`. Empty falls back to `*`. */ allowHeaders?: string[]; /** Send `access-control-allow-credentials` under `mode: "custom"`. */ allowCredentials?: boolean; /** Preflight cache seconds under `mode: "custom"`. `0` omits the header. */ maxAge?: number; /** * Methods allowed under `mode: "custom"`. This also GATES the real response: * a request whose method is not enabled here gets no CORS headers back, even * though its preflight passes. Enable every verb the group's queries use. * Leaving all of them off sends no `access-control-allow-methods` header. */ allowMethods?: { delete?: boolean; get?: boolean; head?: boolean; patch?: boolean; post?: boolean; put?: boolean; }; } interface ApiGroupDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; canonical?: string; description?: string; docs?: string; swagger?: boolean; apiGroupEnabled?: boolean; documentation?: { require_token: boolean; token: string; }; cors?: CorsConfig; /** * Group-level pre/post middleware. Queries in this group inherit this chain * when they don't set their own `middleware` (the API-Group tier of the * Query → API Group → Workspace fallback). Providing a phase sets its * `_customize` flag; `pre: middleware.clear()` overrides with nothing. */ middleware?: MiddlewareAttach; /** * Group-level request-history default. This is the container tier queries in * the group inherit from (stored `query_enabled`/`query_limit`). Omit to * inherit from the workspace. A scalar: `false` off, `true` on at default * depth, a number = capture depth, `"all"` unlimited. See {@link HistoryInput}. */ history?: HistoryInput; /** Workspace tags (stored `tag: [{tag}]`), e.g. `["xano:quick-start"]`. */ tags?: string[]; } interface ApiGroupXdo { name: string; description: string; canonical: string; swagger: boolean; api_group_enabled: boolean; docs: string; documentation: { require_token: boolean; token: string; }; middleware: MiddlewareBlock; history: ContainerHistoryBlock<"query">; tag: unknown[]; cors: Required & { allowMethods: Required>; }; } declare function encodeApiGroup(def: ApiGroupDef): ApiGroupXdo; declare const apiGroupKind: ObjectKind; /** Author an API group (query container). */ declare function apiGroup(def: ApiGroupDef): ApiGroupDef; /** * Query (API endpoint) kind (U7) → payload key `query`. Function-like * (input/run/result) plus HTTP fields: `verb`, `app` (api_group binding), * `auth`, `response_type`, `cache`, `output`. Validated against * the Xano engine's persisted shape. */ /** * The six HTTP verbs the engine stores, in the exact casing it stores them. * Exported as a runtime tuple (not just the {@link HttpVerb} type) so the * encoder can check a value the type system never saw — see * {@link assertQueryClosedSets}. */ declare const HTTP_VERBS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]; type HttpVerb = (typeof HTTP_VERBS)[number]; /** The two `response_type` values the engine stores for a query. */ declare const QUERY_RESPONSE_TYPES: readonly ["standard", "stream"]; type QueryResponseType = (typeof QUERY_RESPONSE_TYPES)[number]; /** * `QueryDef` is generic over its `input` map `I` so a consumer can recover the * exact, branded input types via `InferInput` (see * `src/inputs/infer.ts`), and over its declared response shape `Res` so * `InferResponse` (see `src/responses/infer.ts`) recovers the * read shape. Both default so every existing use — a bare `QueryDef` — works * unchanged; `Res` defaults to `never` (undeclared), which routes * `InferResponse` to automatic derivation. */ interface QueryDef = Record, Res = never, Resp extends ResponseDef = ResponseDef, S extends readonly Statement[] = readonly Statement[], N extends string = string> { /** * The endpoint path within its api group — the last segment(s) of * `/api:/`. * * Holds ONLY letters, digits, `_`, `-`, `/`, and the `{}` of a path param — * the engine's stored charset, capped at 200 characters. Anything else (most * often a `.`, as in `export.zip`) is NOT rejected by Xano: it saves the * endpoint with an empty name, which deploys clean and then 404s * `Unable to locate request.` on every request. `query()` throws instead. * For a download endpoint use `export_zip` or `export/zip` and put the file * extension in the response headers. * * A `{param}` segment makes it a URL PATH PARAM: `"blog/{slug}"` binds the * segment to the `slug` input, and segments chain * (`"blog/{slug}/review/{review_id}"`). Every `{param}` MUST have a matching * `input` entry with a scalar type, or `query()` throws — a marker with no * input deploys as a permanently-broken route. `required: true` is NOT * demanded, because Xano's own editor leaves path-param inputs unmarked. A * marker need not be a whole segment: `"blog/post-{slug}"` routes fine. * Inputs that are not in the path are ordinary query-string/body params and * need nothing special. * * Captured as a literal so `getPath({ params })` types its keys from it. */ name: N; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; /** * The HTTP method, UPPERCASE — one of `GET`, `POST`, `PUT`, `PATCH`, * `DELETE`, `HEAD`. Anything else is refused at authoring time: the engine * stores an unrecognized verb as NULL, a null verb serves as GET, and the * endpoint then answers on the wrong method while the intended one 404s * `Unable to locate request.` A lowercase `"post"` is the usual miss. */ verb: HttpVerb; /** * The API group this query belongs to — an `apiGroup()` def or its name. * Resolved to the group's guid (which the engine remaps to a local id on * import), so the binding is stable across syncs. Prefer this over the raw * numeric `apiGroupId`. Pass the def handle when the group sets an explicit `guid`. */ apiGroup?: ApiGroupDef | string; /** Escape hatch: a raw numeric `app.id`. Takes precedence over `apiGroup`. */ apiGroupId?: number; /** * The authentication table backing this endpoint, or `false`/omitted for a * public (no-auth) endpoint. Pass the auth `table()` def — the table marked * `table({ auth: true })` — and `export()` resolves it to that table's guid * (the engine remaps guid→local id on import), so the binding is stable across * syncs. A bare table name resolves the same way, but pass the def handle when * the table pins an explicit `guid`: a bare name derives its guid from the name * alone and would diverge from the pinned identity. A raw numeric `dbo.id` is * an escape hatch that wins when given. Xano supports any number of auth * tables, so name the one this endpoint authenticates against; `export()` * rejects a reference to a table it cannot find (and warns when the table is * not marked `auth: true`, which the engine allows). Once set, read the * authenticated record inside the stack with the `auth("path")` value ref. * * Unlike `apiGroup`, the numeric escape hatch lives in this same field rather * than a separate `authId`: `auth` is a single terminal value with no second * consumer (the `apiGroup` handle also feeds `getPath()`'s `canonical`, which * is why *it* needs the symbolic ref and the raw id to coexist as two fields). */ auth?: false | null | TableDef | string | number; description?: string; docs?: string; /** * `"standard"` (default) buffers the response; `"stream"` streams it. Any * other value is refused at authoring time — the engine stores an * unrecognized `response_type` as NULL and falls back to `"standard"`, so a * misspelled `"streaming"` silently buffers. */ responseType?: QueryResponseType; apiEnabled?: boolean; disabled?: boolean; cache?: Partial; /** * Pre/post middleware attachment. `middleware: { pre: [mw], post: [...] }` * runs the listed middleware around this endpoint's stack. Providing a phase * sets its `_customize` flag (override); omitting it inherits from the API * group, then the workspace (the engine resolves the chain — XanoTS emits * the flags and lists). `pre: middleware.clear()` overrides with nothing * (stop inheriting). Reference a `middleware()` def handle or its name. * * A `pre` middleware runs **after** auth resolution, so `auth()` is available * inside the middleware when this endpoint is authenticated (its `auth` names * an auth table). On a public endpoint (`auth` unset) `auth()` is `null` — so * keying a rate limit by `auth("id")` on a public endpoint collapses every * caller into one shared bucket. `export()` **warns** (never blocks) when an * `auth()`-keyed middleware is attached here and this endpoint has no auth table. */ middleware?: MiddlewareAttach; /** * Request-history capture. Omit to inherit (API group → workspace). A scalar: * `false` off, `true` on at default depth, a number = capture depth, `"all"` * unlimited. Any value stops inheriting. See {@link HistoryInput}. */ history?: HistoryInput; /** Workspace tags (stored `tag: [{tag}]`), e.g. `["xano:quick-start"]`. */ tags?: string[]; /** * Saved UNIT TESTS — named input sets run against this object, with * assertions on the response. The same tests the Xano editor shows. * * Build assertions with the top-level `expect.*` helpers (NOT `s.expect.*`, * which builds workflow-test statements). A statement in this object's stack * can return a mock instead of running, per test, via its `mock` option. * * ⚠ A run uses an EMPTY datasource, so no `table({ seed })` row is visible to * it: every `db` read misses, and an assertion on the first row fails against * a deployment whose endpoint returns those rows over HTTP. Create what the * test needs inside the run — a `defineFunction` fixture the stack calls * first — or `mock` the read. */ tests?: TestDef[]; /** * The saved request/response SAMPLE the Xano editor records — free-form JSON, * not tagged values. Recorded from a real call, so treat it as user data: * `codegen` DOES bring it back, deliberately, rather than dropping an * authored artefact silently. */ example?: QueryExample; input?: I; /** * The endpoint's statement stack. Captured as the literal tuple `S` (via * `query()`'s `const` inference) so `InferResponse` can trace a single-variable * response back to the branded `db.get`/`db.query` that bound it (U5). A * dynamically-built `Statement[]` widens `S` and the trace degrades to * `unknown` — the override (`responseShape`) remains the escape hatch. */ stack?: S; /** * The response assignment: a single {@link Value} (returned directly) or a * record of named values (an object with those keys). Captured as the literal * `Resp` so `InferResponse` can auto-derive object-literal keys (U2) and, with * the branded stack, trace a single-variable response (U5). */ response?: Resp; /** * Type-only: declare the endpoint's response shape so * `InferResponse` recovers it exactly (the always-correct * override, taking precedence over automatic derivation). Reuse the read-side * types you already have — e.g. `responseShape: [] as InferRow[]` * for a list, or `null as InferRow | null` for a get. Use it for * responses the static walk can't see (filters, lambdas, control-flow vars). * The runtime value is ignored by `encodeQuery`; only its type is read. */ responseShape?: Res; } /** * A `query()` handle: the def plus `getPath()` and `toSearchParams()`. It stays * a plain data descriptor with two added methods — they are dropped by * `JSON.stringify` and ignored by `encodeQuery`, so serialization and * conformance are unaffected. */ type QueryHandle = Record, Res = never, Resp extends ResponseDef = ResponseDef, S extends readonly Statement[] = readonly Statement[], N extends string = string> = QueryDef & { /** * The endpoint's **group-relative** URL path — `/api:/` — * ready to prepend a host and drop into `fetch`. The api group's `canonical` * is resolved from the bound `apiGroup` handle (or `opts.canonical`); it * throws if neither is available (an empty canonical is minted into * `xano.lock` at export and is not knowable from the def alone). The HTTP * verb is available separately as `.verb`. * * When the name carries `{param}` segments, `params` is REQUIRED and its * keys are exactly those params — `getPath({ params: { slug: "hello" } })` * → `/api:blog/blog/hello`. It throws on a missing, empty, or unknown param, * and on a value containing `/` (which would address a different route). */ getPath: IsStaticPath extends true ? (opts?: { canonical?: string; }) => string : (opts: { canonical?: string; params: PathParamValues; }) => string; /** * Serialize this endpoint's inputs into a GET query string, dropping the * ones bound to `{param}` path segments — those already ride in the path via * {@link getPath}, and sending them twice is how `?slug=` ends up alongside * `/blog/hello`. Otherwise identical to the free {@link toSearchParams} * (which has no view of the route and so keeps every key). */ toSearchParams: { (input: Record): URLSearchParams; (input: Record): URLSearchParams; }; }; /** A query's saved request/response sample — free-form JSON on both sides. */ interface QueryExample { input?: unknown; output?: unknown; } interface QueryXdo { name: string; description: string; docs: string; api_enabled: boolean; /** `false` (no auth), the auth table's guid, or a raw numeric `dbo.id`. */ auth: false | number | string; response_type: string; verb: HttpVerb; disabled: boolean; /** The api group binding: a numeric local id, or the group's guid (the portable form). */ app: { id: number | string; }; cache: CacheXdo; output: unknown[]; middleware: MiddlewareBlock; tag: unknown[]; history: { inherit: boolean; enabled: boolean; limit: number; }; input: InputXdo[]; result: ResultItemXdo[]; run: StackItemXdo[]; test: TestXdo[]; example: QueryExample; market_item: { id: number; version: number; guid: string; }; } declare function encodeQuery(def: QueryDef, unknown>): QueryXdo; declare const queryKind: ObjectKind; declare function queryImpl = Record, Res = never, Resp extends ResponseDef = ResponseDef, const S extends readonly Statement[] = readonly [], const N extends string = string>(def: QueryDef): QueryHandle; /** * A value acceptable in a query-string param. Covers the *scalar* subset an * `InferInput` map yields — scalars, plus arrays of scalars (repeated as * `?k=a&k=b`). Nested `input.object`/`input.list` shapes are deliberately * excluded (no canonical query-string encoding): a literal typed against this * member won't type-check, and one reaching {@link toSearchParams} through the * wide overload throws at runtime rather than serializing to `"[object Object]"`. * `null`/`undefined` are dropped so an absent optional input contributes no param. */ type SearchParamValue = string | number | boolean | null | undefined | ReadonlyArray; /** * Serialize a query input map into {@link URLSearchParams} for a GET request — * `query.toSearchParams(input)`. GET endpoints carry their inputs in the query * string, not a JSON body; this is the transport counterpart to the scalar * inputs of `InferInput`, so a generic `fetch` wrapper doesn't have to * hand-roll the `?k=v` convention. Scalars stringify (`true`→`"true"`, `1`→`"1"`, * `0`/`false` are kept), arrays repeat the key, and `null`/`undefined` are omitted. * * Fails loud rather than emitting a garbage param: a non-finite number * (`NaN`/`Infinity`) or a non-primitive value (an object slipping past the type * via `any`) throws a {@link TypeError} instead of serializing to `"NaN"` / * `"[object Object]"`. * * Two call shapes, one runtime. Authored literals get {@link SearchParamValue} * autocomplete from the strict overload, but the wide `Record` * overload accepts everything the strict one rejects — so a bad literal * type-checks here and is caught only at runtime, not at compile time. That is * deliberate: a generic transport that holds its endpoint input opaquely * (`Record`, or an `InferInput` map behind a generic type * param) passes it with no `as` cast, and the runtime scalar guard in the body * below is the real check — a non-serializable value throws rather than slipping * through, whichever overload it came in on. * * @example * const q = query({ name: "get_snippet", verb: "GET", apiGroup: g, input: { id: input.int() } }); * const url = `${BASE}${q.getPath()}?${query.toSearchParams({ id: 7 })}`; * @example * // generic GET transport — the input map is `Record`, no cast * url += `?${query.toSearchParams(opts.input)}`; */ declare function toSearchParams(input: Record): URLSearchParams; declare function toSearchParams(input: Record): URLSearchParams; /** * Author an API query. Callable as `query({…})`; also carries * {@link toSearchParams} as `query.toSearchParams(input)` for GET transport. */ declare const query: typeof queryImpl & { toSearchParams: typeof toSearchParams; }; /** * Task (scheduled/background job) kind (U8) → payload key `task`. Function-like * `run[]` plus a `schedule[]` of cron-like entries. Validated against * the Xano engine's persisted shape. */ interface ScheduleDef { startsOn: string; /** Repeat frequency in seconds (defaults to 86400 when omitted). */ freq?: number; repeatEnabled?: boolean; /** End timestamp; when present, `ends.enabled` defaults to true. */ endsOn?: string; /** * Whether the end date applies. Defaults to `endsOn != null`; state it only to * represent a stored schedule that REMEMBERS an end date with the gate off — * a state the derivation alone cannot spell, and one real tasks are in. */ endsEnabled?: boolean; } interface TaskDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; docs?: string; datasource?: string; active?: boolean; tags?: string[]; schedule?: ScheduleDef[]; stack?: Statement[]; /** * Pre/post middleware attachment. Tasks have no API-Group tier — an * un-customized phase inherits straight from the workspace. Providing a phase * sets its `_customize` flag; `pre: middleware.clear()` overrides with nothing. */ middleware?: MiddlewareAttach; /** * Request-history capture. Omit to inherit from the workspace (tasks have no * container tier). A scalar: `false` off, `true` on at default depth, a number * = capture depth, `"all"` unlimited. Any value stops inheriting. See * {@link HistoryInput}. */ history?: HistoryInput; } interface ScheduleXdo { starts_on: string; repeat: { enabled: boolean; ends: { enabled: boolean; on: string; }; freq: number; }; } interface TaskXdo { name: string; description: string; docs: string; datasource: string; active: boolean; middleware: MiddlewareBlock; tag: Array<{ tag: string; }>; history: { inherit: boolean; enabled: boolean; limit: number; }; run: StackItemXdo[]; schedule: ScheduleXdo[]; } declare function encodeSchedule(def: ScheduleDef): ScheduleXdo; declare function encodeTask(def: TaskDef): TaskXdo; declare const taskKind: ObjectKind; declare function task(def: TaskDef): TaskDef; /** * Workflow test (end-to-end test) kind → payload key `workflow_test`. A named * stack with NO input and NO response: it invokes other workspace objects * (`s.function.call`, `s.task.call`, `s.api.call`, …) and asserts on the results * with `s.expect.*`. Structurally a `task` without a schedule, which is why this * file mirrors `task.ts` rather than the function envelope — there is no * `input`, `result`, `cache`, `middleware`, or `history` on this kind. * * ## The datasource is the hazard * * `datasource: ""` (the default) runs against an EMPTY datasource — what the * Xano UI labels "empty (recommended)". A non-empty value names a datasource * that the engine **clones** before running the test. Cloning a production-sized * datasource is slow enough to fail the run outright, so `""` is the only * default worth having and `"live"` warns at encode time. */ interface WorkflowTestDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; docs?: string; /** * Datasource to run against. `""` (the default) means an EMPTY datasource — * the recommended setting. Any other value names a datasource the engine * **clones** before the test runs; `"live"` warns at encode time. */ datasource?: string; /** Whether the test is enabled. Defaults to `true` (the engine's own default). */ active?: boolean; tags?: string[]; /** * The test body: runs (`s.function.call`, `s.task.call`, …) interleaved with * assertions (`s.expect.*`). A workflow test takes no input and returns no * response — assert on what the runs bind with `as`. */ stack?: Statement[]; } interface WorkflowTestXdo { name: string; description: string; docs: string; datasource: string; active: boolean; tag: Array<{ tag: string; }>; run: StackItemXdo[]; } /** Encode a `WorkflowTestDef` into the flattened importable `workflow_test` xdo. */ declare function encodeWorkflowTest(def: WorkflowTestDef): WorkflowTestXdo; declare const workflowTestKind: ObjectKind; /** * Declare an end-to-end test. Register it with `Xano.registerWorkflowTests`. * * The body is always the same shape: `.call` something and bind it with `as`, * then assert on that variable. `s.expect.*` is only meaningful here. * * ```ts * workflowTest({ * name: "signup_works", * // datasource omitted — "" is an EMPTY datasource, and cloning a real one * // before every run is slow enough to fail the run. * stack: [ * s.function.call({ fn: createUser, input: { email: "a@b.c" }, as: "created" }), * s.expect.to_equal({ expr: ref("created.status"), value: c.text("ok") }), * ], * }); * ``` */ declare function workflowTest(def: WorkflowTestDef): WorkflowTestDef; /** * Middleware kind (U8) → payload key `middleware`. Function-like * (input/run/result) plus `result_type` (merge|replace) and `exception` * (silent|rethrow|critical). Validated against the Xano engine's persisted shape. */ type ResultStrategy = "merge" | "replace"; /** * What Xano does to the request when the middleware stack **throws** (e.g. a * tripped `s.redis.ratelimit`). XanoTS passes the value through verbatim; the * Xano engine interprets it: * * - `"rethrow"` **(XanoTS's default)** — the throw aborts the request and the * authored `error`/status surfaces to the caller (a tripped `ratelimit` → * HTTP 429). The `post` chain still runs. This is what a guard-style * middleware wants, and guards are what middleware is mostly used for. * - `"silent"` — the throw is swallowed; the host continues as if the * middleware succeeded. For a guard (rate limit, auth check) this means the * guard is **not enforced** — the over-limit request goes through. Set this * only for advisory middleware (logging, metrics) that must never block. * - `"critical"` — like `"rethrow"` (same aborted request, same HTTP status) but * additionally **skips the entire `post` middleware chain**. Use it when a * failed `pre` guard should suppress post-processing (audit shaping, response * rewrites) that assumes the host ran. * * No status or logging difference between `rethrow` and `critical` — the only * distinction is whether `post` middleware runs. * * ## Why XanoTS defaults to `rethrow` and the engine does not * * The engine falls back to `silent` when the field is absent. XanoTS always * writes the field, and writes `rethrow`, so nothing here depends on the * engine's fallback — the value is explicit in the bundle either way. * * The default is different on purpose. Verified live: a middleware that throws * under `silent` returns the host's normal 200 and the guard is simply not * enforced; under `rethrow` the same middleware returns the authored error. An * author who writes a rate limiter and does not think about this field gets, by * default, a limiter that does nothing and says nothing. An inert guard is * worse than a loud one, so the safe reading is the default and the permissive * one is opt-in (issue #210). */ type ExceptionPolicy = "silent" | "rethrow" | "critical"; /** * Generic over its branded stack tuple `S`, literal response `Resp`, and * declared `Res`, so `InferResponse` can trace a response ref back to the * statement that bound it (issue #119). All default, so a bare `MiddlewareDef` * is unchanged. * * No input generic, deliberately: a middleware's declared `input` is NEVER bound * by the host request (see the field below), so typing a payload that cannot be * read would be a claim the runtime does not honor. */ interface MiddlewareDef { name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `name`; set it to keep identity across a rename or to match an existing object. */ guid?: string; description?: string; docs?: string; resultStrategy?: ResultStrategy; /** * How a throw in the stack affects the request. Defaults to `"rethrow"` — the * throw aborts the request and the authored error reaches the caller, which * is what a guard wants. Set `"silent"` for advisory middleware that must * never block. See {@link ExceptionPolicy}. */ exceptionPolicy?: ExceptionPolicy; tags?: string[]; /** * Saved UNIT TESTS — named input sets run against this object, with * assertions on the response. The same tests the Xano editor shows. * * Build assertions with the top-level `expect.*` helpers (NOT `s.expect.*`, * which builds workflow-test statements). A statement in this object's stack * can return a mock instead of running, per test, via its `mock` option. * * ⚠ A run uses an EMPTY datasource, so no `table({ seed })` row is visible to * it: every `db` read misses, and an assertion on the first row fails against * a deployment whose endpoint returns those rows over HTTP. Create what the * test needs inside the run — a `defineFunction` fixture the stack calls * first — or `mock` the read. */ tests?: TestDef[]; /** * Request-history capture. Omit to inherit from the workspace. A scalar: * `false` off, `true` on at default depth, a number = capture depth, `"all"` * unlimited. Any value stops inheriting. Middleware defaults OFF. See * {@link HistoryInput}. */ history?: HistoryInput; /** * ⚠ **Never bound.** Declaring an input here is accepted and stored (the * engine persists the field, and real workspaces carry one), but the host * request does not populate it: `inp("x")` inside a middleware stack fails at * runtime with `Unable to locate input: x` — verified live. Read the request * body with `s.util.get_all_input` instead, which hands back a * `{ type, vars }` envelope. `export()` warns if this is set. */ input?: Record; /** * The middleware's statement stack. Captured as the literal tuple `S` (via * `middleware()`'s `const` inference) so `InferResponse` can trace a response * ref back to the statement that bound it; a dynamically-built `Statement[]` * widens it and the trace degrades — declare `responseShape` there. */ stack?: S; /** What this middleware hands back (subject to {@link ResultStrategy}). * Captured as the literal `Resp` so `InferResponse` can derive its keys. */ response?: Resp; /** * Type-only: declare the response shape so `InferResponse` recovers * it exactly, overriding automatic derivation. The runtime value is ignored by * `encodeMiddleware`; only its type is read. */ responseShape?: Res; } interface MiddlewareXdo { name: string; description: string; docs: string; result_type: ResultStrategy; exception: ExceptionPolicy; history: { inherit: boolean; enabled: boolean; limit: number; }; tag: Array<{ tag: string; }>; shared_workspace: { is_shared: boolean; }; input: InputXdo[]; result: ResultItemXdo[]; run: StackItemXdo[]; test: TestXdo[]; } /** * Any middleware def, whatever its stack/response — the parameter type every * consumer that only READS a def wants. `Res` is widened to `unknown` rather * than left at the `never` default, which would reject a def that declares * `responseShape`; the same widening `encodeQuery` uses. */ type AnyMiddlewareDef$1 = MiddlewareDef; declare function encodeMiddleware(def: AnyMiddlewareDef$1): MiddlewareXdo; declare const middlewareKind: ObjectKind; declare function middlewareImpl(def: MiddlewareDef): MiddlewareDef; /** * Author a middleware object. Callable as `middleware({…})`; also carries * {@link clear} as `middleware.clear()` — the readable spelling of an explicit * empty pre/post override (`pre: middleware.clear()` ⇒ customize the phase, run * nothing, stop inheriting the parent tier). */ declare const middleware: typeof middlewareImpl & { clear: typeof clear; }; /** * Realtime message (`message`) — the invocable unit of the realtime layer, and * the bottom of `realtime_server -> channel -> message`. It is the realtime * analogue of a query: `run[]` is the SAME stack type queries, functions, tasks * and tools use, so input validation, middleware, history and the debugger all * behave identically. * * A channel owns N named message types, each with its own validated payload and * its own stack — rather than one handler switching on a convention field. * * `input` here types the message PAYLOAD. The owning channel's `input` types the * channel PATH parameters (`rooms/{room_id}`). Both reach the stack as ordinary * declared inputs, read the same way — `inp("body")` for a payload field, * `inp("room_id")` for the channel's path param: * * ```ts * const rooms = realtimeChannel({ name: "rooms/{room_id}", server, input: { room_id: input.int() } }); * realtimeMessage({ * name: "send", * channel: rooms, * input: { body: input.text() }, * stack: [s.db.add({ table: messages, data: { room_id: inp("room_id"), body: inp("body") } })], * }); * ``` * * A path param is bound ONCE at join and read from the connection thereafter — * never from the frame — so a sender cannot claim a room it did not join. The * same values reach a channel `join`/`leave` trigger's stack, and ride the * realtime session as `session.params.` (`s.realtime.get_session`). * * WHAT THE STACK RETURNS IS WHAT IS DELIVERED, and the three failure directions * are not symmetric: * * - a RESPONSE is fanned out to `deliverTo` and, for `channel`/`others`, stored * as the `conversation` transcript row — so broadcast everything a replayed * message needs to render (author name, id, timestamp); nothing else comes back * - a NULL response delivers NOTHING. This is the supported way for a handler to * veto its own message; the sender is told it was dropped * - a REJECTED PAYLOAD (the declared `input` refusing it) also delivers nothing, * and the validation detail goes only to the sender * - a CRASHED stack FAILS OPEN: the sender's ORIGINAL, UNVALIDATED payload is * broadcast to the channel unchanged, so a bug cannot black-hole a channel. * A handler that redacts, authorizes, or enriches must therefore not be the * only thing between client input and subscribers * * A message names BOTH its channel and its server: a channel path is unique only * within a server, so the path alone cannot be resolved. Passing a * `realtimeChannel()` handle supplies both. */ /** * A reference to the owning channel: a `realtimeChannel()` handle (which also * carries the server), or a bare channel path — in which case `server` must be * given alongside it. */ type RealtimeChannelRef = string | RealtimeChannelDef; /** * Who receives this handler's response: * - `channel` — everyone in the channel (the default) * - `sender` — only the sender (request/response over a socket) * - `others` — the channel minus the sender * - `explicit` — nobody implicitly; the stack chooses the recipients */ type MessageDeliverTo = "channel" | "sender" | "others" | "explicit"; /** * Generic over its input map `I` (so `InferInput` recovers the payload type), * over the branded stack tuple `S` and the literal response `Resp` (so * `InferResponse` can trace a ref back to the statement that bound it), and over * a declared `Res` (the `responseShape` override). All default, so every existing * use — a bare `RealtimeMessageDef` — works unchanged. * * This carried `I` alone until issue #119. A `Statement[]` stack is a widened * non-tuple, so no `as` binding in it was visible and every `ref()` in a * handler's response bottomed out — and with no `responseShape` field there was * no override either. That gap costs more here than on an HTTP kind: a realtime * client's whole data path is the socket (the transcript hydrates a joiner, so * there is deliberately no hydration endpoint to derive a shape from), which * makes the handler's response the ONLY type the client has. */ interface RealtimeMessageDef = Record, Res = never, Resp extends ResponseDef = ResponseDef, S extends readonly Statement[] = readonly Statement[]> { /** The message type as addressed by clients, e.g. `"place_bid"`. */ name: string; /** Explicit Xano `guid` (this object's identity). Defaults to a guid derived from `||`. */ guid?: string; /** * The owning channel — a `realtimeChannel()` handle (preferred: it carries the * server too), or a bare channel path alongside an explicit `server`. */ channel: RealtimeChannelRef; /** * The owning realtime server. Required only when `channel` is a bare path; a * channel handle already names its server, and an explicit value here must * not contradict it. */ server?: RealtimeServerRef; description?: string; /** Whether this message type is dispatchable. Defaults to `true`. */ active?: boolean; /** * Auth for THIS message type, independent of the channel's join policy — a * channel may admit anonymous clients while a specific message still requires * a token. Name an auth **table** (a `table({ auth: true })` def or its name) * and it resolves to that table's guid; `false`/omitted means no auth. */ auth?: AuthRef; /** * Who receives this handler's response. Defaults to `"channel"`. * * `"explicit"` hands recipient choice to the stack — and nothing can take it. * There is still no statement that SELECTS recipients from inside a handler, so * `"explicit"` delivers to nobody; prefer another value. * * `s.realtime.publish` is NOT the missing piece: it originates an event INTO a * channel from an ordinary stack (the push direction) and never chooses who a * handler's own response reaches. * * Only `"channel"` and `"others"` fan out, and ONLY THOSE TWO are written to * the channel's `conversation` transcript — a `"sender"` response is invisible * to every future joiner by construction. */ deliverTo?: MessageDeliverTo; /** The message PAYLOAD schema. Distinct from the channel's path parameters. */ input?: I; /** Pre/post middleware wrapping `stack` — the same block a query carries. */ middleware?: MiddlewareAttach; /** * The handler's statement stack. Captured as the literal tuple `S` (via * `realtimeMessage()`'s `const` inference) so `InferResponse` can trace a * response ref back to the branded `db.get`/`db.query` that bound it. A * dynamically-built `Statement[]` widens `S` and the trace degrades — declare * `responseShape` there. */ stack?: S; /** * The broadcast payload: a single {@link Value} or a record of named values. * Captured as the literal `Resp` so `InferResponse` can derive its keys and, * with the branded stack, trace each member. * * ⚠ For `deliverTo: "channel"`/`"others"` this payload IS the stored * transcript row, so it must carry everything a replayed message needs to * render. Typing it is what lets a client catch a dropped field at compile * time instead of losing it irrecoverably from every past message. */ response?: Resp; /** * Type-only: declare the broadcast payload's shape so `InferResponse` recovers it exactly (the always-correct override, taking * precedence over automatic derivation). Use it for responses the static walk * can't see (filters, lambdas, control-flow vars, a widened stack). The * runtime value is ignored by `encodeRealtimeMessage`; only its type is read. */ responseShape?: Res; /** * Request-history capture. Omit to inherit (channel → server → workspace). * Defaults **off** — message history is a hot path. A scalar: `false` off, * `true` on at default depth, a number = capture depth, `"all"` unlimited. */ history?: HistoryInput; /** Soft-delete flag mirroring a query's. Defaults to `false`; prefer `active` to switch a message off. */ disabled?: boolean; /** Workspace tags (stored `tag: [{tag}]`). */ tags?: string[]; } /** * Any message def, whatever its inputs/stack/response — the parameter type every * consumer that only READS a def wants (encoding, guid derivation, host * resolution). `Res` is widened to `unknown` rather than left at the `never` * default, which would reject a def that declares `responseShape`; the same * widening `encodeQuery` uses. */ type AnyRealtimeMessageDef$1 = RealtimeMessageDef, unknown>; interface RealtimeMessageXdo { name: string; description: string; active: boolean; /** The owning channel — `id` carries the resolved guid. */ channel: { id: number | string; }; /** The owning server — `id` carries the resolved guid. */ server: { id: number | string; }; /** `false` (no auth), the auth table's guid, or a raw numeric `dbo.id`. */ auth: false | number | string; deliver_to: MessageDeliverTo; input: InputXdo[]; output: unknown[]; middleware: MiddlewareBlock; run: StackItemXdo[]; result: ResultItemXdo[]; history: { inherit: boolean; enabled: boolean; limit: number; }; disabled: boolean; tag: Array<{ tag: string; }>; } /** The guid a message def resolves to — composed from its server, channel path, and name. */ declare function realtimeMessageGuid(def: AnyRealtimeMessageDef$1): string; declare function encodeRealtimeMessage(def: AnyRealtimeMessageDef$1): RealtimeMessageXdo; declare const realtimeMessageKind: ObjectKind; /** * Author a realtime message handler — a named message type on a channel, with * its own validated payload and its own stack. * * The exact input map, stack tuple, and response are preserved on the return * type, so `InferInput` types what a client SENDS and * `InferResponse` types what it RECEIVES (issue #119). */ declare function realtimeMessage, Res = never, Resp extends ResponseDef = ResponseDef, const S extends readonly Statement[] = readonly []>(def: RealtimeMessageDef): RealtimeMessageDef; /** * Workspace config kind (U8) → payload key `workspace` (singleton object, not * an array). Emits the author-provided settings subset; the engine fills the * remaining server-managed fields on import (KTD-1). Authoring shape validated * against the Xano engine's persisted workspace shape. * * Two blocks — `preferences` and `settings` — merge over a named engine default * rather than passing through, so an author can name one flag without restating * the twenty members beside it and still send complete bytes. Codegen subtracts * the same defaults on the way back out. */ /** * The three preferences the workspace settings form persists. * * `sql_columns`/`sql_names`/`internal_docs` used to be declared here and are not * keys the engine stores — a pulled workspace carries `allow_push`, * `track_performance`, and `use_internal_docs`, so the generated * `preferences: {...}` literal failed excess-property checking and the tree did * not type-check. (The form also offers `use_marketplace`, which is browser * local storage and never reaches the workspace object.) */ interface WorkspacePreferences { /** Allow this workspace to be pushed to a linked git remote. Default `false`. */ allow_push?: boolean; /** Collect per-request performance samples. Default `true`. */ track_performance?: boolean; /** Show the internal docs panel beside objects. Default `false`. */ use_internal_docs?: boolean; } /** * Workspace-tier middleware — the **terminal fallback** of the * Query → API Group → Workspace chain. Keyed by host type; each host's * `pre`/`post` is the default chain a host of that type inherits when it (and, * for queries, its API group) don't customize. Unlike the object/group tiers * there are **no `_customize` flags** here — workspace is always terminal, so * an empty list simply means "no workspace-level middleware for that host". */ interface WorkspaceMiddlewareDef { query?: MiddlewareAttach; function?: MiddlewareAttach; task?: MiddlewareAttach; tool?: MiddlewareAttach; } /** The stored 8-key workspace middleware map (`{objType}_{phase}`). */ interface WorkspaceMiddlewareXdo { function_pre: StackItemXdo[]; function_post: StackItemXdo[]; query_pre: StackItemXdo[]; query_post: StackItemXdo[]; task_pre: StackItemXdo[]; task_post: StackItemXdo[]; tool_pre: StackItemXdo[]; tool_post: StackItemXdo[]; } /** * One non-live datasource defined on the workspace. `label` is the datasource * name queries target; `color` is the editor's tint for it. */ interface WorkspaceDatasourceDef { label: string; color?: string; } /** Editor presentation for the `live` datasource, which has no `datasources[]` entry. */ interface WorkspaceDatasourceLiveDef { color?: string; show_banner?: boolean; } /** Workspace-wide defaults applied when creating new objects. */ interface WorkspaceDefaultsDef { /** Primary-key type new tables get when they don't declare one. */ db_primary_key?: "int" | "uuid"; } interface WorkspaceConfigDef { /** * OPTIONAL — omit it and the workspace inherits the name `workspace("…")` * already gave it (#228). * * There is exactly one config per workspace and the entry point names it, so * restating it here was pure duplication that every documented example got * wrong: `workspace("app").registerWorkspace(workspaceConfig({ history }))` * failed to typecheck on a field the registry already knew. Supply it only to * override, or when building a config with no `workspace("…")` above it. */ name?: string; description?: string; canonical?: string; /** * Default storage mode for tables in this workspace: `true` stores fields as * JSON under each table's `xdo` column, `false` (the default) gives them real * Postgres columns. The source of truth a table's own `use_xdo` mirrors — keep * them in sync (see {@link TableDef.useXdo}). */ use_xdo?: boolean; preferences?: WorkspacePreferences; /** * The workspace's LEGACY realtime block, carried verbatim. * * Not the realtime primitives this SDK authors — those are `realtimeServer` / * `realtimeChannel` / `realtimeMessage`, each its own object with its own * canonical. This is the older workspace-level block that predates them, and * XanoTS models none of its members: whatever the engine stored is round- * tripped as-is, so a pulled workspace keeps it without this SDK taking a * position on a shape it does not author. * * Omit it. It exists so the round trip is honest, not to be authored. */ realtime?: Record; /** * The workspace's public-documentation block, carried verbatim — the token and * whitelist gating the hosted docs. Server-shaped; omit unless round-tripping * a pulled workspace. */ documentation?: Record; /** Whether the workspace publishes a Swagger/OpenAPI spec. */ swagger?: boolean; /** * Workspace-level default middleware chains (the terminal fallback tier). * Emitted only when provided — a workspace config without this field leaves * the engine's existing workspace middleware untouched on import (consistent * with this kind's author-provided-subset contract). * * WHOLESALE, not partial: once set, the full 8-key `{host}_{phase}` map is * emitted and any host/phase you don't list is emitted empty. The workspace * tier has no per-key `_customize` flag, so an empty list means "no middleware" * — deploying `{ query: { pre: [x] } }` **clears** any UI-configured * `function_*`/`task_*`/`tool_*`/`query_post` middleware. Declare every * workspace-level chain you want to keep. Branch-tier middleware is not * modeled; the engine falls through absent branch middleware to this tier. */ middleware?: WorkspaceMiddlewareDef; /** * Workspace-level default request history (the terminal fallback tier). A * scalar per object type; every type an object of that kind inherits when it * (and, for queries/tools, its container) doesn't customize. Unlike the * object/container tiers there is **no `inherit` flag** — the workspace is * always terminal. * * WHOLESALE, not partial: once set, the full 14-key `{objType}_enabled`/ * `{objType}_limit` map is emitted and any type you don't list falls back to * its engine default (`enabled` per the kind rule, `limit:100`) — deploying * `{ query: 100 }` overwrites any UI-configured `function_*`/`task_*`/… values. * Declare every workspace-level default you want to keep. Branch-tier history * is not modeled; the engine falls through absent branch history to this tier. */ history?: WorkspaceHistoryDef; /** * Workspace **environment variables** — the secrets/config a tenant reads at * request time with `env("NAME")` (→ `$env.NAME`). Authored as an ergonomic * name→value map; XanoTS encodes it to the engine's persisted `env[]` array * of `{ name, value, market_item }`. Order is preserved. * * VALUES ARE SECRETS. Prefer sourcing them from the deploy environment rather * than committing literals — `env: { STRIPE_KEY: process.env.STRIPE_KEY! }` — * and don't commit a compiled bundle that contains real values. * * WRITE SEMANTICS DIFFER BY COMMAND, and the difference is not cosmetic: * * - `deploy` (ephemeral) REPLACES the tenant's env with this map — the * workspace object is restored wholesale on import, so a key absent here is * dropped, and an empty value is written as an empty string. * - `release` (merge) is ADD-ONLY. It creates keys that do not yet exist and * does NOT update or remove ones that do. Changing a value in code and * releasing leaves the live value as it was (issue #164). * - `release --replace` replaces, but it rebuilds the whole workspace to do it. * * So a value you need to CHANGE on an instance workspace cannot be changed by * an ordinary `release` today. Omit the field entirely to leave existing env * untouched on every path. * * This is the SETTER — the {@link env} value helper is the READER. Distinct * from the built-in request-context vars (`sys.*`). */ env?: Record; /** * Workspace settings (AI provider config, agent visibility). Modeled as an * opaque map — declare only the members you want to change; the rest are * filled from the engine's own default scaffold on export. */ settings?: Record; /** * Allow tables to carry custom SQL names distinct from their workspace names. * Emitted only when set — omit to leave the tenant's current setting alone. */ use_custom_names?: boolean; /** * Workspace-wide defaults for newly created objects. Emitted only when set, * so omitting it leaves the tenant's configured defaults untouched. */ defaults?: WorkspaceDefaultsDef; /** * The workspace's non-live datasources. WHOLESALE, not partial: once set, the * full list is emitted and any datasource you don't list is dropped from the * tenant. Omit the field to leave the existing datasources alone. */ datasources?: WorkspaceDatasourceDef[]; /** Editor presentation for the `live` datasource. Emitted only when set. */ datasource_live?: WorkspaceDatasourceLiveDef; } /** One persisted workspace env var (engine `env[]` element). */ interface WorkspaceEnvXdo { name: string; value: string; /** Marketplace-provenance links; always empty for author-declared vars. */ market_item: never[]; } interface WorkspaceConfigXdo { name: string; description: string; canonical: string; use_xdo: boolean; preferences: WorkspacePreferences; realtime: Record; documentation: Record; swagger: boolean; /** Present only when the author sets `middleware` (author-provided subset). */ middleware?: WorkspaceMiddlewareXdo; /** Present only when the author sets `history` (author-provided subset). */ history?: WorkspaceHistoryXdo; env: WorkspaceEnvXdo[]; settings: Record; /** * The four blocks below are `?=`-optional in the engine's workspace schema and * are emitted **by presence only** — written when the author sets them, absent * when they don't. That is deliberate: omitting `datasources` leaves a tenant's * datasources alone, where writing `[]` would clear them. * * Codegen going the OTHER way does compare against the default, because the * engine materializes all four on save and carrying them into a pulled tree is * ten lines nobody wrote — see `WORKSPACE_DEFAULTED_KEYS`, which verification * reads too so the elision is an equivalence rather than a loss. */ use_custom_names?: boolean; defaults?: WorkspaceDefaultsDef; datasources?: WorkspaceDatasourceDef[]; datasource_live?: WorkspaceDatasourceLiveDef; } declare function encodeWorkspaceConfig(def: WorkspaceConfigDef): WorkspaceConfigXdo; declare const workspaceKind: ObjectKind; declare function workspaceConfig(def: WorkspaceConfigDef): WorkspaceConfigDef; /** * Agent-grounding manifest (DX follow-up). A machine-readable description of the * entire xanots authoring surface — object kinds, statement catalog (with field * schemas for the declarative statements), value constructors, and tag catalog — * plus a human/LLM-readable `llms.txt` renderer. * * Everything here is DERIVED from the SDK's own sources of truth (the surface * catalog, the generated statement specs, the kind registry, the value/tag * primitives), so the manifest can never drift from what the SDK can actually * emit. Regenerate the committed `manifest.json` / `llms.txt` with * `npm run manifest`; the manifest test fails if they fall out of sync. */ /** A statement field, flattened from its generated spec rule. */ interface ManifestField { name: string; /** `string` (a plain string arg), `value` (a tagged `Value`), or `comparison`. */ type: StatementSpec["rules"][number]["type"]; optional: boolean; default?: string; /** * The field's closed set of legal values, where the engine declares one. Both * a bare literal and the `c.text(...)` spelling are accepted, and a constant * outside the set is rejected at authoring time. */ enum?: string[]; } /** One statement authoring surface. */ interface ManifestStatement { /** Canonical surface key (the engine schema basename), e.g. `array.filter`. */ surface: string; /** Stored `mvp:` name emitted into the bundle. */ storedName: string; /** Dotted accessor under `s`, e.g. `array.filter` → `s.array.filter`. */ sPath: string; /** Whether the stored name has a registered factory (authorable today). */ registered: boolean; /** True when generated from the engine schema (carries a field schema). */ declarative: boolean; /** Whether the statement emits an `output` envelope. Declarative only. */ output?: boolean; /** * What the statement's `as:` output variable holds — so the manifest answers * "what does this bind?" without falling back to prose. Curated (see * `STATEMENT_RESULTS`); present only for statements whose result is stable and * documented. Analogous to {@link ManifestFilter.result}, but structured: `name` * is the binding field (always `as` today, carried explicitly so the descriptor * is self-describing for machine consumers), `type` its value type, `note` an * optional caveat. */ result?: { name: string; type: string; note?: string; }; /** Field schema — present for declarative statements. */ fields?: ManifestField[]; /** * An older paradigm the SDK still SUPPORTS but no longer wants authored — same * contract as {@link ManifestValue.legacy}: withheld from the per-namespace * catalog an agent picks from, and named-only in the legacy index (`llms/legacy.md`). */ legacy?: boolean; } /** * One authoring factory within a kind that fans out into several distinct * root factories sharing a single encoder/payload key — today only `trigger` * (six `obj_type`s, one shared stored envelope). Each sub-kind is a first-class * root factory in its own right; the manifest lists them individually so agents * treat them like every other primitive. */ interface ManifestSubKind { /** Root authoring factory export, e.g. `tableTrigger`. */ authorFactory: string; /** The stored `obj_type` this factory produces, e.g. `database`. */ objType: string; /** Rich "what this primitive does" descriptor, in the style of a top-level kind. */ description: string; /** Built and registered, but withheld from the published catalog — see {@link ManifestKind.unpublished}. */ unpublished?: boolean; /** * An older paradigm the SDK still SUPPORTS but no longer wants authored — same * contract as {@link ManifestValue.legacy}: withheld from the sub-kind catalog * and the trigger prose, and named-only in the legacy index (`llms/legacy.md`). * * Distinct from `unpublished`, which withholds a factory that is not ready. * A legacy factory is fully ready and fully supported; it is the *paradigm* * that has been superseded. */ legacy?: boolean; } /** One top-level object kind. */ interface ManifestKind { /** Stable kind name, e.g. `function`. */ kind: string; /** `packageExport` payload key, e.g. `function`, `dbo`. */ payloadKey: string; /** Authoring factory export, e.g. `defineFunction`, `table`. */ authorFactory: string; /** One-line "what this primitive does" descriptor, surfaced in `llms.txt`. */ description: string; /** `Xano` registration method, e.g. `registerFunctions`. */ registerMethod: string; /** Whether the kind has a registered encoder (implemented today). */ registered: boolean; /** * Distinct root factories that all persist under this kind's encoder/payload * key. When present, the `## Object kinds` catalog lists each sub-kind as its * own root-level entry instead of the grouped `authorFactory` line. Only * `trigger` uses this today. */ subKinds?: ManifestSubKind[]; /** * Built and registered, but deliberately withheld from the published agent * surface — kept out of the emitted manifest, out of `llms.txt`, and out of * the coverage numerator. * * This is a *release* gate, not a completeness gate. The kind still has a * descriptor here so the "descriptors match the live kind registry" drift * guard keeps covering it; it just does not ship in the catalog yet. Flip the * flag off to publish — nothing else needs to change. */ unpublished?: boolean; } /** A value constructor / helper. */ interface ManifestValue { name: string; signature: string; description: string; /** * An older paradigm the SDK still SUPPORTS but no longer wants authored. * * Kept out of the `## Values` catalog — the list an agent picks from when it * builds — and named-only in the legacy index (`llms/legacy.md`). * That split is the whole point: hiding it entirely is worse than useless, * because a pulled workspace can legitimately contain one, and an agent that * has never heard of it will "fix" what it does not recognize. Naming it * without a signature says "you will see this; do not reach for it." */ legacy?: boolean; } /** One value-pipeline filter (`fl.`). */ interface ManifestFilter { /** Filter name, as stored in the value `filters[]` chain. */ name: string; /** Dotted accessor: `fl.`. */ fl: string; /** * Whether the SDK knows this filter's exact signature. * * True for every filter with a spec — INCLUDING the zero-argument ones. A * filter that takes nothing has nothing to type, so `fl.abs()` is as fully * typed as `fl.add(value)`; counting it as untyped reported a 131/225 coverage * gap that never existed. Whether a filter takes arguments is `args`, which is * a different question and the one the catalog renders from. */ typed: boolean; /** * Takes arguments the catalog declares no list for, so there is no arity to * enforce and no signature to print. Distinct from a filter with neither * `args` nor `variadic`, which takes NOTHING. */ variadic?: boolean; /** * Named, typed args (richly-specified filters only). `enum` carries the exact * accepted spellings where the arg has a closed set — printed in place of the * bare word "enum", which told a reader nothing (#198). */ args?: Array<{ name: string; type: string; optional?: boolean; enum?: string[]; }>; /** Result type (richly-specified filters only). */ result?: string; /** Group, e.g. `timestamp`, `vector` (richly-specified filters only). */ group?: string; /** One-line description, when known. */ description?: string; } /** One field-catalog type (`f.` / `input.`). */ interface ManifestFieldType { /** Authoring constructor name under `f` / `input`, e.g. `text`, `tableRef`. */ name: string; /** The stored type string emitted into the schema, e.g. `epochms`, `blob_img`. */ stored: string; /** Valid bind-method names for this type (empty = none; `{name,arg}` escape hatch only). */ methods: string[]; /** Present and true when the type exists under `input.` only, with no `f.` column form. */ inputOnly?: boolean; } /** One CLI flag, with the effect it has. */ interface ManifestCliFlag { flag: string; description: string; } /** * One CLI command, DERIVED from the `COMMANDS`/`FLAGS` registry in * `src/emit/commands.ts` — the same table that renders `--help` and generates the * shell completions. * * It used to be hand-maintained here, and it drifted: `init` and `validate` were * missing outright, `deploy` was short six flags, and the `--static` description * claimed the frontend always lands on the parent workspace (true only under * `--dest sandbox`). Deriving it makes that class of drift unrepresentable, which * matters more now that `llms.txt` no longer documents the CLI — this array and * `--help` are the only two surfaces, and they are now one source. */ interface ManifestCliCommand { /** The invocation verb, e.g. `deploy`. */ command: string; /** Positional argument grammar, when the command takes one. */ args?: string; flags?: ManifestCliFlag[]; description: string; } interface Manifest { name: string; version: string; description: string; coverage: { /** * Counted over the ENGINE's object-kind catalog, where every trigger type is * its own kind. `unmodeled` names the shortfall so the denominator is * inspectable rather than a bare ratio. */ objectKinds: { implemented: number; total: number; unmodeled: { kind: string; absence: string; }[]; }; statements: { implemented: number; total: number; }; filters: { typed: number; total: number; }; }; values: { constructors: ManifestValue[]; tags: readonly string[]; }; objectKinds: ManifestKind[]; fieldTypes: ManifestFieldType[]; statements: ManifestStatement[]; filters: ManifestFilter[]; /** The CLI command surface (compile/export/deploy/auth/lock). Hand-maintained. */ cli: ManifestCliCommand[]; /** * Flags every command accepts. They appear in no per-command `flags` list, so * without this they would be undiscoverable from the manifest alone — an agent * reading `cli` would conclude `--json` does not exist. */ cliGlobalFlags: ManifestCliFlag[]; } /** Total engine object kinds — the size of the catalog above, never a literal. */ declare const TOTAL_OBJECT_KINDS: number; /** Build the full authoring manifest from the SDK's sources of truth. */ declare function buildManifest(opts?: { version?: string; }): Manifest; /** Repo-relative path of the always-loaded grounding router. */ declare const LLMS_TXT = "llms.txt"; /** * Every committed grounding artifact, as repo-relative path → content. * * The map is the seam the grounding surface is split across: the router at * {@link LLMS_TXT} is what an agent always loads, and later entries are topic * files it reads only when the surface they cover is in play. Callers write * whatever the map holds rather than naming files individually, so a new * artifact is picked up by the writer and by the drift guard without either * needing to learn about it. * * Pure function of the manifest, like every other artifact here — regenerating * is deterministic and the committed files are asserted against a fresh render. */ declare function renderDocs(m: Manifest): Map; /** The router alone, for callers that want it without the rest of the set. */ declare function renderLlmsTxt(m: Manifest): string; /** The only lock format version this build reads or writes. */ declare const LOCK_VERSION = 1; /** Fixed key for the workspace's own `canonical` (never keyed by workspace name). */ declare const WORKSPACE_KEY = "workspace"; /** Resolve a user-facing kind name or payloadKey to the lock's payloadKey. */ declare function resolvePayloadKey(kindOrPayloadKey: string): string; /** Build the lock key for an object — the same seed `deriveGuid` hashes. */ declare function lockKey(payloadKey: string, name: string): string; /** One locked identity. At least one of `guid`/`canonical` is present. */ interface LockEntry { guid?: string; canonical?: string; } interface LockFile { version: typeof LOCK_VERSION; objects: Record; } /** A fresh, empty lock model. */ declare function emptyLock(): LockFile; /** * Parse + strictly validate lock file text. Every failure is a hard error * (R11) — the caller must never fall back to an unlocked export when a lock * file exists but is broken. */ declare function parseLock(text: string, path?: string): LockFile; /** * Re-run the model-level invariants (key shape, entry shape, duplicate * guid/canonical values) on an in-memory lock. The CLI calls this on the * merged lock BEFORE writing it, so an export can never persist a lock that * the next run's `parseLock` would reject (e.g. two api groups given the same * explicit `canonical` in code). */ declare function validateLockModel(lock: LockFile, label: string): void; /** Serialize with sorted keys (stable diffs) and a trailing newline. */ declare function serializeLock(lock: LockFile): string; /** * Mint a canonical: 8 chars of websafe base64 from crypto randomness, the * engine's canonical format. Random (NOT name-derived — a * canonical is a public URL token; deriving it from names would make API paths * guessable). This is the repo's only intentional randomness; determinism is * preserved because a minted value is immediately frozen in the lock. */ declare function mintCanonical(): string; /** * The channel between the CLI (or a programmatic caller) and `Xano.export()`. * The caller creates it from the validated on-disk lock; `export()` MUTATES it, * filling `observed` with every identity the bundle actually emitted. The * caller then merges `observed` back into the lock via {@link mergeObserved}. * * `observed` (not the global override store) is the write-back source so a * process exporting multiple workspaces cannot leak one workspace's entries * into another's lock file. */ interface LockExportContext { /** The validated lock the export runs against (empty for a first lock). */ lock: LockFile; /** Filled by `export()`: lock key → identity actually emitted in the bundle. */ observed: Record; } /** Create the context `Xano.export({ lock })` fills. */ declare function createLockContext(lock?: LockFile): LockExportContext; /** * Record one emitted identity into `ctx.observed`, hard-erroring on an * explicit-vs-lock guid split (R3): within one bundle every reference resolves * through the seeded lock, so an object whose payload guid disagrees with its * lock entry would ship a bundle where references point at a guid the target * no longer carries — never emit that silently. */ declare function recordObserved(ctx: LockExportContext, key: string, identity: LockEntry): void; /** Result of `renameLockEntry`. */ interface RenameResult { lock: LockFile; /** A fresh name-derived entry the export already appended for the new name, replaced by the move. */ discardedNewcomer?: LockEntry; } /** * Move a lock entry to a new name keeping its identity values (R7), so the * next export emits the ORIGINAL guid under the new name and the engine * renames in place. * * The normal sequence is rename-in-code → export (which warns about the * orphan and appends a fresh entry for the new name) → `lock rename`. So an * existing entry under the new key is expected — but ONLY when it is the * fresh name-derivation the export just appended. That newcomer is replaced * (its guid was never the object's real identity; a canonical minted for it is * discarded and reported). Any OTHER entry under the new key is a real pinned * identity and the rename refuses to clobber it. */ declare function renameLockEntry(lock: LockFile, payloadKey: string, oldName: string, newName: string): RenameResult; /** One entry-level change `adoptFromBundle` would apply. */ interface AdoptChange { key: string; before: LockEntry; after: LockEntry; } /** Result of `adoptFromBundle`. */ interface AdoptResult { lock: LockFile; /** Keys newly added to the lock. */ added: string[]; /** Existing entries whose values the bundle overwrites. */ changed: AdoptChange[]; /** True when at least one adopted object carried a canonical. */ canonicalsSeen: boolean; /** Number of `vault` payload entries in the source bundle (secrets!). */ vaultCount: number; } /** * Seed/update the lock from a live engine `packageExport` bundle (R9) — * capturing the workspace's random guids and canonicals by `(type, name)` so * an existing workspace can be adopted into code without a delete+create sync. * * Adopted values win over existing lock values field-by-field; a lock-held * canonical is KEPT when the bundle has none for that object (the engine's * standard partial export strips canonicals — erasing ours would lose minted * values). Two same-named objects in one section (e.g. a GET/POST query verb * pair) are a hard error: the lock keys by `(type, name)` and silently keeping * one of the two would weld the wrong identity onto both. */ declare function adoptFromBundle(lock: LockFile, bundle: unknown, bundlePath: string): AdoptResult; /** Result of folding an export's observed identities back into the lock. */ interface MergeResult { lock: LockFile; /** Lock keys no exported object matched — candidates for `lock rename`/`prune`. */ orphans: string[]; /** Orphans dropped because their GUID re-appeared under a live key. */ dropped: string[]; /** Orphans kept, but whose canonical was ceded to a live entry that now emits it. */ cededCanonicals: string[]; } /** * Merge observed identities into the lock (pure — returns a new model). * * Observed values win field-by-field (an explicit in-code value updates the * recorded one, per R2). Entries nothing matched are kept as orphans and * reported — renames are never guessed (R6) — with one exception: an orphan * whose GUID now belongs to a LIVE entry is dropped. That happens when a * rename is reverted after a `lock rename` fix-up (the old name re-derives the * pinned guid): keeping the orphan would wedge the lock on its own * duplicate-identity validation forever. * * A canonical-only match is NOT grounds for dropping: the orphan's guid may be * a real adopted engine identity, and deleting it would delete+create the * server object on the next rename fix-up. Instead the orphan stays (with its * guid) and only its canonical is ceded to the live entry that now emits it — * which also keeps the merged lock free of duplicate canonical values. */ declare function mergeObserved(lock: LockFile, observed: Record): MergeResult; /** Bundle `type` (workspace | schema | content | share). */ type BundleType = "workspace" | "schema" | "content" | "share"; /** * Canonical payload key order, matching the engine's partial-export order. Each kind's * encoded objects land under its key; unsupported sections stay empty arrays so * the bundle shape matches the engine's full export. */ declare const PAYLOAD_ARRAY_KEYS: readonly ["dbo", "addon", "function", "middleware", "trigger", "task", "query", "tool", "toolset", "app", "realtime_server", "channel", "message", "microservice", "vault", "market_item", "run_install", "action_package_install", "env", "workflow_test", "service", "branch"]; type PayloadArrayKey = (typeof PAYLOAD_ARRAY_KEYS)[number]; interface BundlePayload { partial: boolean; workspace: Record; [key: string]: unknown; } interface Bundle { app: string; version: string; type: BundleType; payload: BundlePayload; sig: string; } /** * Iterative, like the guards' walkers (issue #19), and for the same reason: what * is being crossed here is the author's own structure — a deep expression tree, * or a `raw()` envelope carrying whatever the engine handed back on a pull — and * a recursive encoder turns that into a bare `RangeError` at `export()`. Depth * belongs to the heap, not to whatever stack the platform happened to give the * main thread (Linux's default is roughly half of macOS's, which is why this * only ever failed in CI). * * Output is assembled by pushing frames back-to-front so they pop in document * order — a signature is a byte-for-byte comparison against what the engine * recomputes, so the emitted order is not free to change. */ declare function phpJsonEncode(value: unknown): string; /** Replicates the engine's signature routine: sort top-level keys, encode, sha1, websafe base64. */ declare function calcSignatureJson(exportObj: Record): string; interface BuildBundleArgs { type?: BundleType; workspace?: Record; /** Encoded objects keyed by their payload key (e.g. function, dbo, query). */ sections: Partial>; /** When exporting under a lock: lets guid-collision errors name the lock entry. */ lock?: LockFile; } /** Assemble a signed `packageExport` bundle from encoded sections. */ declare function buildBundle(args: BuildBundleArgs): Bundle; /** * The register methods take the WIDEST instantiation of each def type, not the * bare name (issue #208). * * A def's `Res` parameter surfaces as `responseShape?: Res` and defaults to * `never`, so the bare `FunctionDef` — the spelling the issue proposed — * accepts only defs that declare NO `responseShape`: the moment an author adds * one, `responseShape?: MyType` stops being assignable to `responseShape?: * never` and the registration they were already making stops compiling. Pinning * `Res` to `unknown` (and every other parameter to its own constraint) accepts * every instantiation while still rejecting an object that is not that kind of * def at all, which is what #208 asked for. */ type AnyInputs = Record; type AnyStack = readonly Statement[]; /** Any function def, whatever its input/response/stack parameters resolved to. */ type AnyFunctionDef = FunctionDef; type AnyQueryDef = QueryDef; type AnyToolDef = ToolDef; type AnyTriggerDef = TriggerDef; type AnyMiddlewareDef = MiddlewareDef; type AnyRealtimeMessageDef = RealtimeMessageDef; type AnyRealtimeChannelDef = RealtimeChannelDef; type AnyAddonDef = AddonDef; type AnyTableDef = TableDef; /** * Cross-realm brand. `instanceof Xano` breaks when XanoTS is loaded by two * different module loaders (e.g. the CLI under bare Node while a `.ts` entry is * loaded through tsx), since each loader has its own `Xano` constructor. A * `Symbol.for` key is shared through the global registry, so a structural * brand check survives that split. */ declare const XANO_BRAND: unique symbol; declare class Xano { /** @internal cross-realm identity brand — see {@link Xano.isXano}. */ readonly [XANO_BRAND] = true; /** Encoded objects keyed by `packageExport` payload key. */ private readonly sections; /** Table defs, encoded lazily at {@link export} so each can inherit the workspace `use_xdo`. */ private readonly tableDefs; private workspaceConfig; private bundleType; /** True for any `Xano` registry, even one created by a different module instance. */ static isXano(value: unknown): value is Xano; /** Encode one def and stamp its deterministic guid (the engine's sync/reference anchor). */ private encodeOne; /** Register one or more authoring defs of a given kind. */ register(kindName: string, defOrDefs: unknown): this; /** * Every def object handed to {@link register}, by identity, so the SAME one * registered twice is caught at the call that did it (issue #54). * * Identity, not structure. Two separately constructed defs sharing a name are * a genuine collision, and the guid check at export already diagnoses that * correctly — it explains that identity derives from `(type, name)` and names * both objects. This guard must not shadow it. */ private readonly registered; /** * Refuse the same def object twice. * * The failure this front-runs: a helper registers a def on your behalf and * you then register it again yourself — `registerAuth(ws).registerTables([userTable])` * is the reported shape. Nothing objected at the second call; it surfaced much * later at `emitBundle` as `Duplicate object guid (efb55…) shared by "dbo/user" * and "dbo/user"`, a message that names a guid and the same label twice and * points at neither line. */ private assertNotAlreadyRegistered; /** * Register the workspace settings object (singleton). * * A config with no `name` inherits the one already on the registry — which is * what `workspace("my-app")` set — so the natural chain * `workspace("my-app").registerWorkspace(workspaceConfig({ history }))` no * longer makes an author restate a name this registry has held since its * first call (#228). An explicit `name` still wins, and a rename this way is * a rename of the workspace. */ registerWorkspace(def: WorkspaceConfigDef): this; /** Set the bundle `type` (defaults to "workspace"). */ setBundleType(type: BundleType): this; /** * The registered table defs (with their authored `seed`), for the Node deploy * path to build `content/` seed entries. Deliberately NOT reached from * `export()` — seed values are resolved only in the deploy pipeline, so they * never enter the browser-safe bundle. See {@link import("./seed.js")}. */ tables(): readonly TableDef[]; registerFunctions(defs: AnyFunctionDef[]): this; registerTriggers(defs: AnyTriggerDef[]): this; registerTools(defs: AnyToolDef[]): this; registerMcpServers(defs: McpServerDef[]): this; registerAgents(defs: AgentDef[]): this; registerTables(defs: AnyTableDef[]): this; registerQueries(defs: AnyQueryDef[]): this; registerApiGroups(defs: ApiGroupDef[]): this; registerTasks(defs: TaskDef[]): this; registerMiddleware(defs: AnyMiddlewareDef[]): this; registerAddons(defs: AnyAddonDef[]): this; registerMicroservices(defs: MicroserviceDef[]): this; registerRealtimeServers(defs: RealtimeServerDef[]): this; registerRealtimeChannels(defs: AnyRealtimeChannelDef[]): this; registerRealtimeMessages(defs: AnyRealtimeMessageDef[]): this; registerWorkflowTests(defs: WorkflowTestDef[]): this; /** * Assemble the signed aggregate `packageExport` bundle. * * With `options.lock` (a {@link LockExportContext}), the export participates * in identity locking: empty api-group/toolset canonicals are filled from the * lock — minted fresh on first sight — and every identity the bundle emits is * reported back through `ctx.observed` (MUTATING the passed context), which * the caller merges into the lock file. All lock work happens before the * bundle is signed. Without options the output is byte-identical to before. * * With `options.strict`, every build WARNING fails the export instead of * printing — the shapes that deploy clean and then lose data or return the * wrong rows. Nothing about the emitted bundle changes; it either exports or * it does not (issue #15). */ export(options?: { lock?: LockExportContext; strict?: boolean; }): Bundle; /** * Cross-check every query's resolved `auth` against the registered auth tables. * * `resolveAuth` (in `encodeQuery`) turns a table ref into a guid with no * registry visibility, so a bare-name typo produces a valid-looking guid that * only fails at deploy with an opaque engine error. Here we have the registry, * so we catch it at export and name the offending query. * * A registered table that is not `table({ auth: true })` only WARNS: the * engine reads that flag nowhere at request time (it compares the token's * `dbo` to the endpoint's by name, and mints tokens for any table by name), so * refusing the combination blocked a real workspace's round trip. A numeric * `auth` (raw `dbo.id` escape hatch) references a table by id xanots never * sees, so it's left as-is; `false` is a public endpoint. A table that pins an * explicit `guid` referenced by bare name lands in the "not registered" branch * (its name-derived guid diverges from the pinned one) — pass the def instead. */ private validateQueryAuth; /** * Warn about an `auth()`-keyed middleware **directly attached** to a host where * `auth()` may resolve to `null` (issue #81). * * The footgun: a rate limiter keyed by `auth("id")` is the canonical middleware, * but attach it to a host with no authenticated caller and `auth()` silently * resolves to `null` — every caller collapses into one shared bucket, with no * signal at author, export, or runtime. This surfaces it at export, where the * middleware registry is known (an attachment entry only carries the target's * guid; we resolve it back to the encoded `run` to inspect for `auth()`). * * It **warns, never throws** — a bare `auth()` reference is not proof of a * collapse (an IP-disambiguated key or a personalize-if-logged-in middleware * uses `auth()` where `null` is fine), so blocking the export would produce * false positives on legitimate use. The warning names the host and reason so * the author can confirm intent, vary the key, or move to an authenticated host. * * Scope: the host's EFFECTIVE chain, across all three tiers (issue #43). The * guard used to see only a host's own `middleware.pre`/`post`, so an author who * DRY'd a per-user limiter up to `apiGroup({ middleware })` or to the workspace * tier silently reintroduced the shared bucket — the same collapse, attached * one level up, with the warning switched off. Since the limiter is more * naturally written once at the group than repeated on every endpoint, the * tier that most needed the warning was the one that never got it. * * The cascade is the engine's own: a phase whose `_customize` flag is set is * answered by that tier (including `clear()`, which customizes with an empty * list and therefore stops inheritance); an un-customized phase falls through * to the API group, then to the workspace's `{objType}_{phase}` map. A query * that clears a phase is NOT warned about a group middleware it does not run. * * An authenticated `query` (its own `auth` table set) resolves an identity, so * it is skipped at every tier. */ private validateMiddlewareAuth; /** * Lock participation (see {@link export}): canonical fill + identity report. * Runs after all sections are assembled and before signing. */ private applyLock; } /** * Convenience entry point — the natural name for "make a workspace." * `workspace("my-app")` is exactly `new Xano().registerWorkspace({ name: * "my-app" })`, returning the chainable {@link Xano} registry. Continue with the * per-kind `register*` methods and finish with `.export()`: * * ```ts * export default workspace("my-app") * .registerTables([users]) * .registerQueries([listUsers]); * ``` * * Authoring is functional/declarative: there is **no** callback-builder form — * you pass typed def-objects (`table({...})`, `query({...})`, * `defineFunction({...})`) to the `register*` methods, not a `w => {...}` closure. */ declare function workspace(name: string): Xano; /** * Encode a single def to its pretty-printed JSON envelope, dispatching on the * def's kind so each is encoded correctly: * - a `query()` (has `verb`) → the query envelope (`input`/`result`/`run`), * - a `table()` (has `schema`) → the `dbo` payload (its real columns/indexes), * - anything else → a `function` envelope (the historical default). * * Before this dispatch, `emit()` force-compiled every def as a function, so * `emit(myTable)` produced a misleading function-shaped envelope with no schema * (it looked like the table had no columns). A `table()` still needs * {@link emitBundle} + registration for its guid/lock wiring — this single-def * form is a sanity-check view of the encoding, not a substitute for export. */ declare function emit(def: FunctionDef | QueryDef | TableDef, opts?: { indent?: number; }): string; /** Pretty-print an already-built bundle (the CLI's lock path builds it itself). */ declare function serializeBundle(bundle: Bundle, opts?: { indent?: number; }): string; /** * Pretty-print the aggregate `packageExport` bundle from a `Xano` registry. * * `opts.strict` fails the build on any warning instead of printing it — the * shapes that export clean and then lose data or return the wrong rows (a * `bulk.update` zero-filling omitted columns, an `ignoreEmpty` that drops its * predicate). Worth setting in CI and in any script whose output nobody reads. */ declare function emitBundle(xano: Xano, opts?: { indent?: number; strict?: boolean; }): string; /** * The cross-realm lock override store — how a `xano.lock` reaches `deriveGuid`. * * Reference guids are baked at AUTHORING time, not export time: statement * factories (`s.function.run`, `s.db.get`, …) and even field defs * (`f.tableRef`) call `resolveRef` → `deriveGuid` the moment the workspace * module is evaluated, and some embed the guid inside strings (`dbo=` * method args, auth-token const values). An export-time payload rewrite would * have to chase those string-embedded forms — overriding at the `deriveGuid` * choke point instead makes reference and target agree everywhere by * construction, with zero changes at call sites. * * That yields the seeding contract (R12): **seed once per process, BEFORE any * def module is evaluated.** Node's module cache means seeding after defs have * loaded is a silent no-op for already-baked references. The CLI honors this * automatically (it seeds before importing the workspace entry); programmatic * users must call `seedLockOverrides` before importing their defs. * `resetLockOverrides` exists for tests (reset-then-seed per run). * * The store lives on `globalThis` under a `Symbol.for` key because the CLI and * the workspace entry can load xanots in different module realms (the * tsx-loader split — same reason `Xano.isXano` brands with `Symbol.for`). Every * accessor goes through a fresh `globalThis` lookup, never a module-local * reference, so both realms see one store. */ /** * Seed the override store from a validated lock. Replaces any previous seed — * the contract is one workspace per process (see module doc), so the CLI does * reset-then-seed per run and multiple concurrent workspaces are out of scope. */ declare function seedLockOverrides(lock: LockFile): void; /** Clear the store (tests; a process about to seed a different workspace). */ declare function resetLockOverrides(): void; /** True when a lock has been seeded in this process. */ declare function isLockSeeded(): boolean; /** The locked guid for a `payloadKey:name` key, if the lock pins one. */ declare function getLockedGuid(key: string): string | undefined; /** The locked canonical for a lock key, if the lock pins one. */ declare function getLockedCanonical(key: string): string | undefined; export { encodeObject as $, type AdoptChange as A, type Bundle as B, encodeAddon as C, encodeAgent as D, encodeApiGroup as E, type FunctionDef as F, GENERATED_STATEMENT_NAMES as G, encodeColumn as H, encodeContainerHistory as I, encodeFromSpec as J, encodeHistory as K, LLMS_TXT as L, type Manifest as M, encodeIndex as N, type ObjectKind as O, PAYLOAD_ARRAY_KEYS as P, QUERY_EXPRESSION_FILTERS as Q, type RenameResult as R, type StatementSpec as S, TOTAL_OBJECT_KINDS as T, encodeMcpServer as U, VECTOR_FILTERS as V, WORKSPACE_KEY as W, encodeMicroservice as X, encodeMiddleware as Y, encodeMiddlewareEntry as Z, encodeMiddlewareList as _, type AdoptResult as a, type AgentDef as a$, encodeQuery as a0, encodeRealtimeChannel as a1, encodeRealtimeMessage as a2, encodeRealtimeServer as a3, encodeSchedule as a4, encodeTable as a5, encodeTask as a6, encodeTool as a7, encodeToolRefs as a8, encodeToolsetBase as a9, registerKind as aA, registerSpec as aB, registeredKinds as aC, renameLockEntry as aD, renderDocs as aE, renderLlmsTxt as aF, resolveAuthRef as aG, resolvePayloadKey as aH, resolveRealtimeServerCanonical as aI, resolveToolsetCanonical as aJ, serializeBundle as aK, serializeLock as aL, tableKind as aM, taskKind as aN, toolKind as aO, triggerKind as aP, validateLockModel as aQ, workflowTestKind as aR, workspaceKind as aS, type LambdaOptions as aT, type CaptureValue as aU, type LambdaSurface as aV, type CaptureRecord as aW, type LambdaBindings as aX, Xano as aY, type AddonDef as aZ, type AddonXdo as a_, encodeTrigger as aa, encodeView as ab, encodeWorkflowTest as ac, encodeWorkspaceConfig as ad, generated as ae, getKind as af, getLockedCanonical as ag, getLockedGuid as ah, isLockSeeded as ai, isQueryExpressionFilter as aj, isRegisteredKind as ak, lockKey as al, mcpServerKind as am, mergeObserved as an, microserviceKind as ao, middlewareKind as ap, mintCanonical as aq, parseLock as ar, phpJsonEncode as as, queryKind as at, realtimeChannelGuid as au, realtimeChannelKind as av, realtimeMessageGuid as aw, realtimeMessageKind as ax, realtimeServerKind as ay, recordObserved as az, type BundlePayload as b, type MicroserviceContainer as b$, type AgentHandle as b0, type AgentOutput as b1, type AgentSettingsXdo as b2, type AgentXdo as b3, type AmbientBindings as b4, type AnthropicLlm as b5, type ApiGroupDef as b6, type ApiGroupXdo as b7, type AuthRef as b8, type Authored as b9, type HttpVerb as bA, type IndexDef as bB, type IndexLang as bC, type IndexOp as bD, type IndexType as bE, type IndexXdo as bF, type InferRow as bG, type InputDescriptor as bH, type InputOptions as bI, type IteratingBindings as bJ, LAMBDA_BINDINGS as bK, LAMBDA_CODE_FILTERS as bL, LAMBDA_GLOBALS as bM, LAMBDA_MODULE_GLOBALS as bN, LAMBDA_STATEMENTS as bO, type LambdaBody as bP, type LlmPrompt as bQ, type LlmProvider as bR, type LlmSettings as bS, type ManifestValue as bT, type McpPathOptions as bU, type McpServerDef as bV, type McpServerHandle as bW, type McpServerXdo as bX, type MessageDeliverTo as bY, type MicroserviceChart as bZ, type MicroserviceConfig as b_, type Capturable as ba, type ChannelConversationDef as bb, type ChannelDeliveryDef as bc, type ChannelDeliveryGuarantee as bd, type ChannelPublishDef as be, type ChannelPublishWho as bf, type ChannelRateLimitDef as bg, type ColumnDef as bh, type Comparison as bi, type ContainerEnv as bj, type ContainerPort as bk, type ContainerResources as bl, type ContainerVolume as bm, type CorsConfig as bn, type CorsMode as bo, type DatabaseActions as bp, type DatabaseInputs as bq, type DbEval as br, type DbEvalFilter as bs, type DbWhere as bt, type ErrorInputs as bu, type ExceptionPolicy as bv, type FieldAccessor as bw, type FieldRule as bx, type GoogleGenAiLlm as by, type HistoryInput as bz, type BundleType as c, type TriggerXdo as c$, type MicroserviceDef as c0, type MicroserviceDeployment as c1, type MicroserviceIngress as c2, type MicroserviceRegistryAuth as c3, type MicroserviceVolume as c4, type MicroserviceXdo as c5, type MiddlewareAttach as c6, type MiddlewareAttachEntry as c7, type MiddlewareDef as c8, type MiddlewareXdo as c9, type RowOf as cA, type ScheduleDef as cB, type SchemaCols as cC, type SchemaDef as cD, type SearchComparison as cE, type SearchGroup as cF, type SearchNode as cG, type SearchOp as cH, type SearchParamValue as cI, type SeedFileSource as cJ, type SeedRow as cK, type SeedSource as cL, type SortDir as cM, type SortDirective as cN, type TableDef as cO, type TableXdo as cP, type TaskDef as cQ, type TaskXdo as cR, type ToolDef as cS, type ToolXdo as cT, type ToolsetBaseDef as cU, type ToolsetBaseXdo as cV, type ToolsetInputs as cW, type ToolsetToolEntry as cX, type ToolsetToolRef as cY, type TriggerDef as cZ, type TriggerObjType as c_, type OpenAiLlm as ca, type QueryDef as cb, type QueryFilterName as cc, type QueryHandle as cd, type QueryResponseType as ce, type QueryXdo as cf, type RealtimeActions as cg, type RealtimeChannelActions as ch, type RealtimeChannelDef as ci, type RealtimeChannelHandle as cj, type RealtimeChannelRef as ck, type RealtimeChannelTriggerInputs as cl, type RealtimeChannelXdo as cm, type RealtimeClient as cn, type RealtimeInputs as co, type RealtimeMessageDef as cp, type RealtimeMessageXdo as cq, type RealtimeServerActions as cr, type RealtimeServerDef as cs, type RealtimeServerHandle as ct, type RealtimeServerRef as cu, type RealtimeServerTriggerInputs as cv, type RealtimeServerXdo as cw, type RealtimeUrlOptions as cx, type ResultStrategy as cy, type Route as cz, FILTER_NAMES as d, workspaceConfig as d$, type ViewDef as d0, type ViewXdo as d1, type WorkflowTestDef as d2, type WorkflowTestXdo as d3, type WorkspaceActions as d4, type WorkspaceConfigDef as d5, type WorkspaceConfigXdo as d6, type WorkspaceHistoryDef as d7, type WorkspaceHistoryXdo as d8, type WorkspaceInputs as d9, mcpServerTrigger as dA, microservice as dB, middleware as dC, mixed as dD, objectEntries as dE, objectKeys as dF, objectValues as dG, or as dH, query as dI, realtimeChannel as dJ, realtimeChannelTrigger as dK, realtimeMessage as dL, realtimeServer as dM, realtimeServerTrigger as dN, realtimeTrigger as dO, resetLockOverrides as dP, seedFile as dQ, seedLockOverrides as dR, table as dS, tableTrigger as dT, task as dU, textAppend as dV, textPrepend as dW, toSearchParams as dX, tool as dY, workflowTest as dZ, workspace as d_, type WorkspaceMiddlewareDef as da, type WorkspaceMiddlewareXdo as db, type XanoFreeLlm as dc, addon as dd, agent as de, agentTrigger as df, and as dg, apiGroup as dh, assertLambdaBody as di, bitwiseAnd as dj, bitwiseOr as dk, bitwiseXor as dl, cmp as dm, defineFunction as dn, emit as dp, emitBundle as dq, errorTrigger as dr, expr as ds, fl as dt, input as du, mathAdd as dv, mathDiv as dw, mathMul as dx, mathSub as dy, mcpServer as dz, LOCK_VERSION as e, workspaceTrigger as e0, type Condition as e1, type FilterResults as e2, type ElementResult as e3, type SameArrayResult as e4, type GroupedArrayResult as e5, type AgentResultOf as e6, type OutputPath as e7, type QualifiedCol as e8, type OutputRoot as e9, type AggregateRow as ea, type EvalFields as eb, type OutputAuthored as ec, lam as ed, type LockEntry as f, type LockExportContext as g, type LockFile as h, type ManifestField as i, type ManifestKind as j, type ManifestStatement as k, type MergeResult as l, type PayloadArrayKey as m, addonKind as n, adoptFromBundle as o, agentKind as p, apiGroupKind as q, buildBundle as r, buildManifest as s, buildMiddlewareBlock as t, buildWorkspaceHistory as u, calcSignatureJson as v, channelPathParams as w, createLockContext as x, declaredServicePorts as y, emptyLock as z };