@xanots/sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/agent-file-refresh-7XCACU34.js +95 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +184 -0
- package/dist/capture-HUV5BNTC.js +32 -0
- package/dist/chunk-26CNBZE2.js +963 -0
- package/dist/chunk-33ND6LKO.js +78 -0
- package/dist/chunk-3DUAGYGP.js +1550 -0
- package/dist/chunk-3IGNIP6R.js +89 -0
- package/dist/chunk-47WDWMBJ.js +14 -0
- package/dist/chunk-4BXJGVZ3.js +318 -0
- package/dist/chunk-4HT3BNZ7.js +675 -0
- package/dist/chunk-4YMD2OOZ.js +23 -0
- package/dist/chunk-5WGEURVI.js +156 -0
- package/dist/chunk-5YTBTF25.js +29 -0
- package/dist/chunk-6DHBYBTO.js +166 -0
- package/dist/chunk-76QBEIGO.js +66 -0
- package/dist/chunk-7DKX2SPN.js +100 -0
- package/dist/chunk-7REDODS2.js +468 -0
- package/dist/chunk-C56BC2FY.js +254 -0
- package/dist/chunk-DGSF2Q5H.js +60 -0
- package/dist/chunk-EMCAWMHC.js +3982 -0
- package/dist/chunk-EZG76F7R.js +159 -0
- package/dist/chunk-FJ6ZS5KB.js +3607 -0
- package/dist/chunk-GNPVYOPB.js +17 -0
- package/dist/chunk-HJPTWBLH.js +38 -0
- package/dist/chunk-IZW6MK3U.js +248 -0
- package/dist/chunk-LH6Z6KFV.js +247 -0
- package/dist/chunk-MZUI3JNX.js +93 -0
- package/dist/chunk-NDZFBZHC.js +167 -0
- package/dist/chunk-NOO35BQ2.js +37 -0
- package/dist/chunk-NUQCEOKA.js +13 -0
- package/dist/chunk-QTNO2WD6.js +56 -0
- package/dist/chunk-RVB7ITKN.js +49 -0
- package/dist/chunk-TRU5OHQ5.js +6904 -0
- package/dist/chunk-VKFQJEMW.js +814 -0
- package/dist/chunk-VTIL47DT.js +125 -0
- package/dist/chunk-WHOJWOSV.js +112 -0
- package/dist/chunk-WKP4D5MO.js +1692 -0
- package/dist/chunk-X4DVXBFY.js +38 -0
- package/dist/chunk-XQ22GLYS.js +118 -0
- package/dist/chunk-YDZYMZBE.js +670 -0
- package/dist/chunk-YGPIY3WT.js +1146 -0
- package/dist/chunk-YYRDLFSJ.js +602 -0
- package/dist/chunk-ZUTSMMAG.js +46 -0
- package/dist/cli.d.ts +331 -0
- package/dist/cli.js +33 -0
- package/dist/codegen-command-OOKK6AZK.js +40 -0
- package/dist/codegen.d.ts +114 -0
- package/dist/codegen.js +71 -0
- package/dist/completion-TIPPZN7H.js +298 -0
- package/dist/config-NL33PN4D.js +84 -0
- package/dist/deploy-command-4EQ7XX5G.js +451 -0
- package/dist/env-target-IFJ5QDHC.js +16 -0
- package/dist/ephemeral-command-3X3I4ULC.js +242 -0
- package/dist/index.d.ts +4477 -0
- package/dist/index.js +300 -0
- package/dist/init-command-R6H7ZXW5.js +27 -0
- package/dist/internal.d.ts +74 -0
- package/dist/internal.js +2065 -0
- package/dist/io-AMIKRLPC.js +11 -0
- package/dist/live-diff-Y3AKETHE.js +196 -0
- package/dist/lock-3CVKALKT.js +37 -0
- package/dist/lock-commands-XTR2YKLY.js +232 -0
- package/dist/login-command-ACJF6KWQ.js +167 -0
- package/dist/logout-command-MX3MJS5U.js +68 -0
- package/dist/loop-OM3P2Z6O.js +93 -0
- package/dist/marketplace-command-RATXTKXN.js +379 -0
- package/dist/meta-client-OW5WKWW7.js +170 -0
- package/dist/node.d.ts +59 -0
- package/dist/node.js +442 -0
- package/dist/profile-command-SWJ3SPKR.js +77 -0
- package/dist/release-command-VV5UVZKL.js +427 -0
- package/dist/response-CVAE2kMj.d.ts +1915 -0
- package/dist/routes-manifest-SP3ZXLMR.js +244 -0
- package/dist/runtime-V4C3AC3A.js +13 -0
- package/dist/sandbox-details-command-HJE5SPVG.js +18 -0
- package/dist/sandbox-export-command-QCJY4GMV.js +24 -0
- package/dist/static-host-D6KS7X45.js +138 -0
- package/dist/store-BQ97nKIF.d.ts +7760 -0
- package/dist/test-command-TSPBQHBM.js +383 -0
- package/dist/validate-command-4ERHJSCO.js +122 -0
- package/dist/verify-rollout-FAVQ6EJY.js +40 -0
- package/dist/workspace-K72NP7SX.js +49 -0
- package/dist/workspace-command-OF23DPNI.js +156 -0
- package/dist/workspace-export-AJMGN3CQ.js +10 -0
- package/llms/fields.md +104 -0
- package/llms/filters.md +183 -0
- package/llms/kinds-agent-mcp.md +11 -0
- package/llms/kinds-core.md +68 -0
- package/llms/kinds-realtime.md +76 -0
- package/llms/lambda.md +111 -0
- package/llms/legacy.md +43 -0
- package/llms/object-kinds.md +31 -0
- package/llms/statements-calls.md +23 -0
- package/llms/statements-catalog.md +309 -0
- package/llms/statements-data.md +58 -0
- package/llms/statements-runtime.md +48 -0
- package/llms/tests.md +58 -0
- package/llms/triggers.md +22 -0
- package/llms/values.md +29 -0
- package/llms-full.txt +1672 -0
- package/llms.txt +483 -0
- package/manifest.json +9592 -0
- package/package.json +110 -0
|
@@ -0,0 +1,1915 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared tagged-value primitive (KTD-2). Every place a function references
|
|
3
|
+
* data — input bindings, statement context, response — uses this `{value, tag,
|
|
4
|
+
* filters}` shape. Built and tested once here, reused everywhere.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A XanoTS authored value is just the stored tagged-value shape. */
|
|
8
|
+
type Value = TaggedValue;
|
|
9
|
+
/**
|
|
10
|
+
* A {@link Value} that also carries, **at the type level only**, the name of the
|
|
11
|
+
* stack variable it references (`ref("user")` → `RefValue<"user">`). The `__ref`
|
|
12
|
+
* carrier is phantom — never present at runtime — and required (not optional) so
|
|
13
|
+
* `InferResponse`'s trace (U5) matches only real refs, never a plain `Value`.
|
|
14
|
+
* Because it is a subtype of `Value`, every existing `ref(...)` use — filter
|
|
15
|
+
* args, `db.query` `where`, response fields — keeps type-checking unchanged.
|
|
16
|
+
*/
|
|
17
|
+
type RefValue<Name extends string = string> = Value & {
|
|
18
|
+
readonly __ref: Name;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A {@link Value} that has had a filter chain attached (`withFilters(...)`).
|
|
22
|
+
* The `__filtered` carrier is phantom (type-only). A filter can reshape the
|
|
23
|
+
* value arbitrarily at runtime — turn an object into a scalar, add or drop keys
|
|
24
|
+
* — with no static signal, so `InferResponse` treats a filtered response value
|
|
25
|
+
* as `unknown` (the honest floor, matching how the Xano engine degrades a
|
|
26
|
+
* filtered result to `json`). Overriding via `responseShape` remains available.
|
|
27
|
+
*/
|
|
28
|
+
type FilteredValue<Base = unknown, Chain extends readonly unknown[] = readonly unknown[]> = Value & {
|
|
29
|
+
readonly __filtered: true;
|
|
30
|
+
/** Phantom: the value the chain was applied to, so its type can be folded. */
|
|
31
|
+
readonly __base?: Base;
|
|
32
|
+
/** Phantom: the flattened filter chain, in application order. */
|
|
33
|
+
readonly __chain?: Chain;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* A {@link Value} built by {@link c.regex} — a PCRE pattern the engine can
|
|
37
|
+
* actually run, delimiter-wrapped (`/…/flags`).
|
|
38
|
+
*
|
|
39
|
+
* The `__regex` carrier is phantom (type-only). It exists so a helper that takes
|
|
40
|
+
* a pattern can SAY so in its signature (`function match(p: RegexValue)`) and so
|
|
41
|
+
* an editor's hover distinguishes a pattern from the text beside it.
|
|
42
|
+
*
|
|
43
|
+
* It is deliberately NOT used to restrict `s.expect.to_match` or the `fl.regex_*`
|
|
44
|
+
* pattern slot, which is what issue #213 proposed. A nominal restriction there
|
|
45
|
+
* would have to reject `Value`, and `inp()`, `env()`, `auth()` and `sys.*` all
|
|
46
|
+
* return exactly `Value` — the same type `c.text` does — so it would reject every
|
|
47
|
+
* DYNAMIC pattern along with the broken constant ones. The enforcement is instead
|
|
48
|
+
* a build-time refusal that inspects the value it was actually given
|
|
49
|
+
* ({@link assertRegexPattern}), which fires on the bare constant and stays silent
|
|
50
|
+
* on a reference it cannot see through.
|
|
51
|
+
*/
|
|
52
|
+
type RegexValue = Value & {
|
|
53
|
+
readonly __regex: true;
|
|
54
|
+
};
|
|
55
|
+
/** Flatten one level of `withFilters`'s spread-or-array argument list. */
|
|
56
|
+
type FlattenFilters<Fs extends readonly unknown[]> = Fs extends readonly [
|
|
57
|
+
infer Head,
|
|
58
|
+
...infer Tail
|
|
59
|
+
] ? Head extends readonly unknown[] ? [...Head, ...FlattenFilters<Tail>] : [Head, ...FlattenFilters<Tail>] : [];
|
|
60
|
+
/**
|
|
61
|
+
* A {@link Value} produced by {@link col} (or a filter chain built from one). The
|
|
62
|
+
* `__col` carrier is phantom (type-only). It exists so a `col()` reference can be
|
|
63
|
+
* *statically rejected* where it would silently fail at runtime: inside a
|
|
64
|
+
* `db.edit`/`db.add` `row`, `{tag:"col"}` does not resolve to the row's stored
|
|
65
|
+
* value — it evaluates to `null`, so a following `fl.add(1)` computes `null + 1`
|
|
66
|
+
* and the engine aborts ("Numbers are required for mathematical operations",
|
|
67
|
+
* issue #32). `col()` is only meaningful in a `db.query` `where`/view expression.
|
|
68
|
+
*/
|
|
69
|
+
type ColValue = Value & {
|
|
70
|
+
readonly __col: true;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The error branch surfaced when a tagged {@link Value} is nested inside a
|
|
74
|
+
* `c.obj`/`c.array` literal (issue #42). The long message is the *property key*
|
|
75
|
+
* so TypeScript prints it verbatim in the "property … is missing" diagnostic; a
|
|
76
|
+
* `Value` has no such key, so intersecting it here makes the offending position
|
|
77
|
+
* fail to type-check. The runtime guard ({@link assertPlainJson}) carries the
|
|
78
|
+
* same guidance for JS/`any`-typed callers the type can't reach.
|
|
79
|
+
*/
|
|
80
|
+
type TaggedValueNotAllowed = {
|
|
81
|
+
"❌ c.obj/c.array take plain JSON only — a tagged value (inp/ref/auth/col/c.*) can't be nested. For a computed object response use a record of values: `response: { key: value }` (not c.obj). See issue #42.": never;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Recursively reject any nested {@link Value} in a plain-JSON literal `T`. A
|
|
85
|
+
* member assignable to `Value` maps to {@link TaggedValueNotAllowed}; plain JSON
|
|
86
|
+
* (primitives, arrays, objects) passes through unchanged. Used intersected with
|
|
87
|
+
* a naked `T` (`o: T & RejectValues<T>`) so `T` stays inferrable while the
|
|
88
|
+
* rejection rides along. Structural `extends Value` detection — not a `JsonLiteral`
|
|
89
|
+
* constraint — so it survives a future `TaggedValue` interface→alias refactor.
|
|
90
|
+
*/
|
|
91
|
+
type RejectValues<T> = T extends Value ? TaggedValueNotAllowed : T extends readonly (infer E)[] ? readonly RejectValues<E>[] : T extends object ? {
|
|
92
|
+
[K in keyof T]: RejectValues<T[K]>;
|
|
93
|
+
} : T;
|
|
94
|
+
/**
|
|
95
|
+
* Tags {@link c.blank} can spell — the constant tags with no exact blank form of
|
|
96
|
+
* their own.
|
|
97
|
+
*
|
|
98
|
+
* Derived by exclusion from {@link TAGS} rather than listed, so a constant tag
|
|
99
|
+
* added to the catalog is blank-spellable without a second edit here. The two
|
|
100
|
+
* carve-outs are the tags that already round-trip a blank exactly: `const` via
|
|
101
|
+
* `c.text("")`, and `const:obj` via `c.obj(null)`.
|
|
102
|
+
*
|
|
103
|
+
* Reference tags are excluded by construction: a blank `var`/`input`/`col` is an
|
|
104
|
+
* unbound reference, not an empty value, and the two want different fixes.
|
|
105
|
+
*/
|
|
106
|
+
type BlankTag = Exclude<Extract<Tag, `const${string}`>, "const" | "const:obj">;
|
|
107
|
+
/**
|
|
108
|
+
* Tags {@link c.null} can spell — the two the engine stores a `"null"` value
|
|
109
|
+
* under. `const:null` is the plain one; `const:obj` is the object-typed null a
|
|
110
|
+
* `db.*` statement's `@meta` slot carries (issue #151).
|
|
111
|
+
*
|
|
112
|
+
* Listed rather than derived: a `"null"` value under any OTHER constant tag is
|
|
113
|
+
* unobserved, and inventing spellings for it would offer an authoring form for
|
|
114
|
+
* bytes no engine writes.
|
|
115
|
+
*/
|
|
116
|
+
type NullTag = "const:null" | "const:obj";
|
|
117
|
+
/** Constant constructors. Values always serialize as strings (per fixture). */
|
|
118
|
+
declare const c: {
|
|
119
|
+
/**
|
|
120
|
+
* Plain string constant → `tag:"const"`.
|
|
121
|
+
*
|
|
122
|
+
* `null` is accepted alongside a string because the engine stores both: 47
|
|
123
|
+
* values in the sweep are a bare `const` holding `null` rather than `""`,
|
|
124
|
+
* mostly ignored statement-input entries the engine never reads. They are
|
|
125
|
+
* distinct bytes — `normalize` keeps them apart, unlike the `const:obj` blanks
|
|
126
|
+
* it does canonicalize — so a pull has to be able to spell the null form, and
|
|
127
|
+
* `c.text(null)` did not type-check.
|
|
128
|
+
*
|
|
129
|
+
* Write `c.text("")` for an empty string. The null form is here so a pulled
|
|
130
|
+
* workspace round-trips, not to be authored.
|
|
131
|
+
*/
|
|
132
|
+
text(s: string | null): Value;
|
|
133
|
+
/**
|
|
134
|
+
* Integer constant → `tag:"const:int"`, value stringified (e.g. `"123"`).
|
|
135
|
+
*
|
|
136
|
+
* Pass a **string** or `bigint` for an integer outside JavaScript's safe
|
|
137
|
+
* range. The engine stores integers as strings and has no such limit, so
|
|
138
|
+
* `c.int("18446744073709551615")` — 2^64 − 1, what a bitwise NOT returns —
|
|
139
|
+
* is exact, where the number literal for it silently rounds to `…616`.
|
|
140
|
+
*
|
|
141
|
+
* A `number` that is not an integer, or is past `Number.MAX_SAFE_INTEGER`,
|
|
142
|
+
* **throws**: by the time such a literal reaches here its precision is already
|
|
143
|
+
* gone, so encoding it would write a value the caller did not type. The throw
|
|
144
|
+
* names the string form. (issue #153)
|
|
145
|
+
*/
|
|
146
|
+
int(n: number | bigint | string): Value;
|
|
147
|
+
/**
|
|
148
|
+
* Decimal constant → `tag:"const:decimal"`.
|
|
149
|
+
*
|
|
150
|
+
* Pass a **string** to preserve a stored spelling a number literal cannot
|
|
151
|
+
* reproduce — `c.decimal("10.00")` keeps its trailing zeros, where
|
|
152
|
+
* `c.decimal(10)` writes `"10"`. The engine stores decimals as strings either
|
|
153
|
+
* way, so this is exactness rather than a workaround; prefer the number form
|
|
154
|
+
* whenever it reproduces the value you want.
|
|
155
|
+
*/
|
|
156
|
+
decimal(n: number | string): Value;
|
|
157
|
+
/**
|
|
158
|
+
* The editor's **unconfigured value box** — a value cell added and never
|
|
159
|
+
* filled in, stored as `{value: "", tag}`.
|
|
160
|
+
*
|
|
161
|
+
* This is not a zero, an empty string, or an empty collection. The engine
|
|
162
|
+
* reads `""` and `"0"` differently, so `c.blank("const:int")` and `c.int(0)`
|
|
163
|
+
* are different stored values and the SDK will not canonicalize one into the
|
|
164
|
+
* other. It exists because 13 real values in the survey corpus are in this
|
|
165
|
+
* state and had no authoring form, so a pull emitted them as annotated
|
|
166
|
+
* literals with a warning attached — describing a workspace that was fine.
|
|
167
|
+
*
|
|
168
|
+
* Constant tags only. A blank `var` or `input` is an unbound REFERENCE, which
|
|
169
|
+
* is a different defect with a different fix, and is deliberately not
|
|
170
|
+
* spellable here. `const` and `const:obj` are excluded too: they already have
|
|
171
|
+
* exact blank forms in `c.text("")` and `c.obj(null)`, and a second spelling
|
|
172
|
+
* for the same bytes is how two constructors start disagreeing.
|
|
173
|
+
*/
|
|
174
|
+
blank(tag: BlankTag): Value;
|
|
175
|
+
/** Boolean constant → `"true"`/`"false"` with `tag:"const:bool"`. */
|
|
176
|
+
bool(b: boolean): Value;
|
|
177
|
+
/**
|
|
178
|
+
* Null constant → value `"null"` (per engine fixture).
|
|
179
|
+
*
|
|
180
|
+
* Bare `c.null()` is the plain `const:null`. Pass a tag for the **object-typed
|
|
181
|
+
* null** the engine writes into a `db.*` statement's `@meta` slot —
|
|
182
|
+
* `c.null("const:obj")` → `{tag:"const:obj", value:"null"}`, 16 of them across
|
|
183
|
+
* eight ordinary queries in the survey corpus, which had no spelling and came
|
|
184
|
+
* back as `rawValue` (issue #151).
|
|
185
|
+
*
|
|
186
|
+
* ⚠ Not the same bytes as `c.obj(null)`, which is the BLANK object
|
|
187
|
+
* (`value: ""`). Both evaluate to null — the engine JSON-decodes the stored
|
|
188
|
+
* string and `""` and `"null"` both decode to it — but they are two stored
|
|
189
|
+
* spellings, and the SDK writes back the one it was given rather than
|
|
190
|
+
* re-pointing a pulled workspace at the other.
|
|
191
|
+
*/
|
|
192
|
+
null(tag?: NullTag): Value;
|
|
193
|
+
/**
|
|
194
|
+
* Build a **regex pattern value** for the pattern-piped regex filters
|
|
195
|
+
* (`fl.regex_test`/`regex_match`/`regex_replace`/…). Xano runs PHP `preg_*`, so
|
|
196
|
+
* the pattern MUST be delimiter-wrapped — a bare `c.text("^…$")` is an invalid
|
|
197
|
+
* PCRE and the filter then matches *nothing* for every input, so a precondition
|
|
198
|
+
* built on it silently rejects all values (issue #128). This wraps the raw
|
|
199
|
+
* pattern in `/…/`, escapes any interior `/`, and appends `flags`, so the result
|
|
200
|
+
* is always valid; `withFilters` rejects a bare `c.text` pattern and points here.
|
|
201
|
+
*
|
|
202
|
+
* Pass a JS `RegExp` (source + flags used directly, minus JS-only `g`/`y`/`d`):
|
|
203
|
+
* `c.regex(/^[^@\s]+@[^@\s]+\.[^@\s]+$/i)`, or a raw body + optional PCRE flags:
|
|
204
|
+
* `c.regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", "i")`. The pattern is the piped
|
|
205
|
+
* value; the filter's `subject` arg is the text tested against it.
|
|
206
|
+
*/
|
|
207
|
+
regex(pattern: string | RegExp, flags?: string): RegexValue;
|
|
208
|
+
/**
|
|
209
|
+
* Current time as an **epoch-milliseconds** value — the engine's native
|
|
210
|
+
* `const:epochms` constant. Xano has no `$now` *setting*, so this lives on
|
|
211
|
+
* `c.*` (a current-time literal) rather than {@link sys} (which emits
|
|
212
|
+
* `setting("$…")` and would reference a dead `$now` var).
|
|
213
|
+
*
|
|
214
|
+
* Emits `{tag:"const:epochms", value:"now"}` — an UNFILTERED value, valid
|
|
215
|
+
* inline as a `where`/`cmp` operand. It previously emitted the equivalent
|
|
216
|
+
* `text("now") |to_epoch_ms` chain; both evaluate to the same epoch-ms number
|
|
217
|
+
* on a live engine, and both persist verbatim, but the native tag is what the
|
|
218
|
+
* editor writes and needs no filter to get there. Chain math onto it as usual:
|
|
219
|
+
* `withFilters(c.now(), fl.epochms_add_ms(c.int(-maxAgeMs)))`. (issues #120,
|
|
220
|
+
* #145)
|
|
221
|
+
*/
|
|
222
|
+
now(): Value;
|
|
223
|
+
/**
|
|
224
|
+
* Object constant → JSON-string value with `tag:"const:obj"`. Takes **plain
|
|
225
|
+
* JSON literals only**: nesting a tagged value (`inp`/`ref`/`auth`/`c.*`) is a
|
|
226
|
+
* compile error and throws at runtime — it would serialize as internal
|
|
227
|
+
* representation the engine can't decode. For a computed/multi-key object
|
|
228
|
+
* response use a record of values (`response: { key: value }`), not `c.obj`.
|
|
229
|
+
* See issue #42.
|
|
230
|
+
*
|
|
231
|
+
* Called with no argument it is the **empty object**, `{}` — the same default
|
|
232
|
+
* the editor gives a new object variable, and the only empty form current
|
|
233
|
+
* editors write.
|
|
234
|
+
*
|
|
235
|
+
* Called with an explicit `null` it is the **blank** form (stored `value: ""`),
|
|
236
|
+
* which the engine evaluates to `null` rather than `{}` — it JSON-decodes the
|
|
237
|
+
* stored string, and decoding `""` yields null. That is a real difference in
|
|
238
|
+
* what the statement sees, so the two are separate spellings rather than one
|
|
239
|
+
* "empty object": `c.obj()` is `{}`, `c.obj(null)` is null.
|
|
240
|
+
*
|
|
241
|
+
* ⚠ **Prefer `c.obj()`.** The blank form is legacy — no current editor path
|
|
242
|
+
* writes it — and it exists here so a pulled workspace round-trips to the
|
|
243
|
+
* same bytes instead of being quietly re-pointed at `{}`.
|
|
244
|
+
*
|
|
245
|
+
* A **populated** object is stored the way the editor stores one: an empty
|
|
246
|
+
* `{}` base carrying one `set` filter per key (issue #248). It is NOT a
|
|
247
|
+
* populated JSON string — that form fails the request at runtime, see
|
|
248
|
+
* {@link objSetFilters}.
|
|
249
|
+
*
|
|
250
|
+
* ⚠ **Zero-based numeric keys come back as a LIST**, not an object:
|
|
251
|
+
* `c.obj({ "0": "a" })` evaluates to `["a"]`. That is the engine's data model
|
|
252
|
+
* — a numeric key IS an index there, and the same object decoded from JSON
|
|
253
|
+
* anywhere else behaves identically — not something this encoding introduces.
|
|
254
|
+
* A non-zero-based numeric key (`{ "2": … }`) survives as a key. Live-verified.
|
|
255
|
+
*/
|
|
256
|
+
obj<const T>(o?: (T & RejectValues<T>) | null): Value;
|
|
257
|
+
/**
|
|
258
|
+
* Array constant → JSON-string value with `tag:"const:array"`. Takes **plain
|
|
259
|
+
* JSON literals only** — like {@link obj}, a nested tagged value is a compile
|
|
260
|
+
* error and throws at runtime. See issue #42.
|
|
261
|
+
*/
|
|
262
|
+
array<const T extends readonly unknown[]>(a: T & RejectValues<T>): Value;
|
|
263
|
+
/**
|
|
264
|
+
* A **Xano Expression Engine** expression, passed through verbatim →
|
|
265
|
+
* `tag:"const:expr2"`. The string IS the expression, exactly as it would be
|
|
266
|
+
* typed into the expression editor:
|
|
267
|
+
*
|
|
268
|
+
* c.expression('"Hello, " ~ $input.name')
|
|
269
|
+
* c.expression("$var.price * $var.qty")
|
|
270
|
+
* c.expression("{ id: $var.user.id, tier: $var.plan }")
|
|
271
|
+
*
|
|
272
|
+
* ⚠️ **THE STRING IS NOT VALIDATED.** XanoTS does not parse it, does not
|
|
273
|
+
* type-check it, and cannot tell a working expression from a typo — the whole
|
|
274
|
+
* string is handed to the engine as-is. Nothing here participates in
|
|
275
|
+
* `InferResponse`, so a var referenced inside it is invisible to the type
|
|
276
|
+
* system, and a rename that updates every typed `ref()` will NOT update this.
|
|
277
|
+
* A malformed expression surfaces at RUNTIME, and one that is merely wrong
|
|
278
|
+
* (`$var.tota1`) surfaces as a wrong answer, not an error. Play at your own
|
|
279
|
+
* risk until validation exists.
|
|
280
|
+
*
|
|
281
|
+
* Prefer the typed surfaces whenever they cover the case: `ref`/`inp`/`col`
|
|
282
|
+
* for references, `withFilters(..., fl.*)` for transforms, {@link obj} for a
|
|
283
|
+
* dynamic object (it BUILDS a checked expression for you). Reach for this only
|
|
284
|
+
* for expression-engine syntax the typed surfaces cannot express — string
|
|
285
|
+
* concatenation with `~`, inline arithmetic, conditionals.
|
|
286
|
+
*
|
|
287
|
+
* NOT the `expr()` condition builder. `expr(col("id"), "=", inp("id"))` builds
|
|
288
|
+
* a comparison for a `where`; this builds a VALUE from raw expression source.
|
|
289
|
+
*/
|
|
290
|
+
expression(source: string): Value;
|
|
291
|
+
/**
|
|
292
|
+
* The **older** expression form → `tag:"const:expr"`, kept because real
|
|
293
|
+
* workspaces still hold values stored that way and codegen has to bring them
|
|
294
|
+
* back as something readable. New code wants {@link expression}, which is what
|
|
295
|
+
* the expression editor writes today.
|
|
296
|
+
*
|
|
297
|
+
* ⚠️ Unvalidated passthrough, exactly like {@link expression} — see the
|
|
298
|
+
* warning there. This one additionally uses the older syntax generation, so an
|
|
299
|
+
* expression copied out of the current editor may not mean the same thing
|
|
300
|
+
* here.
|
|
301
|
+
*/
|
|
302
|
+
expressionLegacy(source: string): Value;
|
|
303
|
+
};
|
|
304
|
+
/** Options for {@link ref}. */
|
|
305
|
+
interface RefOptions {
|
|
306
|
+
/**
|
|
307
|
+
* Null-safe nested access (opt-in). A dotted `ref("owner.user_id")` normally
|
|
308
|
+
* compiles to the raw var path `$owner.user_id`, which the engine resolves in
|
|
309
|
+
* a single lookup — so when the base var `owner` is null (e.g. a `db.get` that
|
|
310
|
+
* matched no row), it raises a runtime `ERROR_FATAL` "Unable to locate var"
|
|
311
|
+
* (HTTP 500) instead of yielding null (issue #47).
|
|
312
|
+
*
|
|
313
|
+
* With `safe: true` the path compiles through the `get` filter
|
|
314
|
+
* (`$owner|get:"user_id"`), which walks the remaining path and resolves to
|
|
315
|
+
* null when the base is null — so an ownership/existence guard evaluates to
|
|
316
|
+
* `false` cleanly rather than throwing. Has no effect on a plain, dot-free name
|
|
317
|
+
* (a bare var already resolves to null without error).
|
|
318
|
+
*/
|
|
319
|
+
safe?: boolean;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Reference a **stack variable** — the `as:` output of an earlier statement:
|
|
323
|
+
* `{tag:"var", value}`. e.g. `dbGet({ ..., as: "user" })` then `ref("user")`.
|
|
324
|
+
*
|
|
325
|
+
* Pass `{ safe: true }` to make a *nested* path null-safe: `ref("owner.user_id",
|
|
326
|
+
* { safe: true })` resolves to null instead of 500ing when the base `owner` is
|
|
327
|
+
* null (issue #47) — the intent-revealing opt-in for drilling into a `db.get`
|
|
328
|
+
* result that may not exist.
|
|
329
|
+
*
|
|
330
|
+
* Picking a reference helper (these are easy to mix up):
|
|
331
|
+
* - {@link ref} — a stack variable (`as:` output). **Not** a foreign key — that's
|
|
332
|
+
* the field constructor `f.tableRef`.
|
|
333
|
+
* - {@link inp} — an endpoint/function `input`.
|
|
334
|
+
* - {@link col} — a table column (in a `db.query` `where`/view comparison).
|
|
335
|
+
* - {@link auth} — the authenticated caller (`auth("id")`).
|
|
336
|
+
* - `c.*` — a literal constant (`c.int(1)`, `c.text("x")`).
|
|
337
|
+
*/
|
|
338
|
+
declare function ref<const Name extends string>(name: Name, opts?: RefOptions): RefValue<Name>;
|
|
339
|
+
/**
|
|
340
|
+
* The second parameter every non-`ref` reference creator declares, so that
|
|
341
|
+
* passing `{ safe: true }` to one is a COMPILE error whose expected type is the
|
|
342
|
+
* explanation (issue #80).
|
|
343
|
+
*
|
|
344
|
+
* `safe` was silently dropped before this: the creators took one parameter, so
|
|
345
|
+
* JavaScript discarded the extra argument and TypeScript never saw it. An
|
|
346
|
+
* author who wrote `inp("owner.id", { safe: true })` believed they had a
|
|
347
|
+
* null-safe drill and still got a 500 on a null base.
|
|
348
|
+
*/
|
|
349
|
+
type SafeIsRefOnly = "`safe` is only valid on ref() — see issue #80";
|
|
350
|
+
/** Reference a function/endpoint **input**: `{tag:"input", value}`. See {@link ref} for the full picker. */
|
|
351
|
+
declare function inp(name: string, opts?: SafeIsRefOnly): Value;
|
|
352
|
+
/**
|
|
353
|
+
* Reference a table **column**: `{tag:"col", value}` (used in `db.query` `where` +
|
|
354
|
+
* table views). See {@link ref} for the full picker. The return is branded
|
|
355
|
+
* {@link ColValue} so it is a *compile error* to pass `col()` into a `db.edit`/
|
|
356
|
+
* `db.add` `row` — where it would resolve to `null` at runtime (issue #32).
|
|
357
|
+
*/
|
|
358
|
+
declare function col(name: string, opts?: SafeIsRefOnly): ColValue;
|
|
359
|
+
/**
|
|
360
|
+
* Reference the authenticated identity (`{tag:"auth", value}`). Pass a path to
|
|
361
|
+
* drill into the auth record — `auth("id")` is the authenticated row id
|
|
362
|
+
* (Xano's `$auth.id`); bare `auth()` is the whole record. Use it to bind the
|
|
363
|
+
* caller into a row write on an authenticated endpoint (one whose `auth` names
|
|
364
|
+
* an auth table), e.g.
|
|
365
|
+
* `s.db.add({ table: post, row: { author_id: auth("id") } })`.
|
|
366
|
+
*/
|
|
367
|
+
declare function auth(path?: string, opts?: SafeIsRefOnly): Value;
|
|
368
|
+
/**
|
|
369
|
+
* Reference the RESPONSE of the object under test (`{tag:"response", value}`) —
|
|
370
|
+
* the subject nearly every unit-test assertion is written against.
|
|
371
|
+
*
|
|
372
|
+
* Bare `resp()` is the whole response (stored as the literal `"response"`, which
|
|
373
|
+
* is what the editor writes); a path drills into it, `resp("id")` →
|
|
374
|
+
* `"response.id"`. A segment that is not a bare identifier takes the engine's
|
|
375
|
+
* bracket escape, so `resp("user-data")` → `response["user-data"]`.
|
|
376
|
+
*
|
|
377
|
+
* Meaningful only inside a `tests` entry's `expect` list. It is NOT a stack
|
|
378
|
+
* variable: a running stack has no response yet, which is why `ref` cannot
|
|
379
|
+
* spell this and why the engine gives it its own tag.
|
|
380
|
+
*/
|
|
381
|
+
declare function resp(path?: string, opts?: SafeIsRefOnly): Value;
|
|
382
|
+
/**
|
|
383
|
+
* The four fields the engine binds in a `s.try_catch` catch arm. Read straight
|
|
384
|
+
* off the engine's own catch-variable map, which sets exactly these.
|
|
385
|
+
*/
|
|
386
|
+
type CaughtField = "code" | "message" | "name" | "result";
|
|
387
|
+
/**
|
|
388
|
+
* Read the caught error inside a {@link s.try_catch} **catch** arm
|
|
389
|
+
* (`{tag:"trycatch", value}` — XanoScript's `$trycatch.*`).
|
|
390
|
+
*
|
|
391
|
+
* Only valid inside the catch arm; the engine binds these for that scope alone
|
|
392
|
+
* and they read empty anywhere else. The four fields are all the engine sets:
|
|
393
|
+
* - `name` — the error name/type (for a thrown error statement, its message)
|
|
394
|
+
* - `message` — the human-readable message (`"Throw Error Statement"` for a throw)
|
|
395
|
+
* - `code` — the mapped HTTP-ish error code
|
|
396
|
+
* - `result` — the error payload, when one was attached
|
|
397
|
+
*
|
|
398
|
+
* e.g. `s.try_catch({ try: [...], catch: [s.debug_log(caught("message"))] })`.
|
|
399
|
+
* Bare `caught()` is the whole error record.
|
|
400
|
+
*/
|
|
401
|
+
declare function caught(path?: CaughtField | "", opts?: SafeIsRefOnly): Value;
|
|
402
|
+
/**
|
|
403
|
+
* The toolset-scoped bindings, as the engine sets them.
|
|
404
|
+
*
|
|
405
|
+
* Both statements that populate the namespace write exactly two members —
|
|
406
|
+
* `token` (the calling URL's token, null when the call carried none) and
|
|
407
|
+
* `params` (the URL's parameters, an empty object when there were none). A
|
|
408
|
+
* dotted `params.<key>` reads one parameter: the lookup walks the stored map,
|
|
409
|
+
* so a path into `params` resolves the same way `ref("a.b")` does.
|
|
410
|
+
*
|
|
411
|
+
* Closed at the root deliberately — a name outside it resolves to empty rather
|
|
412
|
+
* than raising, so a typo would read as a silent null at runtime.
|
|
413
|
+
*/
|
|
414
|
+
type ToolsetPath = "token" | "params" | `params.${string}`;
|
|
415
|
+
/**
|
|
416
|
+
* Read a **toolset-scoped binding** inside an agent/MCP tool
|
|
417
|
+
* (`{tag:"toolset", value}`) — the token and URL parameters the toolset was
|
|
418
|
+
* called with, alongside {@link ref}/{@link inp}/{@link env}/{@link auth}.
|
|
419
|
+
*
|
|
420
|
+
* - `toolset("token")` — the token on the calling URL, null when absent.
|
|
421
|
+
* - `toolset("params")` — the URL parameters as an object.
|
|
422
|
+
* - `toolset("params.tenant")` — one parameter out of that object.
|
|
423
|
+
*
|
|
424
|
+
* Only bound while a tool runs under its toolset; anywhere else it reads empty
|
|
425
|
+
* (issue #152).
|
|
426
|
+
*/
|
|
427
|
+
declare function toolset(path: ToolsetPath, opts?: SafeIsRefOnly): Value;
|
|
428
|
+
/**
|
|
429
|
+
* Read a **workspace environment variable** — the ones set via `workspaceConfig({ env })`
|
|
430
|
+
* or the workspace dashboard, e.g. `env("STRIPE_KEY")` → `$env.STRIPE_KEY`.
|
|
431
|
+
*
|
|
432
|
+
* Under the hood a workspace env var is a `{tag:"setting", value:"NAME"}` (the plain,
|
|
433
|
+
* non-`$` name) — `$env.NAME` in XanoScript is sugar for that setting. This is the SAME
|
|
434
|
+
* tag the built-in request/system vars use; those just carry a `$`-prefixed name
|
|
435
|
+
* (`$env.$remote_ip`). So `env("remote_ip")` reads a *user* var literally named
|
|
436
|
+
* `remote_ip` (usually unset → null), NOT the caller IP — use {@link sys} (`sys.remoteIp()`)
|
|
437
|
+
* or {@link setting} with the exact `$`-prefixed name for the built-ins.
|
|
438
|
+
*
|
|
439
|
+
* (`$env.NAME` is a setting, not the raw `tag:"env"` form — which does not resolve
|
|
440
|
+
* workspace vars — so this reads them as settings, matching the platform.)
|
|
441
|
+
*/
|
|
442
|
+
declare function env(name: string, opts?: SafeIsRefOnly): Value;
|
|
443
|
+
/**
|
|
444
|
+
* Reference a workspace setting by raw name (`{tag:"setting", value}`). The built-in
|
|
445
|
+
* request/system variables are settings with a **`$`-prefixed** name — `setting("$remote_ip")`,
|
|
446
|
+
* `setting("$datasource")`, etc. Prefer the typed {@link sys} accessors, which spell the
|
|
447
|
+
* names for you and avoid the `$`-prefix footgun; drop to `setting()` only for a name `sys`
|
|
448
|
+
* doesn't cover.
|
|
449
|
+
*/
|
|
450
|
+
declare function setting(name: string, opts?: SafeIsRefOnly): Value;
|
|
451
|
+
/**
|
|
452
|
+
* Built-in **system / request-context variables**. In XanoScript these are written
|
|
453
|
+
* `$env.$remote_ip`, `$env.$datasource`, … — note the second `$`: they are *settings*
|
|
454
|
+
* (`{tag:"setting", value:"$remote_ip"}`), distinct from the user-defined env vars that
|
|
455
|
+
* {@link env} reaches. Reaching for `env("remote_ip")` silently reads the wrong thing;
|
|
456
|
+
* these accessors emit the correct `setting("$…")` form so you never type the `$` prefix.
|
|
457
|
+
*
|
|
458
|
+
* The one that matters most: on a **public** endpoint `auth("id")` is null (every caller
|
|
459
|
+
* collapses into one bucket), so key a rate limit off {@link sys.remoteIp} instead —
|
|
460
|
+
* `withFilters(c.text("rl:apply:"), fl.concat(sys.remoteIp()))`.
|
|
461
|
+
*
|
|
462
|
+
* Mirrors the full workspace "environment" panel; every accessor returns a {@link Value}.
|
|
463
|
+
*/
|
|
464
|
+
declare const sys: {
|
|
465
|
+
/** Client IP address (`$remote_ip`, text). Best public-endpoint rate-limit key. */
|
|
466
|
+
remoteIp: () => Value;
|
|
467
|
+
/** HTTP method of the request — `GET`, `POST`, … (`$request_method`, text). */
|
|
468
|
+
requestMethod: () => Value;
|
|
469
|
+
/** Full request URI/path (`$request_uri`, text). */
|
|
470
|
+
requestUri: () => Value;
|
|
471
|
+
/** Raw query-string portion of the URL (`$request_querystring`, text). */
|
|
472
|
+
requestQueryString: () => Value;
|
|
473
|
+
/** Request headers as an object/map (`$http_headers`, object). */
|
|
474
|
+
httpHeaders: () => Value;
|
|
475
|
+
/** The caller's `Authorization` bearer token, if present (`$request_auth_token`, text). */
|
|
476
|
+
requestAuthToken: () => Value;
|
|
477
|
+
/** API base URL for the request (`$api_baseurl`, text). */
|
|
478
|
+
apiBaseUrl: () => Value;
|
|
479
|
+
/** Active data source name — e.g. `live` or a branch source (`$datasource`, text). */
|
|
480
|
+
datasource: () => Value;
|
|
481
|
+
/** Active branch name (`$branch`, text). */
|
|
482
|
+
branch: () => Value;
|
|
483
|
+
/** Tenant identifier for multi-tenant instances (`$tenant`, text). */
|
|
484
|
+
tenant: () => Value;
|
|
485
|
+
/** Current release number (`$release`, int). */
|
|
486
|
+
release: () => Value;
|
|
487
|
+
/** Platform identifier (`$platform`, int). */
|
|
488
|
+
platform: () => Value;
|
|
489
|
+
/** `true` when the request is running under the debugger (`$debugger`, bool). */
|
|
490
|
+
isDebugger: () => Value;
|
|
491
|
+
};
|
|
492
|
+
/**
|
|
493
|
+
* Reference a column of the **parent statement's output row** (`{tag:"output",
|
|
494
|
+
* value}`) — the `$output.<col>` reference an addon input binds to. Only
|
|
495
|
+
* meaningful inside an addon spec's `input` map (see `s.db.query`'s `addon`
|
|
496
|
+
* arg), where the engine resolves it against each row the parent query returns,
|
|
497
|
+
* e.g. `addon: [{ addon: transactions, as: "items._txns",
|
|
498
|
+
* input: { user_id: out("id") } }]`.
|
|
499
|
+
*/
|
|
500
|
+
declare function out(name: string, opts?: SafeIsRefOnly): Value;
|
|
501
|
+
/**
|
|
502
|
+
* Build a `mvp_filter` chain entry: `{name, disabled:false, arg}`.
|
|
503
|
+
*
|
|
504
|
+
* Generic in the name so a literal call carries it at the type level
|
|
505
|
+
* (`filter("upper")` is a `FilterXdo<"upper">`), which is what lets a chain's
|
|
506
|
+
* result type be folded. A `string` name — the escape-hatch spelling, and what a
|
|
507
|
+
* decoded workspace passes — widens to `FilterXdo<string>` and folds to
|
|
508
|
+
* `unknown`, exactly as before.
|
|
509
|
+
*/
|
|
510
|
+
declare function filter<const N extends string>(name: N, ...args: (Value | undefined)[]): FilterXdo<N>;
|
|
511
|
+
/**
|
|
512
|
+
* Attach a filter chain to a value, returning a new value. Pass filters spread
|
|
513
|
+
* (the canonical form, `withFilters(v, fl.trim(), fl.lower())`); the array form
|
|
514
|
+
* (`withFilters(v, [fl.trim(), fl.lower()])`) is also accepted — both are flattened.
|
|
515
|
+
*/
|
|
516
|
+
declare function withFilters<V extends Value, const Fs extends readonly (FilterXdo | FilterXdo[])[]>(value: V, ...filters: Fs): FilteredValue<V, FlattenFilters<Fs>> & (V extends ColValue ? {
|
|
517
|
+
readonly __col: true;
|
|
518
|
+
} : unknown);
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Unit-test ASSERTIONS — the `expect[]` list on a saved `test`.
|
|
522
|
+
*
|
|
523
|
+
* ## Not to be confused with `s.expect.*`
|
|
524
|
+
*
|
|
525
|
+
* Xano overloads the word, and so does this SDK, because both surfaces are
|
|
526
|
+
* really called `expect` in the product:
|
|
527
|
+
*
|
|
528
|
+
* - `expect.*` (this module) builds a `{type, vars[]}` **assertion record**
|
|
529
|
+
* stored on a `tests` entry of a query/function/middleware. It is data, not a
|
|
530
|
+
* step, and it only ever appears inside `tests: [{ expect: [...] }]`.
|
|
531
|
+
* - `s.expect.*` builds an assertion **statement** that runs inside a
|
|
532
|
+
* `workflowTest()` stack. It is a stack item like any other.
|
|
533
|
+
*
|
|
534
|
+
* They are not interchangeable and the type system enforces it: these return
|
|
535
|
+
* {@link TestExpect}, those return `Statement`.
|
|
536
|
+
*
|
|
537
|
+
* ## The subject comes first
|
|
538
|
+
*
|
|
539
|
+
* Every matcher takes its subject as the first argument — almost always
|
|
540
|
+
* {@link resp}. Argument ORDER is the assertion's meaning:
|
|
541
|
+
* `expect.to_contain(resp("tags"), c.text("new"))` asserts the response's tags
|
|
542
|
+
* contain `"new"`, not the reverse.
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
/** The stored matcher names, in the engine's own order. */
|
|
546
|
+
declare const TEST_EXPECT_TYPES: readonly ["to_be_defined", "to_be_empty", "to_be_false", "to_be_greater_than", "to_be_in_the_future", "to_be_in_the_past", "to_be_less_than", "to_be_null", "to_be_true", "to_be_within", "to_contain", "to_end_with", "to_equal", "to_match", "to_not_be_defined", "to_not_be_null", "to_not_equal", "to_start_with", "to_throw"];
|
|
547
|
+
/** One of the 19 stored matcher names. */
|
|
548
|
+
type TestExpectType = (typeof TEST_EXPECT_TYPES)[number];
|
|
549
|
+
/** A stored assertion — `{type, vars[]}`, the `mvp_test_expect` shape. */
|
|
550
|
+
interface TestExpect {
|
|
551
|
+
type: TestExpectType;
|
|
552
|
+
vars: Value[];
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Unit-test assertions, for the `expect` list of a `tests` entry.
|
|
556
|
+
*
|
|
557
|
+
* See the module header for why this is NOT `s.expect.*`.
|
|
558
|
+
*
|
|
559
|
+
* ```ts
|
|
560
|
+
* query({
|
|
561
|
+
* name: "score",
|
|
562
|
+
* verb: "POST",
|
|
563
|
+
* input: { score: input.int() },
|
|
564
|
+
* tests: [
|
|
565
|
+
* {
|
|
566
|
+
* name: "adds one",
|
|
567
|
+
* input: { score: c.int(1) },
|
|
568
|
+
* expect: [expect.to_equal(resp(), c.int(2))],
|
|
569
|
+
* },
|
|
570
|
+
* ],
|
|
571
|
+
* stack: [s.set_var("x1", c.expression("$input.score + 1"))],
|
|
572
|
+
* response: ref("x1"),
|
|
573
|
+
* });
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
declare const expect: {
|
|
577
|
+
/** The subject is set (not undefined). */
|
|
578
|
+
readonly to_be_defined: (subject: Value) => TestExpect;
|
|
579
|
+
/** The subject is undefined. */
|
|
580
|
+
readonly to_not_be_defined: (subject: Value) => TestExpect;
|
|
581
|
+
/** The subject is empty — `""`, `[]`, `{}`, `0`, or null. */
|
|
582
|
+
readonly to_be_empty: (subject: Value) => TestExpect;
|
|
583
|
+
/** The subject is `null`. */
|
|
584
|
+
readonly to_be_null: (subject: Value) => TestExpect;
|
|
585
|
+
/** The subject is not `null`. */
|
|
586
|
+
readonly to_not_be_null: (subject: Value) => TestExpect;
|
|
587
|
+
/** The subject is boolean true. */
|
|
588
|
+
readonly to_be_true: (subject: Value) => TestExpect;
|
|
589
|
+
/** The subject is boolean false. */
|
|
590
|
+
readonly to_be_false: (subject: Value) => TestExpect;
|
|
591
|
+
/** The subject is a timestamp later than now. */
|
|
592
|
+
readonly to_be_in_the_future: (subject: Value) => TestExpect;
|
|
593
|
+
/** The subject is a timestamp earlier than now. */
|
|
594
|
+
readonly to_be_in_the_past: (subject: Value) => TestExpect;
|
|
595
|
+
/** `subject === value`. */
|
|
596
|
+
readonly to_equal: (subject: Value, value: Value) => TestExpect;
|
|
597
|
+
/** `subject !== value`. */
|
|
598
|
+
readonly to_not_equal: (subject: Value, value: Value) => TestExpect;
|
|
599
|
+
/** `subject > value`, both read as numbers. */
|
|
600
|
+
readonly to_be_greater_than: (subject: Value, value: Value) => TestExpect;
|
|
601
|
+
/** `subject < value`, both read as numbers. */
|
|
602
|
+
readonly to_be_less_than: (subject: Value, value: Value) => TestExpect;
|
|
603
|
+
/** A list subject contains the item; a text subject contains the substring. */
|
|
604
|
+
readonly to_contain: (subject: Value, item: Value) => TestExpect;
|
|
605
|
+
/** The subject, as text, starts with `prefix`. */
|
|
606
|
+
readonly to_start_with: (subject: Value, prefix: Value) => TestExpect;
|
|
607
|
+
/** The subject, as text, ends with `suffix`. */
|
|
608
|
+
readonly to_end_with: (subject: Value, suffix: Value) => TestExpect;
|
|
609
|
+
/**
|
|
610
|
+
* The subject matches a regular expression. `pattern` is a PCRE pattern
|
|
611
|
+
* INCLUDING its delimiters — build it with `c.regex(...)` rather than by hand;
|
|
612
|
+
* a bare `"^a"` is not a runnable pattern and the engine's match silently
|
|
613
|
+
* fails rather than erroring.
|
|
614
|
+
*/
|
|
615
|
+
readonly to_match: (subject: Value, pattern: Value) => TestExpect;
|
|
616
|
+
/**
|
|
617
|
+
* `min < subject < max`, both bounds EXCLUSIVE and all three read as numbers.
|
|
618
|
+
* A subject equal to either bound fails.
|
|
619
|
+
*/
|
|
620
|
+
readonly to_be_within: (subject: Value, min: Value, max: Value) => TestExpect;
|
|
621
|
+
/**
|
|
622
|
+
* The run raised an error. Pass `exception` to require that text to appear in
|
|
623
|
+
* the error message (a case-insensitive substring, not an exact match);
|
|
624
|
+
* omit it to accept ANY error.
|
|
625
|
+
*
|
|
626
|
+
* Unlike every other matcher this one takes no subject: the thing under
|
|
627
|
+
* assertion is the run's failure, and the engine stores the response slot as
|
|
628
|
+
* a fixed first var. That is what the editor writes, and it is preserved so
|
|
629
|
+
* an authored test and a pulled one are the same bytes.
|
|
630
|
+
*/
|
|
631
|
+
readonly to_throw: (exception?: Value) => TestExpect;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The lean statement-input entry — `{name, tag, value, filters}` — shared by
|
|
636
|
+
* every call site that emits the *lean* input form (no rich
|
|
637
|
+
* `ignore`/`expand`/`children`):
|
|
638
|
+
*
|
|
639
|
+
* - addon `input[]` bindings ({@link ../statements/special/addon-encode.ts}),
|
|
640
|
+
* - the lean db family (`db.add_or_edit` / the bulk ops in
|
|
641
|
+
* {@link ../statements/special/db.ts}),
|
|
642
|
+
* - lean spec-driven statements ({@link ../statements/schema-dsl/interpret.ts}).
|
|
643
|
+
*
|
|
644
|
+
* One builder so the shape lives in exactly one place. (JSON key order is not
|
|
645
|
+
* significant to the engine parser, which reads by field name.)
|
|
646
|
+
*/
|
|
647
|
+
|
|
648
|
+
/** A lean input binding — `{name, tag, value, filters}`, no `ignore/expand/children`. */
|
|
649
|
+
interface LeanInput {
|
|
650
|
+
name: string;
|
|
651
|
+
tag: string;
|
|
652
|
+
value: string;
|
|
653
|
+
filters: unknown[];
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* A saved UNIT TEST — one entry of the `test[]` a query, function, or
|
|
658
|
+
* middleware carries. This is the thing the Xano UI calls a unit test: a named
|
|
659
|
+
* set of inputs, run against the object it hangs off, with assertions on the
|
|
660
|
+
* response.
|
|
661
|
+
*
|
|
662
|
+
* ## Not `workflowTest()`
|
|
663
|
+
*
|
|
664
|
+
* A {@link ../kinds/workflow-test.js WorkflowTestDef} is a standalone top-level
|
|
665
|
+
* object with its own stack that calls other objects. A unit test is a PROPERTY
|
|
666
|
+
* of one object and has no stack of its own — the object under test is the
|
|
667
|
+
* stack. The two never interact.
|
|
668
|
+
*
|
|
669
|
+
* ## Mocks live on the statements, not here
|
|
670
|
+
*
|
|
671
|
+
* A test can substitute a value for any statement in the object's stack, so the
|
|
672
|
+
* step returns the mock instead of doing its work. That is authored on the
|
|
673
|
+
* STATEMENT (`s.*(…, { mock: { "<test name>": … } })`) and keyed by this test's
|
|
674
|
+
* name; see {@link ./test-mocks.js}.
|
|
675
|
+
*/
|
|
676
|
+
|
|
677
|
+
interface TestDef {
|
|
678
|
+
/**
|
|
679
|
+
* The test's display name, unique within the object. It is also the key a
|
|
680
|
+
* statement's `mock` map uses to name this test, which is why a duplicate is
|
|
681
|
+
* refused rather than merged.
|
|
682
|
+
*/
|
|
683
|
+
name: string;
|
|
684
|
+
/**
|
|
685
|
+
* Explicit stored `id` (this test's identity). Defaults to one derived from
|
|
686
|
+
* the owning object and the test name; set it to keep identity across a
|
|
687
|
+
* rename. A pulled test always carries the id Xano minted for it.
|
|
688
|
+
*/
|
|
689
|
+
id?: string;
|
|
690
|
+
description?: string;
|
|
691
|
+
/**
|
|
692
|
+
* Datasource to run against. `""` (the default) means an EMPTY datasource —
|
|
693
|
+
* the recommended setting. Any other value names a datasource the engine
|
|
694
|
+
* **clones** before the test runs; `"live"` warns at encode time.
|
|
695
|
+
*/
|
|
696
|
+
datasource?: string;
|
|
697
|
+
/**
|
|
698
|
+
* The inputs to call the object with, by input name — the same tagged values
|
|
699
|
+
* used everywhere else (`c.*`, `ref`, …), not plain JS scalars.
|
|
700
|
+
*/
|
|
701
|
+
input?: Record<string, Value>;
|
|
702
|
+
/**
|
|
703
|
+
* The assertions, built with the top-level `expect.*` helpers — NOT
|
|
704
|
+
* `s.expect.*`, which builds workflow-test statements. See
|
|
705
|
+
* {@link ../values/expect.js}.
|
|
706
|
+
*/
|
|
707
|
+
expect?: TestExpect[];
|
|
708
|
+
/**
|
|
709
|
+
* An auth token to run the test as an authenticated caller.
|
|
710
|
+
*
|
|
711
|
+
* Accepted so an authored test can supply one, but note that a pull does NOT
|
|
712
|
+
* bring it back: a token is an expiring credential rather than authored
|
|
713
|
+
* configuration, so `codegen` reports a stored one as a deliberate omission
|
|
714
|
+
* instead of writing it into a committed tree.
|
|
715
|
+
*/
|
|
716
|
+
token?: string;
|
|
717
|
+
}
|
|
718
|
+
/** The stored `mvp_test` shape. */
|
|
719
|
+
interface TestXdo {
|
|
720
|
+
datasource: string;
|
|
721
|
+
description: string;
|
|
722
|
+
expect: TestExpect[];
|
|
723
|
+
id: string;
|
|
724
|
+
input: LeanInput[];
|
|
725
|
+
name: string;
|
|
726
|
+
/**
|
|
727
|
+
* Always emitted, blank when unset. The stored schema marks it optional, but a
|
|
728
|
+
* live round trip shows the engine persists `""` regardless — omitting it read
|
|
729
|
+
* as a failed round trip on every test.
|
|
730
|
+
*/
|
|
731
|
+
token: string;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Declare a saved unit test. Sugar for a plain object literal — use it for the
|
|
735
|
+
* inferred type and the doc hover; `tests: [{ … }]` is equally valid.
|
|
736
|
+
*/
|
|
737
|
+
declare function test(def: TestDef): TestDef;
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* TypeScript mirror of Xano's stored `mvp_function` JSON contract (the
|
|
741
|
+
* importable `xdo` subset, flattened per KTD-1 — no `xdo` wrapper).
|
|
742
|
+
*
|
|
743
|
+
* Field shapes are taken from the real Xano engine's persisted function shape.
|
|
744
|
+
*
|
|
745
|
+
* The SDK emits only authored fields. Server/auto-generated keys (`id`,
|
|
746
|
+
* `created_at`, `_xsid`, `@guid`, `@index`, `guid`, `_draft`, `stack_id`, …)
|
|
747
|
+
* are generated by the engine on import and are NEVER emitted here; the test
|
|
748
|
+
* normalizer (U6) strips them from the fixture before comparison.
|
|
749
|
+
*/
|
|
750
|
+
/**
|
|
751
|
+
* Xano tagged-value tags (verified against the Xano engine). The runtime
|
|
752
|
+
* `TAGS` tuple is the single source of truth; the `Tag` type is derived from it
|
|
753
|
+
* so the manifest can enumerate the tag catalog without drifting from the type.
|
|
754
|
+
*/
|
|
755
|
+
|
|
756
|
+
declare const TAGS: readonly ["const", "const:int", "const:decimal", "const:bool", "const:array", "const:obj", "const:null", "const:epochms", "const:expr", "const:expr2", "var", "input", "auth", "env", "setting", "col", "output", "response", "trycatch", "toolset"];
|
|
757
|
+
type Tag = (typeof TAGS)[number];
|
|
758
|
+
/**
|
|
759
|
+
* A filter in a `mvp_filter` chain: `{name, disabled, arg}`.
|
|
760
|
+
*
|
|
761
|
+
* `N` is a **phantom** carrier for the filter's name, set by the `fl.*` factories
|
|
762
|
+
* (`fl.upper()` is a `FilterXdo<"upper">`) and never present at runtime — `name`
|
|
763
|
+
* already holds it. It exists so a chain's RESULT type can be folded statically:
|
|
764
|
+
* knowing a chain is `["trim", "upper"]` rather than `string[]` is what lets
|
|
765
|
+
* `withFilters`/`asFilters` report `string` instead of `unknown`.
|
|
766
|
+
*
|
|
767
|
+
* It defaults to `string`, so every existing `FilterXdo` position — stored
|
|
768
|
+
* envelopes, decoder output, the `filter()` escape hatch — keeps working
|
|
769
|
+
* unchanged and simply folds to `unknown`, the honest floor for a chain whose
|
|
770
|
+
* names are not statically known.
|
|
771
|
+
*/
|
|
772
|
+
interface FilterXdo<N extends string = string> {
|
|
773
|
+
name: string;
|
|
774
|
+
/**
|
|
775
|
+
* Whether the engine skips this filter. OPTIONAL because the engine stores it
|
|
776
|
+
* both ways: `filter()` and every `fl.*` constructor write it, but a filter
|
|
777
|
+
* persisted by an older editor may carry no `disabled` key at all.
|
|
778
|
+
*
|
|
779
|
+
* Required here until now, which made a pulled workspace fail its own
|
|
780
|
+
* type-check: codegen emits a stored filter verbatim when no `fl.*` form
|
|
781
|
+
* reproduces it, and a verbatim emit of a filter stored without the key does
|
|
782
|
+
* not have it (issue #134). Writing `disabled: false` in would have been the
|
|
783
|
+
* other repair, and it is the wrong one — it ADDS a key the engine did not
|
|
784
|
+
* store, so the re-encode would no longer match the stored bytes and the value
|
|
785
|
+
* would degrade further rather than round-trip.
|
|
786
|
+
*
|
|
787
|
+
* Read it as `filter.disabled ?? false`.
|
|
788
|
+
*/
|
|
789
|
+
disabled?: boolean;
|
|
790
|
+
arg: TaggedValue[];
|
|
791
|
+
/** Phantom: the filter's name at the type level. Never present at runtime. */
|
|
792
|
+
readonly __filter?: N;
|
|
793
|
+
}
|
|
794
|
+
/** The core tagged value: `{value, tag, filters}`. Values serialize as strings. */
|
|
795
|
+
interface TaggedValue {
|
|
796
|
+
value: string;
|
|
797
|
+
tag: Tag;
|
|
798
|
+
filters: FilterXdo[];
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* An input/column "method" (e.g. `trim`, or `min:8`). `arg` holds the filter's
|
|
802
|
+
* literal arguments as scalars (`["8"]`); the persisted corpus is numeric/string
|
|
803
|
+
* inconsistent on them (`[8]` vs `["10"]`), the same artifact class as
|
|
804
|
+
* `const:int` tagged values — the normalizer coerces both to strings.
|
|
805
|
+
*/
|
|
806
|
+
interface MethodXdo {
|
|
807
|
+
name: string;
|
|
808
|
+
disabled: boolean;
|
|
809
|
+
arg: Array<string | number>;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* A field-bearing entry — the shape shared by function/query inputs and table
|
|
813
|
+
* columns (KTD-6). In the persisted form the two are byte-identical: both use
|
|
814
|
+
* `customize:{}`, numeric `market_item` ids, an `_xsid` placeholder, and a
|
|
815
|
+
* present `description`. (The older parser generation emitted `customize:""` /
|
|
816
|
+
* string ids for inputs; the live `mvp_query`/`mvp_dbo` rows show they converge.)
|
|
817
|
+
*/
|
|
818
|
+
/**
|
|
819
|
+
* A field's array length bounds, as PERSISTED.
|
|
820
|
+
*
|
|
821
|
+
* Both members are `json`-typed in the engine rather than numeric — the editor
|
|
822
|
+
* declares them that way and every consumer reads them through a coercion
|
|
823
|
+
* (`+list.max`) — so a set bound arrives as `5` or as `"5"`, and an UNSET one as
|
|
824
|
+
* whichever empty the editor serialized: `""` on 8,814 fields in the sweep, `{}`
|
|
825
|
+
* on two. All four spellings are real bytes, so the type admits them rather than
|
|
826
|
+
* asserting a tidiness the engine does not have.
|
|
827
|
+
*/
|
|
828
|
+
type ListBoundsXdo = {
|
|
829
|
+
min?: string | number | Record<string, never>;
|
|
830
|
+
max?: string | number | Record<string, never>;
|
|
831
|
+
};
|
|
832
|
+
interface FieldXdo {
|
|
833
|
+
name: string;
|
|
834
|
+
type: string;
|
|
835
|
+
_xsid: string;
|
|
836
|
+
description?: string;
|
|
837
|
+
nullable: boolean;
|
|
838
|
+
/**
|
|
839
|
+
* Absent for types that persist no default (`uuid`) — the engine drops the
|
|
840
|
+
* key rather than storing it empty, and the two are different stored bytes.
|
|
841
|
+
*/
|
|
842
|
+
default?: string;
|
|
843
|
+
merge: boolean;
|
|
844
|
+
hidden: unknown[];
|
|
845
|
+
override: unknown[];
|
|
846
|
+
customize: string | Record<string, unknown>;
|
|
847
|
+
required: boolean;
|
|
848
|
+
values: unknown[];
|
|
849
|
+
mode: string;
|
|
850
|
+
format: string;
|
|
851
|
+
sensitive: boolean;
|
|
852
|
+
list: ListBoundsXdo;
|
|
853
|
+
vector: {
|
|
854
|
+
size: number;
|
|
855
|
+
};
|
|
856
|
+
access: string;
|
|
857
|
+
style: {
|
|
858
|
+
type: string;
|
|
859
|
+
};
|
|
860
|
+
children: unknown[];
|
|
861
|
+
methods: MethodXdo[];
|
|
862
|
+
market_item: {
|
|
863
|
+
id: string | number;
|
|
864
|
+
version: string | number;
|
|
865
|
+
guid: string;
|
|
866
|
+
};
|
|
867
|
+
is_settings_registry: boolean;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* A single function/query input (`input[]` entry). Shares the persisted field
|
|
871
|
+
* shape with table columns ({@link FieldXdo}) — `customize:{}`, numeric
|
|
872
|
+
* `market_item` ids, `_xsid` present.
|
|
873
|
+
*/
|
|
874
|
+
interface InputXdo {
|
|
875
|
+
name: string;
|
|
876
|
+
type: string;
|
|
877
|
+
_xsid: string;
|
|
878
|
+
description: string;
|
|
879
|
+
nullable: boolean;
|
|
880
|
+
default: string;
|
|
881
|
+
merge: boolean;
|
|
882
|
+
hidden: unknown[];
|
|
883
|
+
override: unknown[];
|
|
884
|
+
customize: string | Record<string, unknown>;
|
|
885
|
+
required: boolean;
|
|
886
|
+
values: unknown[];
|
|
887
|
+
mode: string;
|
|
888
|
+
format: string;
|
|
889
|
+
sensitive: boolean;
|
|
890
|
+
list: ListBoundsXdo;
|
|
891
|
+
vector: {
|
|
892
|
+
size: number;
|
|
893
|
+
};
|
|
894
|
+
access: string;
|
|
895
|
+
style: {
|
|
896
|
+
type: string;
|
|
897
|
+
};
|
|
898
|
+
children: unknown[];
|
|
899
|
+
methods: MethodXdo[];
|
|
900
|
+
market_item: {
|
|
901
|
+
id: string | number;
|
|
902
|
+
version: string | number;
|
|
903
|
+
guid: string;
|
|
904
|
+
};
|
|
905
|
+
is_settings_registry: boolean;
|
|
906
|
+
}
|
|
907
|
+
/** A response item (`result[]` entry). The persisted form always carries `_xsid` and `disabled`. */
|
|
908
|
+
interface ResultItemXdo {
|
|
909
|
+
filters: FilterXdo[];
|
|
910
|
+
name: string;
|
|
911
|
+
tag: Tag;
|
|
912
|
+
value: string;
|
|
913
|
+
_xsid: string;
|
|
914
|
+
disabled: boolean;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* A stack statement (`run[]` entry). The persisted form is a **uniform** 12-key
|
|
918
|
+
* envelope, identical across every statement type (confirmed against live
|
|
919
|
+
* `mvp_query`/`mvp_tool`): every member is always present, with empty defaults.
|
|
920
|
+
* `context` varies per statement (set_var carries a TaggedValue; conditional
|
|
921
|
+
* carries nested runs — see below).
|
|
922
|
+
*/
|
|
923
|
+
interface StackItemXdo {
|
|
924
|
+
as: string;
|
|
925
|
+
name: string;
|
|
926
|
+
_xsid: string;
|
|
927
|
+
addon: unknown[];
|
|
928
|
+
input: unknown[];
|
|
929
|
+
mocks: unknown;
|
|
930
|
+
output: unknown;
|
|
931
|
+
context: unknown;
|
|
932
|
+
runtime: unknown;
|
|
933
|
+
disabled: boolean;
|
|
934
|
+
description: string;
|
|
935
|
+
settings_registry: unknown[] | null;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* A statement `input[]` argument binding — a named tagged value, distinct from
|
|
939
|
+
* the rich `InputXdo` field shape. Lean form is `{name,value,tag,filters}`;
|
|
940
|
+
* "full" statements (api_request, db ops, file ops) add `ignore/expand/children`.
|
|
941
|
+
*/
|
|
942
|
+
interface StatementInputXdo {
|
|
943
|
+
name: string;
|
|
944
|
+
value: string;
|
|
945
|
+
tag: Tag;
|
|
946
|
+
filters: FilterXdo[];
|
|
947
|
+
ignore?: boolean;
|
|
948
|
+
expand?: boolean;
|
|
949
|
+
children?: unknown[];
|
|
950
|
+
}
|
|
951
|
+
/** A single binary comparison operand inside a conditional expression. */
|
|
952
|
+
interface ExprOperand {
|
|
953
|
+
operand: string;
|
|
954
|
+
tag: Tag;
|
|
955
|
+
filters: FilterXdo[];
|
|
956
|
+
ignore_empty?: boolean;
|
|
957
|
+
}
|
|
958
|
+
/** One `statement`-type entry in a conditional's `expr.expression[]`. */
|
|
959
|
+
interface ExprStatement {
|
|
960
|
+
type: "statement";
|
|
961
|
+
or: boolean;
|
|
962
|
+
group: {
|
|
963
|
+
expression: unknown[];
|
|
964
|
+
};
|
|
965
|
+
statement: {
|
|
966
|
+
op: string;
|
|
967
|
+
left: ExprOperand;
|
|
968
|
+
right: ExprOperand;
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
/** A `{type:"group"}` entry — a nested AND/OR subtree of expression nodes. */
|
|
972
|
+
interface ExprGroup {
|
|
973
|
+
type: "group";
|
|
974
|
+
or: boolean;
|
|
975
|
+
group: {
|
|
976
|
+
expression: ExprNode[];
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
/** One node in an expression tree: a comparison statement or a nested group. */
|
|
980
|
+
type ExprNode = ExprStatement | ExprGroup;
|
|
981
|
+
/** `context` for a `mvp:conditional` statement. */
|
|
982
|
+
interface ConditionalContext {
|
|
983
|
+
expr: {
|
|
984
|
+
expression: ExprNode[];
|
|
985
|
+
};
|
|
986
|
+
if: {
|
|
987
|
+
run: StackItemXdo[];
|
|
988
|
+
};
|
|
989
|
+
/**
|
|
990
|
+
* The elif stack — a `run[]` of `mvp:conditional_elif` statements, each its own
|
|
991
|
+
* `{ expr, if:{run} }` branch (mirrors how `mvp:switch` stacks `switch_case`).
|
|
992
|
+
* The engine always persists this key, `{run:[]}` when there are no elif
|
|
993
|
+
* branches (verified against a live conditional capture).
|
|
994
|
+
*/
|
|
995
|
+
elif: {
|
|
996
|
+
run: StackItemXdo[];
|
|
997
|
+
};
|
|
998
|
+
else: {
|
|
999
|
+
run: StackItemXdo[];
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
/** `context` for a `mvp:conditional_elif` statement — one leaf branch of a conditional's elif stack. */
|
|
1003
|
+
interface ConditionalElifContext {
|
|
1004
|
+
expr: {
|
|
1005
|
+
expression: ExprNode[];
|
|
1006
|
+
};
|
|
1007
|
+
if: {
|
|
1008
|
+
run: StackItemXdo[];
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
interface CacheXdo {
|
|
1012
|
+
active: boolean;
|
|
1013
|
+
ttl: number;
|
|
1014
|
+
input: boolean;
|
|
1015
|
+
auth: boolean;
|
|
1016
|
+
datasource: boolean;
|
|
1017
|
+
ip: boolean;
|
|
1018
|
+
headers: unknown[];
|
|
1019
|
+
env: unknown[];
|
|
1020
|
+
}
|
|
1021
|
+
interface HistoryXdo {
|
|
1022
|
+
inherit: boolean;
|
|
1023
|
+
enabled: boolean;
|
|
1024
|
+
limit: number;
|
|
1025
|
+
}
|
|
1026
|
+
interface MiddlewareXdo {
|
|
1027
|
+
pre_customize: boolean;
|
|
1028
|
+
post_customize: boolean;
|
|
1029
|
+
pre: unknown[];
|
|
1030
|
+
post: unknown[];
|
|
1031
|
+
}
|
|
1032
|
+
/** The flattened authored function envelope the SDK emits. */
|
|
1033
|
+
interface FunctionXdo {
|
|
1034
|
+
name: string;
|
|
1035
|
+
description: string;
|
|
1036
|
+
docs: string;
|
|
1037
|
+
workspace: {
|
|
1038
|
+
id: number;
|
|
1039
|
+
};
|
|
1040
|
+
branch: {
|
|
1041
|
+
id: number;
|
|
1042
|
+
};
|
|
1043
|
+
cache: CacheXdo;
|
|
1044
|
+
history: HistoryXdo;
|
|
1045
|
+
middleware: MiddlewareXdo;
|
|
1046
|
+
tag: unknown[];
|
|
1047
|
+
input: InputXdo[];
|
|
1048
|
+
result: ResultItemXdo[];
|
|
1049
|
+
run: StackItemXdo[];
|
|
1050
|
+
test: TestXdo[];
|
|
1051
|
+
market_item: {
|
|
1052
|
+
id: number;
|
|
1053
|
+
version: number;
|
|
1054
|
+
guid: string;
|
|
1055
|
+
};
|
|
1056
|
+
shared_workspace: {
|
|
1057
|
+
is_shared: boolean;
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* The persisted row: authored envelope plus the server columns the engine
|
|
1062
|
+
* adds on import. Used ONLY by the test normalizer — the SDK never emits these.
|
|
1063
|
+
*/
|
|
1064
|
+
type PersistedFunctionRow = FunctionXdo & {
|
|
1065
|
+
id: number;
|
|
1066
|
+
created_at: string;
|
|
1067
|
+
updated_at: string;
|
|
1068
|
+
deleted_at?: string;
|
|
1069
|
+
guid: string;
|
|
1070
|
+
_draft?: boolean;
|
|
1071
|
+
_xsid?: string;
|
|
1072
|
+
market_item?: {
|
|
1073
|
+
id: number;
|
|
1074
|
+
version: number;
|
|
1075
|
+
guid: string;
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* A statement's per-test MOCK — the value a step returns instead of doing its
|
|
1081
|
+
* work, when one specific test runs.
|
|
1082
|
+
*
|
|
1083
|
+
* Authored keyed by TEST NAME, which is what the Xano editor shows and what
|
|
1084
|
+
* XanoScript spells. The stored form is keyed by test ID; the rewrite happens
|
|
1085
|
+
* at kind-encode time, the only place that knows both the stack and the test
|
|
1086
|
+
* list (see `../kinds/test-mocks.js`).
|
|
1087
|
+
*
|
|
1088
|
+
* A mock applies ONLY while its test runs. It has no effect on a normal
|
|
1089
|
+
* request, so mocking a step does not change what the deployed endpoint does.
|
|
1090
|
+
*/
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* One mock: a value, or a value plus `enabled`.
|
|
1094
|
+
*
|
|
1095
|
+
* The bare form means enabled. The object form exists so a mock can be kept
|
|
1096
|
+
* around switched off — the editor stores a disabled mock rather than deleting
|
|
1097
|
+
* it, and dropping it on encode would lose an authored artefact.
|
|
1098
|
+
*/
|
|
1099
|
+
type MockEntry = Value | {
|
|
1100
|
+
value: Value;
|
|
1101
|
+
enabled?: boolean;
|
|
1102
|
+
};
|
|
1103
|
+
/** A statement's mocks, keyed by the name of the test each applies to. */
|
|
1104
|
+
type MockMap = Record<string, MockEntry>;
|
|
1105
|
+
/** The stored `mvp_test_mock` shape. */
|
|
1106
|
+
interface MockXdo {
|
|
1107
|
+
value: string;
|
|
1108
|
+
tag: string;
|
|
1109
|
+
filters: unknown[];
|
|
1110
|
+
enabled: boolean;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Statement model, base envelope encoder, and registry (U5 — the KTD-4
|
|
1115
|
+
* extensibility seam).
|
|
1116
|
+
*
|
|
1117
|
+
* The real stored statement shape (from the golden fixture's `run[0]`) is lean:
|
|
1118
|
+
* `{name, as?, context, input}`. The base encoder fills the common envelope
|
|
1119
|
+
* (`input: []` default) so each concrete statement factory only declares its
|
|
1120
|
+
* `name`, optional `as`, and `context`. The registry maps statement name →
|
|
1121
|
+
* factory so the eventual ~500-statement catalog plugs in here.
|
|
1122
|
+
*/
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* The type-level contract linking a branded db statement (the **producer** —
|
|
1126
|
+
* `db.get`/`db.query`/`db.add`/`db.edit`/`db.patch`/`db.add_or_edit`/`db.has`/
|
|
1127
|
+
* `db.bulk.patch`/`db.bulk.delete`, each returning `Statement & AsShapeBrand<…>`)
|
|
1128
|
+
* to `InferResponse`'s single-variable trace (the **consumer** — `TraceVar`,
|
|
1129
|
+
* which destructures this shape). Both `__as` (the stack variable the statement
|
|
1130
|
+
* binds) and `__shape` (the row shape it produces) are phantom carriers — never
|
|
1131
|
+
* present at runtime. Naming the contract here keeps producer and consumer
|
|
1132
|
+
* compiler-linked, so any further branded statement joins the trace by extending
|
|
1133
|
+
* this type with zero edits to the trace logic.
|
|
1134
|
+
*/
|
|
1135
|
+
type AsShapeBrand<As extends string, Shape> = {
|
|
1136
|
+
readonly __as: As;
|
|
1137
|
+
readonly __shape: Shape;
|
|
1138
|
+
};
|
|
1139
|
+
/**
|
|
1140
|
+
* The two envelope members every stack item carries, whatever it does.
|
|
1141
|
+
*
|
|
1142
|
+
* They are editor affordances rather than statement arguments — `disabled` is
|
|
1143
|
+
* how a step is commented out (it stays in the stack; the run engine skips it),
|
|
1144
|
+
* and `description` is the note shown on the step. `encodeStatement` writes both
|
|
1145
|
+
* for every statement, so every factory accepts them: the generated ones as two
|
|
1146
|
+
* more optional fields on their argument object, the positional specials as a
|
|
1147
|
+
* trailing options argument.
|
|
1148
|
+
*
|
|
1149
|
+
* Both are elided at their defaults on both sides of a round trip, so setting one
|
|
1150
|
+
* to `false`/`""` is the same bytes as omitting it.
|
|
1151
|
+
*/
|
|
1152
|
+
interface StatementAnnotations {
|
|
1153
|
+
/** Leave the step in the stack but skip it at runtime — Xano's "disable step". */
|
|
1154
|
+
disabled?: boolean;
|
|
1155
|
+
/** Free-text note on the step, shown in the editor beside it. */
|
|
1156
|
+
description?: string;
|
|
1157
|
+
/**
|
|
1158
|
+
* Per-test MOCKS — what this step returns instead of running, keyed by the
|
|
1159
|
+
* name of the test the mock applies to.
|
|
1160
|
+
*
|
|
1161
|
+
* `{ "happy path": c.int(1) }` makes the step return `1` while the test named
|
|
1162
|
+
* "happy path" runs, and changes nothing about a normal request. Wrap an
|
|
1163
|
+
* entry as `{ value, enabled: false }` to keep a mock switched off.
|
|
1164
|
+
*
|
|
1165
|
+
* Every name must match a test declared on the object this statement belongs
|
|
1166
|
+
* to; an unmatched name throws at encode time, because the engine silently
|
|
1167
|
+
* ignores a mock whose key is not a real test id.
|
|
1168
|
+
*/
|
|
1169
|
+
mock?: MockMap;
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* {@link StatementAnnotations} plus the result-filter option, for the statements
|
|
1173
|
+
* that bind an `as` variable.
|
|
1174
|
+
*
|
|
1175
|
+
* Split from the annotations rather than folded into them so `asFilters` is only
|
|
1176
|
+
* offered where there is a binding to attach it to: a statement that returns
|
|
1177
|
+
* nothing (`precondition`, `switch`, `while`, …) should not surface the option
|
|
1178
|
+
* in autocomplete at all. The runtime guard in {@link assertBindsAs} still
|
|
1179
|
+
* backs this up for callers that reach past the types.
|
|
1180
|
+
*/
|
|
1181
|
+
interface StatementOptions extends StatementAnnotations {
|
|
1182
|
+
/**
|
|
1183
|
+
* Filters piped onto the result before it is bound — the editor's
|
|
1184
|
+
* `return as token | upper`.
|
|
1185
|
+
*
|
|
1186
|
+
* Authored from the same `fl.*` catalog as value filters and applied in
|
|
1187
|
+
* order, so `asFilters: [fl.trim(), fl.upper()]` trims and then upper-cases.
|
|
1188
|
+
*
|
|
1189
|
+
* The bound variable is RETYPED by the chain: `InferResponse` folds each
|
|
1190
|
+
* filter's declared result, so a `db.query` bound through `[fl.count()]` is a
|
|
1191
|
+
* `number`. Filters the engine declares as returning `any` (`get`, `set`,
|
|
1192
|
+
* `json_decode`, …) fold to `unknown` — see `values/filter-result.ts`.
|
|
1193
|
+
*/
|
|
1194
|
+
asFilters?: FilterXdo[];
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Apply {@link StatementOptions} to a built statement.
|
|
1198
|
+
*
|
|
1199
|
+
* Only members that were actually authored are copied, so a factory's own
|
|
1200
|
+
* `description` (a few statements route one) is not clobbered by an absent key.
|
|
1201
|
+
*
|
|
1202
|
+
* `asFilters` merges into the `output` envelope rather than replacing it: a db
|
|
1203
|
+
* statement's column selection (`output.items`) and its result filters live in
|
|
1204
|
+
* the same block, and dropping one to write the other would silently discard
|
|
1205
|
+
* whichever the factory set first.
|
|
1206
|
+
*/
|
|
1207
|
+
declare function annotate<T extends Statement>(stmt: T, a?: StatementOptions): T;
|
|
1208
|
+
/** What a statement factory returns before base-envelope encoding. */
|
|
1209
|
+
interface Statement {
|
|
1210
|
+
name: string;
|
|
1211
|
+
as?: string;
|
|
1212
|
+
context: unknown;
|
|
1213
|
+
input?: unknown[];
|
|
1214
|
+
/** `output` envelope. Lean (`{filters:[]}`) or rich (`{customize,filters,items}`) forms are both accepted and normalized to the full rich form. */
|
|
1215
|
+
output?: unknown;
|
|
1216
|
+
/** Statement description (defaults to `""`). */
|
|
1217
|
+
description?: string;
|
|
1218
|
+
/** Settings-registry bindings (defaults to `null`). */
|
|
1219
|
+
settings_registry?: unknown[] | null;
|
|
1220
|
+
/** Attached addons (defaults to `[]`). */
|
|
1221
|
+
addon?: unknown[];
|
|
1222
|
+
/** Async/runtime block (e.g. `mvp:call_agent`'s `{ mode }`); defaults to `null`. */
|
|
1223
|
+
runtime?: unknown;
|
|
1224
|
+
/**
|
|
1225
|
+
* Encoded per-test mocks, keyed by test NAME until the owning kind rewrites
|
|
1226
|
+
* the keys to test ids. Authored via the `mock` option, not set directly.
|
|
1227
|
+
*/
|
|
1228
|
+
mocks?: Record<string, MockXdo> | unknown;
|
|
1229
|
+
/** Whether the statement is disabled in the stack (defaults to `false`). */
|
|
1230
|
+
disabled?: boolean;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Return a statement sequence from a helper **without losing the tuple**
|
|
1234
|
+
* (issue #11).
|
|
1235
|
+
*
|
|
1236
|
+
* `InferResponse` traces the `as` binding a `response` names by walking the
|
|
1237
|
+
* stack's TUPLE type. Spreading anything typed `Statement[]` widens the whole
|
|
1238
|
+
* array, and every binding — including ones declared *after* the spread —
|
|
1239
|
+
* becomes invisible, so the response silently resolves to
|
|
1240
|
+
* {@link StackTupleWidened}. A helper that emits more than one statement has to
|
|
1241
|
+
* return an array, so any shared helper triggers it:
|
|
1242
|
+
*
|
|
1243
|
+
* ```ts
|
|
1244
|
+
* // ✗ widens: every `as` in the calling stack stops being traceable
|
|
1245
|
+
* function assertOk(v: string): Statement[] { return [s.lambda({…}), s.precondition({…})]; }
|
|
1246
|
+
*
|
|
1247
|
+
* // ✓ tuple survives the spread
|
|
1248
|
+
* function assertOk(v: string) { return statements(s.lambda({…}), s.precondition({…})); }
|
|
1249
|
+
* ```
|
|
1250
|
+
*
|
|
1251
|
+
* Type-level only — the returned array is the arguments verbatim, so there is no
|
|
1252
|
+
* encoder involvement and no cost.
|
|
1253
|
+
*
|
|
1254
|
+
* **Fixed arity only.** A helper that builds its array in a loop cannot be a
|
|
1255
|
+
* tuple; declare `responseShape` on the calling def instead.
|
|
1256
|
+
*/
|
|
1257
|
+
declare function statements<const T extends readonly Statement[]>(...items: T): T;
|
|
1258
|
+
/** True when a statement name has a registered factory. */
|
|
1259
|
+
declare function isRegisteredStatement(name: string): boolean;
|
|
1260
|
+
/** Look up a registered factory, throwing a clear error when absent. */
|
|
1261
|
+
declare function getStatementFactory(name: string): (...args: any[]) => Statement;
|
|
1262
|
+
/**
|
|
1263
|
+
* Encode a statement into the stored `StackItemXdo`, filling the **full**
|
|
1264
|
+
* persisted envelope. Every stored statement carries the same 12 keys
|
|
1265
|
+
* regardless of type (confirmed against live `mvp_query`/`mvp_tool`), so the
|
|
1266
|
+
* envelope is uniform here rather than per-statement: empty members are emitted
|
|
1267
|
+
* with their canonical defaults so the output is 1:1 with the engine's
|
|
1268
|
+
* persisted form. `_xsid` is engine-generated on import; we emit `""` (the
|
|
1269
|
+
* stored placeholder) so the key is present for comparison.
|
|
1270
|
+
*/
|
|
1271
|
+
declare function encodeStatement(stmt: Statement): StackItemXdo;
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* Shared field encoder (KTD-6). Function inputs and table columns are nearly
|
|
1275
|
+
* the same stored shape; this encoder fills the common defaults and is
|
|
1276
|
+
* parameterized by a `FieldContext` for the three spots where they differ:
|
|
1277
|
+
* `customize` (`""` vs `{}`), `market_item` id types (string vs numeric), and
|
|
1278
|
+
* whether `description` is emitted.
|
|
1279
|
+
*/
|
|
1280
|
+
|
|
1281
|
+
/** Field visibility — the engine's `access` enum. */
|
|
1282
|
+
type FieldAccess = "public" | "private" | "internal";
|
|
1283
|
+
/** Valid `format` values for text fields (per the engine's text-field schema). */
|
|
1284
|
+
type TextFormat = "" | "plaintext" | "yaml" | "html" | "xml" | "markdown";
|
|
1285
|
+
/** Field cardinality — `"single"` (scalar) or `"list"` (array column). */
|
|
1286
|
+
type FieldStyleType = "single" | "list";
|
|
1287
|
+
interface FieldOptions {
|
|
1288
|
+
required?: boolean;
|
|
1289
|
+
nullable?: boolean;
|
|
1290
|
+
/**
|
|
1291
|
+
* Default value. Authored as a `string`, `number`, or `boolean` for
|
|
1292
|
+
* ergonomics (`default: 0`, `default: false`); the engine stores it as a
|
|
1293
|
+
* string, so it's coerced at encode time (`0` → `"0"`, `false` → `"false"`).
|
|
1294
|
+
*
|
|
1295
|
+
* On a **table column** the default must stay within the Basic Multilingual
|
|
1296
|
+
* Plane: a 4-byte character (codepoint > U+FFFF — emoji, CJK-extension glyphs)
|
|
1297
|
+
* is mangled into an invalid UTF-8 sequence by the engine's default pipeline
|
|
1298
|
+
* and is rejected at export/encode time rather than 500ing at deploy with
|
|
1299
|
+
* Postgres `22021` (issue #45). BMP characters (accents, `€`, most CJK) store
|
|
1300
|
+
* fine. A *function/endpoint input* default binds at runtime and has no limit.
|
|
1301
|
+
*/
|
|
1302
|
+
default?: string | number | boolean;
|
|
1303
|
+
/**
|
|
1304
|
+
* Persist **no `default` key at all** for this field, rather than the empty
|
|
1305
|
+
* `default: ""` every other field carries.
|
|
1306
|
+
*
|
|
1307
|
+
* Set on a table's `uuid` PRIMARY KEY, which is the one column the engine
|
|
1308
|
+
* stores this way — its value is engine-generated, so there is nothing for a
|
|
1309
|
+
* default to mean, and absent vs empty are different stored bytes. Applied
|
|
1310
|
+
* automatically by `idType: "uuid"`; you rarely set it by hand.
|
|
1311
|
+
*
|
|
1312
|
+
* NOT a property of `uuid` in general: an ordinary (non-key) `uuid` column
|
|
1313
|
+
* does carry `default: ""`. Mutually exclusive with {@link default}.
|
|
1314
|
+
*/
|
|
1315
|
+
noDefault?: boolean;
|
|
1316
|
+
description?: string;
|
|
1317
|
+
/**
|
|
1318
|
+
* Methods/filters applied at bind time. Each entry is either a bare name
|
|
1319
|
+
* (`"trim"`), a colon-form string with args (`"min:8"` → `{name:"min",
|
|
1320
|
+
* arg:["8"]}`), or an explicit `{ name, arg }` object.
|
|
1321
|
+
*/
|
|
1322
|
+
methods?: MethodSpec[];
|
|
1323
|
+
/** Enum values (for `type:"enum"` fields), e.g. `["draft","live"]`. */
|
|
1324
|
+
values?: Array<string | number>;
|
|
1325
|
+
mode?: string;
|
|
1326
|
+
/** Text-field display format (text fields only; the engine drops it elsewhere). */
|
|
1327
|
+
format?: TextFormat;
|
|
1328
|
+
sensitive?: boolean;
|
|
1329
|
+
/**
|
|
1330
|
+
* Merge the referenced object's fields into this one rather than nesting them.
|
|
1331
|
+
*
|
|
1332
|
+
* **Leave this unset** unless reproducing a pulled field. It defaults to `false`,
|
|
1333
|
+
* which is what every field this SDK authors from scratch wants; it is here so a
|
|
1334
|
+
* pulled workspace's field can be recovered as a readable `f.*` call instead of
|
|
1335
|
+
* degrading to `rawField()`. Paired with {@link hidden} in the wild — 584 fields
|
|
1336
|
+
* across the sweep carry `merge: true` with a `hidden` list beside it.
|
|
1337
|
+
*/
|
|
1338
|
+
merge?: boolean;
|
|
1339
|
+
/**
|
|
1340
|
+
* Names to hide from this field's expansion (e.g. `["created_at"]`).
|
|
1341
|
+
*
|
|
1342
|
+
* **Leave this unset** unless reproducing a pulled field; it defaults to `[]`.
|
|
1343
|
+
* Each entry is resolved as a name and removed from the expanded shape, so an
|
|
1344
|
+
* entry naming nothing hides nothing — a stored `[""]` is a real spelling that
|
|
1345
|
+
* appears in the wild and round-trips verbatim here rather than being guessed at.
|
|
1346
|
+
*/
|
|
1347
|
+
hidden?: readonly string[];
|
|
1348
|
+
/** Field visibility in API output. Defaults to `"public"`. */
|
|
1349
|
+
access?: FieldAccess;
|
|
1350
|
+
style?: {
|
|
1351
|
+
type: FieldStyleType;
|
|
1352
|
+
};
|
|
1353
|
+
/**
|
|
1354
|
+
* Length bounds for an array field. Both members are `json`-typed in the
|
|
1355
|
+
* engine, not numeric — the editor declares them as `json` and every consumer
|
|
1356
|
+
* reads them through a numeric coercion (`+list.max`) — so a bound can arrive
|
|
1357
|
+
* as `5` or as `"5"` and neither spelling is wrong.
|
|
1358
|
+
*
|
|
1359
|
+
* That is also why "unset" has no single spelling: an empty `json` control
|
|
1360
|
+
* serializes to `""` on 8,814 fields in the sweep and to `{}` on two. Codegen
|
|
1361
|
+
* elides the block whenever every member is blank in either form (see
|
|
1362
|
+
* `hasNoListBounds`), so only a bound that is actually SET reaches this type.
|
|
1363
|
+
*/
|
|
1364
|
+
list?: {
|
|
1365
|
+
min?: string | number;
|
|
1366
|
+
max?: string | number;
|
|
1367
|
+
};
|
|
1368
|
+
vector?: {
|
|
1369
|
+
size: number;
|
|
1370
|
+
};
|
|
1371
|
+
/**
|
|
1372
|
+
* Array/list field — stored as `style:{type:"list"}` (e.g. `int[]`, `object[]`).
|
|
1373
|
+
* Ignored when an explicit `style` is given.
|
|
1374
|
+
*/
|
|
1375
|
+
array?: boolean;
|
|
1376
|
+
/** Nested fields for `type:"obj"` columns; each is itself a named, typed field. */
|
|
1377
|
+
children?: NestedField[];
|
|
1378
|
+
/**
|
|
1379
|
+
* Per-column overrides applied to this field's EXPANSION, keyed by the
|
|
1380
|
+
* expanded column's name. Meaningful on a merged field — an
|
|
1381
|
+
* {@link input.dbLink} — where the engine expands a table's columns into
|
|
1382
|
+
* request inputs and consults this map for each one.
|
|
1383
|
+
*
|
|
1384
|
+
* **Leave this unset** unless reproducing a pulled field; it defaults to `{}`.
|
|
1385
|
+
* Xano's own CRUD scaffold writes it, so it is the common shape in a pulled
|
|
1386
|
+
* workspace, not an edge case.
|
|
1387
|
+
*/
|
|
1388
|
+
customize?: Readonly<Record<string, FieldCustomization>>;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* One expanded column's override inside a merged field's {@link
|
|
1392
|
+
* FieldOptions.customize} map.
|
|
1393
|
+
*
|
|
1394
|
+
* Every member restates what the column already declares, so an omitted member
|
|
1395
|
+
* is not "inherit" — it is the stored default (`hidden:false`, `required:false`,
|
|
1396
|
+
* `default:""`, no methods). Author the ones you mean to change and accept the
|
|
1397
|
+
* rest, which is what the editor writes.
|
|
1398
|
+
*/
|
|
1399
|
+
interface FieldCustomization {
|
|
1400
|
+
/** Drop this column from the expansion entirely. */
|
|
1401
|
+
hidden?: boolean;
|
|
1402
|
+
/** Whether the expanded input is required. */
|
|
1403
|
+
required?: boolean;
|
|
1404
|
+
/** Default for the expanded input; stored as a string (`0` → `"0"`). */
|
|
1405
|
+
default?: string | number | boolean;
|
|
1406
|
+
/** Methods/filters appended to the column's own, in the same spellings as {@link FieldOptions.methods}. */
|
|
1407
|
+
methods?: readonly MethodSpec[];
|
|
1408
|
+
/** Overrides for an object column's OWN children, keyed by child name. */
|
|
1409
|
+
customize?: Readonly<Record<string, FieldCustomization>>;
|
|
1410
|
+
}
|
|
1411
|
+
/** A nested field inside an object column's `children` — a named, typed `FieldOptions`. */
|
|
1412
|
+
interface NestedField extends FieldOptions {
|
|
1413
|
+
name: string;
|
|
1414
|
+
type: string;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* A field method/filter: a bare name (`"trim"`), a colon-form string carrying
|
|
1418
|
+
* args (`"min:8"`, `"min:8:foo"` — first segment is the name, the rest are
|
|
1419
|
+
* args), or an explicit `{ name, arg }` object.
|
|
1420
|
+
*/
|
|
1421
|
+
type MethodSpec = string | {
|
|
1422
|
+
name: string;
|
|
1423
|
+
arg?: Array<string | number>;
|
|
1424
|
+
};
|
|
1425
|
+
/**
|
|
1426
|
+
* A type-narrowed {@link MethodSpec} for a field constructor: a bare method name
|
|
1427
|
+
* from the field type's valid set `N` (`"trim"`), the colon-form carrying args
|
|
1428
|
+
* (`"min:8"`), or the explicit `{ name, arg }` object — which stays a universal
|
|
1429
|
+
* escape hatch for any name the per-type union doesn't enumerate. The per-type
|
|
1430
|
+
* `N` unions live in `fields/generated/field-methods.generated.ts`.
|
|
1431
|
+
*/
|
|
1432
|
+
type MethodArg<N extends string> = N | `${N}:${string}` | {
|
|
1433
|
+
name: string;
|
|
1434
|
+
arg?: Array<string | number>;
|
|
1435
|
+
};
|
|
1436
|
+
/**
|
|
1437
|
+
* An options type `T` with its `methods` array widened to `readonly`. Field/input
|
|
1438
|
+
* constructors capture their options via a `const` type parameter so literal
|
|
1439
|
+
* flags (`required`/`nullable`/`array`) survive for `InferInput`; `const` also
|
|
1440
|
+
* makes any inline `methods: [...]` a readonly tuple, so a constructor's options
|
|
1441
|
+
* constraint must accept readonly arrays. The runtime cast back to `FieldOptions`
|
|
1442
|
+
* is safe — the encoder only ever reads (`.map`) the methods.
|
|
1443
|
+
*/
|
|
1444
|
+
type ReadonlyMethods<T, N extends string> = Omit<T, "methods"> & {
|
|
1445
|
+
methods?: readonly MethodArg<N>[];
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
/**
|
|
1449
|
+
* Value-type algebra for the typed descriptor layer (U1/U2).
|
|
1450
|
+
*
|
|
1451
|
+
* Input and column constructors (`input.*`, `f.*`) return a runtime descriptor
|
|
1452
|
+
* of exactly `{ type, options }`. This module adds a **phantom brand** to those
|
|
1453
|
+
* return types that carries, at the TYPE LEVEL only, the field's value type `V`
|
|
1454
|
+
* and the literal options object `O` the caller passed. The brand props are
|
|
1455
|
+
* optional and never assigned at runtime, so the emitted object is unchanged and
|
|
1456
|
+
* every branded descriptor stays structurally assignable to the un-branded
|
|
1457
|
+
* `FieldDescriptor` / `InputDescriptor` — existing consumers are unaffected.
|
|
1458
|
+
*
|
|
1459
|
+
* `InferInput` (see `../inputs/infer.ts`) reads these brands to turn a query's
|
|
1460
|
+
* declared `input` map into the request-payload TS type. The same algebra powers
|
|
1461
|
+
* nested-object inference, since object `children` are built from `f.*`.
|
|
1462
|
+
*/
|
|
1463
|
+
/**
|
|
1464
|
+
* Opaque runtime value of a file input/column. The request payload carries a
|
|
1465
|
+
* resource reference (path/metadata), not the raw bytes — model it structurally
|
|
1466
|
+
* rather than as `unknown` so a consumer at least sees an object shape.
|
|
1467
|
+
*/
|
|
1468
|
+
interface XanoFileRef {
|
|
1469
|
+
/** Vault path, e.g. `/vault/<…>/<name>`. Join it to your base URL — see {@link import("./file-url.js").fileUrl}. */
|
|
1470
|
+
path?: string;
|
|
1471
|
+
name?: string;
|
|
1472
|
+
type?: string;
|
|
1473
|
+
size?: number;
|
|
1474
|
+
/** Storage visibility as stored on the column (`"public"` / `"private"`). */
|
|
1475
|
+
access?: string;
|
|
1476
|
+
/** Type-specific metadata — e.g. `{ width, height }` on an image. */
|
|
1477
|
+
meta?: unknown;
|
|
1478
|
+
/**
|
|
1479
|
+
* ⚠ The engine's own absolute URL, which on a tenant-scoped environment omits
|
|
1480
|
+
* the `/tenant/<name>` segment and 404s. Do NOT read it directly: pass the
|
|
1481
|
+
* file to `fileUrl(file, baseUrl)`, which joins the correct `path` to the base
|
|
1482
|
+
* URL your client already has (issue #31).
|
|
1483
|
+
*/
|
|
1484
|
+
url?: string;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Opaque runtime value of a raw file **upload** (`input.file()`).
|
|
1488
|
+
*
|
|
1489
|
+
* Distinct from {@link XanoFileRef}, and the distinction matters: this is the
|
|
1490
|
+
* bytes as they arrive on the request (multipart, base64, or a fetched URI). It
|
|
1491
|
+
* is not yet stored anywhere and cannot be written to a file column. Pass it to
|
|
1492
|
+
* a `s.storage.create_*` statement (`create_image`, `create_attachment`, …) to
|
|
1493
|
+
* store it and get back the {@link XanoFileRef} a column holds.
|
|
1494
|
+
*/
|
|
1495
|
+
interface XanoFileUpload {
|
|
1496
|
+
readonly __fileUpload?: never;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Marker value of a database-link input (`input.dbLink()`).
|
|
1500
|
+
*
|
|
1501
|
+
* Opaque on purpose: a dblink input does not bind a value of its own. The engine
|
|
1502
|
+
* EXPANDS it into one input per column of the linked table, so a table with
|
|
1503
|
+
* three columns turns one dblink entry into three request inputs. Read those by
|
|
1504
|
+
* their own column names — `inp("email")`, not `inp("user__")`.
|
|
1505
|
+
*/
|
|
1506
|
+
interface XanoDbLink {
|
|
1507
|
+
readonly __dbLink?: never;
|
|
1508
|
+
}
|
|
1509
|
+
/** Opaque runtime value of a geo input/column (a GeoJSON-shaped object). */
|
|
1510
|
+
/** One `{ lng, lat }` position, as the engine both accepts and returns. */
|
|
1511
|
+
interface XanoGeoPosition {
|
|
1512
|
+
lng: number;
|
|
1513
|
+
lat: number;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* The value of an `f.geo.*` column: `{ type, data }`.
|
|
1517
|
+
*
|
|
1518
|
+
* Named for what it is rather than for GeoJSON, which this is NOT — the
|
|
1519
|
+
* previous `XanoGeoJson` declared `{ type, coordinates }`, a shape the engine
|
|
1520
|
+
* neither accepts nor returns (issue #208).
|
|
1521
|
+
*
|
|
1522
|
+
* The same shape works in BOTH directions, verified live: seeding
|
|
1523
|
+
* `{ type: "point", data: { lng: 1, lat: 2 } }` reads back byte-identical, and
|
|
1524
|
+
* a polygon seeded as `{ type: "poly", data: [{ lng, lat }, …] }` reads back
|
|
1525
|
+
* with its ring closed for you. `type` is the engine's own abbreviation
|
|
1526
|
+
* (`"point"`, `"poly"`, …), not the GeoJSON keyword, and `data` nests by
|
|
1527
|
+
* geometry: an object for a point, an array of positions for a polygon.
|
|
1528
|
+
*
|
|
1529
|
+
* Raw WKT text (`c.text("POINT(1 2)")`) is also accepted wherever a value is
|
|
1530
|
+
* taken — it is just not the typed path, because a read never returns one.
|
|
1531
|
+
*/
|
|
1532
|
+
interface XanoGeoValue {
|
|
1533
|
+
type: string;
|
|
1534
|
+
data: XanoGeoPosition | XanoGeoPosition[] | XanoGeoPosition[][];
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Phantom brand intersected onto a descriptor's return type. `V` is the field's
|
|
1538
|
+
* base value type; `O` is the literal options object captured via a `const` type
|
|
1539
|
+
* parameter at the call site. Both props are optional and never present at
|
|
1540
|
+
* runtime.
|
|
1541
|
+
*/
|
|
1542
|
+
interface TypeBrand<V, O> {
|
|
1543
|
+
readonly __value?: V;
|
|
1544
|
+
readonly __opts?: O;
|
|
1545
|
+
}
|
|
1546
|
+
/** The base value type carried by a branded descriptor `D` (before array/nullable). */
|
|
1547
|
+
type BrandValue<D> = D extends TypeBrand<infer V, unknown> ? V : unknown;
|
|
1548
|
+
/** The literal options object captured on a branded descriptor `D`. */
|
|
1549
|
+
type BrandOpts<D> = D extends TypeBrand<unknown, infer O> ? O : object;
|
|
1550
|
+
type ApplyArray<T, O> = O extends {
|
|
1551
|
+
array: true;
|
|
1552
|
+
} ? T[] : T;
|
|
1553
|
+
type ApplyNullable<T, O> = O extends {
|
|
1554
|
+
nullable: true;
|
|
1555
|
+
} ? T | null : T;
|
|
1556
|
+
/**
|
|
1557
|
+
* The full value type of a single branded descriptor `D`: its base value with
|
|
1558
|
+
* `array` and `nullable` applied. (Optionality of the *key* is a map-level
|
|
1559
|
+
* concern handled by {@link FromFieldMap}.)
|
|
1560
|
+
*/
|
|
1561
|
+
type ValueOf<D> = ApplyNullable<ApplyArray<BrandValue<D>, BrandOpts<D>>, BrandOpts<D>>;
|
|
1562
|
+
/** Keys whose descriptor options declare `required: true`. */
|
|
1563
|
+
type RequiredKeys<M> = {
|
|
1564
|
+
[K in keyof M]: BrandOpts<M[K]> extends {
|
|
1565
|
+
required: true;
|
|
1566
|
+
} ? K : never;
|
|
1567
|
+
}[keyof M];
|
|
1568
|
+
/** Keys without `required: true` — optional in the produced payload type. */
|
|
1569
|
+
type OptionalKeys<M> = Exclude<keyof M, RequiredKeys<M>>;
|
|
1570
|
+
/**
|
|
1571
|
+
* Turn a named map of branded descriptors into an object type: required inputs
|
|
1572
|
+
* become required keys, everything else becomes an optional (`?`) key. Used both
|
|
1573
|
+
* for a query's top-level `input` map and for nested `object` children.
|
|
1574
|
+
*/
|
|
1575
|
+
type FromFieldMap<M> = Prettify<{
|
|
1576
|
+
[K in RequiredKeys<M>]: ValueOf<M[K]>;
|
|
1577
|
+
} & {
|
|
1578
|
+
[K in OptionalKeys<M>]?: ValueOf<M[K]>;
|
|
1579
|
+
}>;
|
|
1580
|
+
/**
|
|
1581
|
+
* Turn a named map of branded descriptors into a **row** type — the read shape
|
|
1582
|
+
* of a table. Unlike {@link FromFieldMap} (a request payload, where `required`
|
|
1583
|
+
* gates key optionality), every declared column is present on a returned row, so
|
|
1584
|
+
* all keys are required here; `nullable`/`array` still apply via {@link ValueOf}.
|
|
1585
|
+
* Powers `InferRow<typeof table>` (see `../kinds/table.ts`).
|
|
1586
|
+
*/
|
|
1587
|
+
type RowFromFieldMap<M> = Prettify<{
|
|
1588
|
+
[K in keyof M]: ValueOf<M[K]>;
|
|
1589
|
+
}>;
|
|
1590
|
+
/** Flatten an intersection into a single object literal for readable hovers. */
|
|
1591
|
+
type Prettify<T> = {
|
|
1592
|
+
[K in keyof T]: T[K];
|
|
1593
|
+
} & {};
|
|
1594
|
+
|
|
1595
|
+
declare const REFERENCEABLE_KINDS: Set<string>;
|
|
1596
|
+
/**
|
|
1597
|
+
* A reference to another workspace object: its def handle, or a bare name.
|
|
1598
|
+
*
|
|
1599
|
+
* A def may carry an explicit `guid` (its Xano identity). When present it's used
|
|
1600
|
+
* verbatim; otherwise the guid is derived from `name`. Pass def handles (which
|
|
1601
|
+
* carry the `guid`) rather than bare names when an object sets an explicit guid,
|
|
1602
|
+
* so the reference and the target agree on the *same* guid.
|
|
1603
|
+
*/
|
|
1604
|
+
type ObjectRef = string | {
|
|
1605
|
+
name: string;
|
|
1606
|
+
guid?: string;
|
|
1607
|
+
};
|
|
1608
|
+
/**
|
|
1609
|
+
* The guid for a `(type, name)` pair. A seeded `xano.lock` override wins (the
|
|
1610
|
+
* lock freezes identity across renames — see lock/store.ts); otherwise the
|
|
1611
|
+
* deterministic 32-char hex derivation. Every reference and every emitted
|
|
1612
|
+
* target flows through here, so a lock override propagates everywhere by
|
|
1613
|
+
* construction — including guids embedded inside strings at authoring time.
|
|
1614
|
+
*/
|
|
1615
|
+
declare function deriveGuid(type: string, name: string): string;
|
|
1616
|
+
/** Resolve a reference target (def handle or name) to the referenced object's guid. */
|
|
1617
|
+
declare function resolveRef(type: string, target: ObjectRef): string;
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* AUTO-GENERATED by scripts/codegen-field-methods.ts — DO NOT EDIT BY HAND.
|
|
1621
|
+
*
|
|
1622
|
+
* Per-field-type method-name unions, distilled from the engine's column-create
|
|
1623
|
+
* API schema (the per-type field-schema definitions). Each
|
|
1624
|
+
* `f.<type>` / `input.<type>` constructor types its `methods` against the
|
|
1625
|
+
* matching union so only names valid for that field type are accepted (the
|
|
1626
|
+
* explicit `{ name, arg }` form remains a universal escape hatch).
|
|
1627
|
+
*
|
|
1628
|
+
* Regenerate with `npm run codegen:methods` (`-- --refresh` to re-distill upstream).
|
|
1629
|
+
*/
|
|
1630
|
+
/** Per-field-type method metadata (method name → colon-form arg type; `bool` = flag, no arg). */
|
|
1631
|
+
declare const FIELD_METHODS: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
1632
|
+
/** Field methods valid on `decimal` fields. */
|
|
1633
|
+
type DecimalMethod = "max" | "min";
|
|
1634
|
+
/** Field methods valid on `email` fields. */
|
|
1635
|
+
type EmailMethod = "lower" | "trim";
|
|
1636
|
+
/** Field methods valid on `int` fields. */
|
|
1637
|
+
type IntMethod = "max" | "min";
|
|
1638
|
+
/** Field methods valid on `password` fields. */
|
|
1639
|
+
type PasswordMethod = "max" | "min" | "minAlpha" | "minDigit" | "minLowerAlpha" | "minSymbol" | "minUpperAlpha" | "salt";
|
|
1640
|
+
/** Field methods valid on `tableRef` fields. */
|
|
1641
|
+
type TableRefMethod = "max" | "min";
|
|
1642
|
+
/** Field methods valid on `text` fields. */
|
|
1643
|
+
type TextMethod = "alphaOk" | "digitOk" | "lower" | "max" | "min" | "ok" | "pattern" | "startsWith" | "trim" | "upper";
|
|
1644
|
+
/** Field methods valid on `vector` fields. */
|
|
1645
|
+
type VectorMethod = "max" | "min";
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* Rich field-type catalog (`f.*`). A typed, validated authoring surface over the
|
|
1649
|
+
* shared field encoder. Each constructor returns a {@link FieldDescriptor}
|
|
1650
|
+
* (`{ type, options }`) carrying the **stored** type string — the engine's
|
|
1651
|
+
* author-facing names differ from what it persists, so this layer applies the
|
|
1652
|
+
* authoritative mapping (the engine's stored-type map):
|
|
1653
|
+
*
|
|
1654
|
+
* object → obj · timestamp → epochms · image → blob_img ·
|
|
1655
|
+
* video → blob_video · audio → blob_audio · attachment → blob
|
|
1656
|
+
*
|
|
1657
|
+
* Every other type (text/int/decimal/bool/uuid/date/email/password/json/enum/
|
|
1658
|
+
* vector/geo_*) is stored under its own name. Columns and inputs both consume
|
|
1659
|
+
* descriptors; the per-context differences (`customize`, `market_item`,
|
|
1660
|
+
* `description`) are still applied by {@link encodeField}.
|
|
1661
|
+
*/
|
|
1662
|
+
|
|
1663
|
+
/** A typed field, ready to attach to a column/input name. */
|
|
1664
|
+
interface FieldDescriptor {
|
|
1665
|
+
/** The **stored** type string (post-mapping), e.g. `blob_img`, `epochms`. */
|
|
1666
|
+
type: string;
|
|
1667
|
+
options: FieldOptions;
|
|
1668
|
+
}
|
|
1669
|
+
/** A named map of fields — used for table schemas and object children. */
|
|
1670
|
+
type FieldMap = Record<string, FieldDescriptor>;
|
|
1671
|
+
/** Options accepted by every catalog constructor (no `values`/`children`/`vector` — those are positional). */
|
|
1672
|
+
type FieldOpts = Omit<FieldOptions, "values" | "children" | "vector">;
|
|
1673
|
+
/**
|
|
1674
|
+
* {@link FieldOpts} with `methods` narrowed to the field type's valid method set
|
|
1675
|
+
* `N` (see {@link MethodArg}). Types with no engine-declared methods use
|
|
1676
|
+
* `MethodOpts<never>`, leaving only the `{ name, arg }` escape hatch.
|
|
1677
|
+
*/
|
|
1678
|
+
type MethodOpts<N extends string> = Omit<FieldOpts, "methods"> & {
|
|
1679
|
+
methods?: MethodArg<N>[];
|
|
1680
|
+
};
|
|
1681
|
+
/** {@link MethodOpts} made safe to capture under a `const` type parameter (see {@link ReadonlyMethods}). */
|
|
1682
|
+
type ConstMethodOpts<N extends string> = ReadonlyMethods<MethodOpts<N>, N>;
|
|
1683
|
+
/** Convert a named field map into the encoder's `NestedField[]` form. */
|
|
1684
|
+
declare function toNestedFields(map: FieldMap): NestedField[];
|
|
1685
|
+
/**
|
|
1686
|
+
* The stored scalar type of a `tableRef` FK, derived from its `type` option: a
|
|
1687
|
+
* `uuid`-keyed reference stores a `string`, everything else (the default `int`)
|
|
1688
|
+
* stores a `number`. Keeps `InferRow` honest — a FK column is the referenced
|
|
1689
|
+
* table's PK value, never the loose `string | number`.
|
|
1690
|
+
*/
|
|
1691
|
+
type TableRefValue<O> = "uuid" extends (O extends {
|
|
1692
|
+
type: infer T;
|
|
1693
|
+
} ? T : never) ? string : number;
|
|
1694
|
+
/** The rich field-type catalog. */
|
|
1695
|
+
declare const f: {
|
|
1696
|
+
readonly text: <const O extends ConstMethodOpts<TextMethod> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<string, O>;
|
|
1697
|
+
readonly int: <const O extends ConstMethodOpts<IntMethod> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<number, O>;
|
|
1698
|
+
readonly decimal: <const O extends ConstMethodOpts<DecimalMethod> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<number, O>;
|
|
1699
|
+
readonly bool: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<boolean, O>;
|
|
1700
|
+
readonly uuid: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<string, O>;
|
|
1701
|
+
readonly date: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<string, O>;
|
|
1702
|
+
readonly email: <const O extends ConstMethodOpts<EmailMethod> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<string, O>;
|
|
1703
|
+
/** Password field; defaults to `access:"internal"` (the engine's stored default). */
|
|
1704
|
+
readonly password: <const O extends ConstMethodOpts<PasswordMethod> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<string, O>;
|
|
1705
|
+
/**
|
|
1706
|
+
* JSON column. Accepts an optional nested `children` schema — the structure the
|
|
1707
|
+
* editor shows when a `json` field is expanded, and what a pulled workspace
|
|
1708
|
+
* carries.
|
|
1709
|
+
*
|
|
1710
|
+
* `children` is spelled as an ARRAY of named, typed fields rather than the
|
|
1711
|
+
* `FieldMap` {@link FieldCatalog.object} takes positionally. The two are not
|
|
1712
|
+
* interchangeable: an object column's children are the column's own schema,
|
|
1713
|
+
* while a json column's are a description of the shape stored inside it, which
|
|
1714
|
+
* the engine persists in the order given. That order is part of the stored
|
|
1715
|
+
* bytes, and a map does not preserve it.
|
|
1716
|
+
*
|
|
1717
|
+
* Without this there is no way to author a structured JSON column at all
|
|
1718
|
+
* (issue #135).
|
|
1719
|
+
*/
|
|
1720
|
+
readonly json: <const O extends ConstMethodOpts<never> & {
|
|
1721
|
+
children?: readonly NestedField[];
|
|
1722
|
+
} = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<unknown, O>;
|
|
1723
|
+
/** Epoch-millisecond timestamp (authored as `timestamp`). */
|
|
1724
|
+
readonly timestamp: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<number, O>;
|
|
1725
|
+
/** Image file resource (stored `blob_img`). */
|
|
1726
|
+
readonly image: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoFileRef, O>;
|
|
1727
|
+
/** Video file resource (stored `blob_video`). */
|
|
1728
|
+
readonly video: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoFileRef, O>;
|
|
1729
|
+
/** Audio file resource (stored `blob_audio`). */
|
|
1730
|
+
readonly audio: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoFileRef, O>;
|
|
1731
|
+
/** Generic file attachment (stored `blob`). */
|
|
1732
|
+
readonly attachment: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoFileRef, O>;
|
|
1733
|
+
readonly geo: {
|
|
1734
|
+
readonly point: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoGeoValue, O>;
|
|
1735
|
+
readonly multipoint: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoGeoValue, O>;
|
|
1736
|
+
readonly linestring: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoGeoValue, O>;
|
|
1737
|
+
readonly multilinestring: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoGeoValue, O>;
|
|
1738
|
+
readonly polygon: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoGeoValue, O>;
|
|
1739
|
+
readonly multipolygon: <const O extends ConstMethodOpts<never> = Record<string, never>>(options?: O) => FieldDescriptor & TypeBrand<XanoGeoValue, O>;
|
|
1740
|
+
};
|
|
1741
|
+
/**
|
|
1742
|
+
* Enum field. `values` may be empty — that is an enum column added in the
|
|
1743
|
+
* editor and not yet given its options, which the engine stores and which
|
|
1744
|
+
* appears in the survey corpus. Refusing it made the SDK stricter than the
|
|
1745
|
+
* engine and cost a real table its readable form.
|
|
1746
|
+
*
|
|
1747
|
+
* An empty `values` brands the column `never` — an enum permitting nothing can
|
|
1748
|
+
* hold nothing. Note what that becomes downstream: `InferRow` surfaces the
|
|
1749
|
+
* column as `undefined`, not `never`, so a read-time narrow written against
|
|
1750
|
+
* `never` (`if (row.e === "x")`) is dead differently than it looks.
|
|
1751
|
+
*/
|
|
1752
|
+
readonly enum: <const V extends ReadonlyArray<string | number>, const O extends ConstMethodOpts<never> = Record<string, never>>(values: V, options?: O) => FieldDescriptor & TypeBrand<V[number], O>;
|
|
1753
|
+
/** Vector field; `size` (>= 1) is the embedding dimensionality. */
|
|
1754
|
+
readonly vector: <const O extends ConstMethodOpts<VectorMethod> = Record<string, never>>(size: number, options?: O) => FieldDescriptor & TypeBrand<number[], O>;
|
|
1755
|
+
/** Nested object field (stored `obj`); `children` is a named field map. */
|
|
1756
|
+
readonly object: <const C extends FieldMap, const O extends ConstMethodOpts<never> = Record<string, never>>(children: C, options?: O) => FieldDescriptor & TypeBrand<FromFieldMap<C>, O>;
|
|
1757
|
+
/**
|
|
1758
|
+
* Table-reference (foreign-key) field — the column holds the referenced
|
|
1759
|
+
* table's primary key. The engine persists the link as a trailing `@` method
|
|
1760
|
+
* carrying the target table's id (`{name:"@", arg:["dbo=<guid>"]}`); on import
|
|
1761
|
+
* it parses that back into the column's `tableref_id`. The reference resolves to the
|
|
1762
|
+
* table's deterministic guid via the shared cross-object resolver, so it
|
|
1763
|
+
* agrees with the target table's payload `guid` with no shared registry.
|
|
1764
|
+
*
|
|
1765
|
+
* Defaults to an `int` column (matching an `int` primary key); pass
|
|
1766
|
+
* `{ type: "uuid" }` to reference a uuid-keyed table. A reference column may
|
|
1767
|
+
* only be `int` or `uuid` — the two valid primary-key types.
|
|
1768
|
+
*
|
|
1769
|
+
* @param table The referenced table (a `table()` def handle or its bare name).
|
|
1770
|
+
* For a **self-reference** (e.g. `tweets.reply_to → tweets`), the table's
|
|
1771
|
+
* `const` binding isn't assigned yet inside its own initializer — using the
|
|
1772
|
+
* handle throws a "used before declaration" error. Pass the **bare name**
|
|
1773
|
+
* instead: `f.tableRef("tweets", { type: "int" })`. Identity guids derive
|
|
1774
|
+
* from `(type, name)`, so the name form resolves to the same guid.
|
|
1775
|
+
*
|
|
1776
|
+
* Byte-verified whole-object against a persisted table readback
|
|
1777
|
+
* (`test/fixtures/tables/ex_field_table_ref.json`, asserted in
|
|
1778
|
+
* `test/conformance/kinds-corpus.test.ts`): the `@` method carries
|
|
1779
|
+
* `disabled:false`, sits last in `methods`, and its `dbo=` arg is the target
|
|
1780
|
+
* table's payload guid.
|
|
1781
|
+
*
|
|
1782
|
+
* The engine asserts the referenced table's primary-key type matches
|
|
1783
|
+
* (int↔int, uuid↔uuid). When the target is passed as a **def handle** its
|
|
1784
|
+
* `idType` is in hand, so a mismatch throws here instead of surfacing as an
|
|
1785
|
+
* import failure. The bare-name form (self-references) carries no schema —
|
|
1786
|
+
* there is nothing to check against, and it is not guessed at.
|
|
1787
|
+
*/
|
|
1788
|
+
readonly tableRef: <const O extends ConstMethodOpts<TableRefMethod> & {
|
|
1789
|
+
type?: "int" | "uuid";
|
|
1790
|
+
} = Record<string, never>>(table: ObjectRef, options?: O) => FieldDescriptor & TypeBrand<TableRefValue<O>, O>;
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* `obj({...})` — a **dynamic** object value: an object literal whose members can
|
|
1795
|
+
* be live references (`inp`/`ref`/`auth`/`col`) or constants. This is the
|
|
1796
|
+
* missing sibling of `c.obj` (issue #42): `c.obj` takes plain JSON only and
|
|
1797
|
+
* rejects nested tagged values, so it can't express `{ id: inp("id") }`.
|
|
1798
|
+
*
|
|
1799
|
+
* Xano stores a dynamic object as a single value with `tag: "const:expr2"` whose
|
|
1800
|
+
* `value` is the object rendered as a **XanoScript expression string** (verified
|
|
1801
|
+
* against the Xano engine's stored inline-value format). So `obj`
|
|
1802
|
+
* serializes each member to its XanoScript form (`$input.x`, `$var.x`, `$auth`,
|
|
1803
|
+
* `$db.col`, quoted strings, numbers) and wraps them in `{ … }`.
|
|
1804
|
+
*
|
|
1805
|
+
* Why `const:expr2` and not a structured `const:obj`: the Xano runtime value
|
|
1806
|
+
* evaluator resolves `const:obj` by JSON-decoding its value — it
|
|
1807
|
+
* treats the value as a *static JSON string*, so dynamic members (`$input.x`)
|
|
1808
|
+
* would never resolve. Only `const:expr2` is run through the expression parser
|
|
1809
|
+
* (it normalizes to `const:expr` and evaluates), so it is the sole
|
|
1810
|
+
* representation that resolves live references inside an object literal. That
|
|
1811
|
+
* choice is therefore runtime-verified, not a preference.
|
|
1812
|
+
*
|
|
1813
|
+
* The rendered string form (spacing/escaping) is now golden-verified: the
|
|
1814
|
+
* `call_agent` object-args fixture in the conformance corpus pins the exact
|
|
1815
|
+
* `const:expr` string this encoder emits for `obj({ question: inp(...) })`.
|
|
1816
|
+
*
|
|
1817
|
+
* **Supported members:** `inp()`, `ref()`, `auth()`, `col()`, `env()` /
|
|
1818
|
+
* `setting()` / `sys.*`, `c.now()`, `c.text/int/decimal/bool/null`, nested
|
|
1819
|
+
* `obj`-style records, and arrays of those — each optionally carrying a
|
|
1820
|
+
* **filter chain**, which renders as the expression language's own postfix pipe
|
|
1821
|
+
* (`$var.row|get:"address.city"`).
|
|
1822
|
+
*
|
|
1823
|
+
* The filter chain used to throw, and the rejection was wrong (#222). It
|
|
1824
|
+
* collided constantly, because `db.get` binds `null` on a miss — the SDK's own
|
|
1825
|
+
* headline gotcha — so almost every object built from a `db.get` result needs a
|
|
1826
|
+
* null-safe drill, which compiles through the `get` filter. One team counted
|
|
1827
|
+
* twenty pure-plumbing `s.set_var` statements written to work around it.
|
|
1828
|
+
*
|
|
1829
|
+
* The representation was never the obstacle: the engine's own parser stores
|
|
1830
|
+
* `{ …, goals: [$q.goal_1, …]|filter:$$ != null, … }` as ONE `const:expr2`
|
|
1831
|
+
* string, so a per-member chain is exactly what it carries. Nothing had to
|
|
1832
|
+
* change in the engine; the SDK was guessing that it could not.
|
|
1833
|
+
*
|
|
1834
|
+
* **On evidence.** The value of an `expr2` is an expression string the engine
|
|
1835
|
+
* parses — it is NOT XanoScript, and nothing validates its contents ahead of a
|
|
1836
|
+
* live run. So what backs this is the engine's own parser fixtures (paired
|
|
1837
|
+
* source → stored JSON), which show the exact string its tooling produces. A
|
|
1838
|
+
* rendering outside that set is not "probably fine"; it is unverified. Keep the
|
|
1839
|
+
* emitted grammar to shapes a fixture demonstrates.
|
|
1840
|
+
*
|
|
1841
|
+
* What still throws: a filter argument carrying its OWN chain (a trailing `|`
|
|
1842
|
+
* binds to the whole value, so it cannot be written without changing meaning),
|
|
1843
|
+
* a **disabled** filter (an expression string has nowhere to record that), and
|
|
1844
|
+
* the remaining exotic tags (`output`/`response`/`toolset`/`reg`). Build those
|
|
1845
|
+
* in a prior stack step and reference them with `ref`.
|
|
1846
|
+
*/
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* A member of an {@link obj} literal — a {@link Value}, a raw scalar literal
|
|
1850
|
+
* (`string`/`number`/`boolean`, coerced to the matching constant), a nested
|
|
1851
|
+
* record, or an array. Raw scalars let `obj({ max_age_days: 3 })` and
|
|
1852
|
+
* `obj({ greeting: "hi" })` *just work* without wrapping each in `c.int`/`c.text`.
|
|
1853
|
+
*/
|
|
1854
|
+
type ObjMember = Value | string | number | boolean | ObjInput | ObjMember[];
|
|
1855
|
+
/** The record shape {@link obj} accepts: keys → members. */
|
|
1856
|
+
interface ObjInput {
|
|
1857
|
+
[key: string]: ObjMember;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* A {@link Value} that also carries, **at the type level only**, the member
|
|
1861
|
+
* record it was built from (`obj({ id: ref("u.id") })` → `ObjValue<{ id:
|
|
1862
|
+
* RefValue<"u.id"> }>`). The `__obj` carrier is phantom — never present at
|
|
1863
|
+
* runtime — and exists so `InferResponse` can recurse into the members and
|
|
1864
|
+
* resolve each one the way it resolves a top-level response key (issue #118).
|
|
1865
|
+
*
|
|
1866
|
+
* Before this, `obj()` returned a bare `Value`: the members were erased at the
|
|
1867
|
+
* signature, so the resolver saw no `__ref` to trace and every member landed on
|
|
1868
|
+
* the `unknown` floor. `{ user: obj({ id: ref("u.id") }) }` derived `{ user:
|
|
1869
|
+
* unknown }` while the identical refs written as object-literal keys derived
|
|
1870
|
+
* `{ id: number | null }` — and the mismatch only surfaced downstream, as
|
|
1871
|
+
* `unknown` flowing into a caller that had asked for a type.
|
|
1872
|
+
*
|
|
1873
|
+
* Because it is a subtype of `Value`, every existing `obj(...)` use — a request
|
|
1874
|
+
* body, an agent's `args`, a statement field — keeps type-checking unchanged.
|
|
1875
|
+
*/
|
|
1876
|
+
type ObjValue<T extends ObjInput = ObjInput> = Value & {
|
|
1877
|
+
readonly __obj: T;
|
|
1878
|
+
};
|
|
1879
|
+
/**
|
|
1880
|
+
* Build a dynamic object {@link Value} from a record of members. Members may be
|
|
1881
|
+
* references (`inp`/`ref`/`auth`/`col`), constants (`c.*`), nested records, or
|
|
1882
|
+
* arrays. Emits `tag:"const:expr2"` — the engine's dynamic-object representation.
|
|
1883
|
+
*
|
|
1884
|
+
* ```ts
|
|
1885
|
+
* obj({ id: inp("id"), name: c.text("Bob"), tags: [c.text("a"), ref("t")] })
|
|
1886
|
+
* // → { value: '{ id: $input.id, name: "Bob", tags: ["a", $var.t] }', tag: "const:expr2", filters: [] }
|
|
1887
|
+
* ```
|
|
1888
|
+
*
|
|
1889
|
+
* The member record is preserved on the return type as {@link ObjValue}, so a
|
|
1890
|
+
* response built with `obj()` traces through `InferResponse` exactly as the same
|
|
1891
|
+
* members written as object-literal keys would (issue #118). A dynamically-built
|
|
1892
|
+
* `ObjInput` has no literal to read and degrades to unknown-valued members.
|
|
1893
|
+
*/
|
|
1894
|
+
declare function obj<T extends ObjInput>(fields: T): ObjValue<T>;
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* Response → `result[]` mapping, shared by every response-bearing kind
|
|
1898
|
+
* (function, query, tool, middleware, and the response-bearing triggers).
|
|
1899
|
+
* Extracted from the MVP's compile.ts so kinds beyond function can reuse it.
|
|
1900
|
+
*/
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* A member of a record response — a {@link Value}, or a nested plain object
|
|
1904
|
+
* literal which is auto-wrapped via {@link obj} (so `response: { user: { id:
|
|
1905
|
+
* ref(x) } }` just works instead of failing tsc against `TaggedValue`; #133).
|
|
1906
|
+
*/
|
|
1907
|
+
type ResponseMember = Value | ObjInput;
|
|
1908
|
+
/** A single `Value` response, or a record of named result items. */
|
|
1909
|
+
type ResponseDef = Value | Record<string, ResponseMember>;
|
|
1910
|
+
/** Map a response into `result[]`: one unnamed item for a single value, or
|
|
1911
|
+
* one named item per key for a record. A record member that is a nested plain
|
|
1912
|
+
* object is auto-wrapped via {@link obj} (a dynamic `const:expr2` value). */
|
|
1913
|
+
declare function encodeResponse(response: ResponseDef | undefined): ResultItemXdo[];
|
|
1914
|
+
|
|
1915
|
+
export { type RowFromFieldMap as $, type MethodOpts as A, type BrandOpts as B, type CacheXdo as C, type DecimalMethod as D, type EmailMethod as E, type FunctionXdo as F, type MethodSpec as G, type HistoryXdo as H, type InputXdo as I, type MethodXdo as J, type MockEntry as K, type ListBoundsXdo as L, type MethodArg as M, type MockMap as N, type MockXdo as O, type ObjInput as P, type ObjMember as Q, REFERENCEABLE_KINDS as R, type ObjValue as S, type ObjectRef as T, type PasswordMethod as U, type Value as V, type PersistedFunctionRow as W, type RefValue as X, type RegexValue as Y, type ResponseDef as Z, type ResultItemXdo as _, FIELD_METHODS as a, type StackItemXdo as a0, type Statement as a1, type StatementAnnotations as a2, type StatementInputXdo as a3, type StatementOptions as a4, TAGS as a5, TEST_EXPECT_TYPES as a6, type TableRefMethod as a7, type Tag as a8, type TaggedValue as a9, setting as aA, statements as aB, sys as aC, test as aD, toolset as aE, withFilters as aF, type FieldOptions as aG, type Prettify as aH, type ReadonlyMethods as aI, type XanoFileUpload as aJ, type XanoDbLink as aK, type ConstMethodOpts as aL, type AsShapeBrand as aM, type TestDef as aa, type TestExpect as ab, type TestExpectType as ac, type TestXdo as ad, type TextFormat as ae, type TextMethod as af, type TypeBrand as ag, type ValueOf as ah, type VectorMethod as ai, type XanoFileRef as aj, type XanoGeoPosition as ak, type XanoGeoValue as al, annotate as am, auth as an, c as ao, caught as ap, col as aq, env as ar, expect as as, f as at, filter as au, inp as av, obj as aw, out as ax, ref as ay, resp as az, encodeStatement as b, type BrandValue as c, deriveGuid as d, encodeResponse as e, type CaughtField as f, getStatementFactory as g, type ConditionalContext as h, isRegisteredStatement as i, type ConditionalElifContext as j, type ExprGroup as k, type ExprNode as l, type ExprOperand as m, type ExprStatement as n, type FieldAccess as o, type FieldDescriptor as p, type FieldMap as q, resolveRef as r, type FieldOpts as s, toNestedFields as t, type FieldStyleType as u, type FieldXdo as v, type FilterXdo as w, type FilteredValue as x, type FromFieldMap as y, type IntMethod as z };
|