@xanots/sdk 0.0.9 → 0.0.11
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/CHANGELOG.md +34 -0
- package/README.md +66 -21
- package/dist/.build-fingerprint +1 -1
- package/dist/{agent-file-refresh-4W37DGXS.js → agent-file-refresh-QNKN5RYD.js} +3 -3
- package/dist/bin.js +10 -8
- package/dist/{branch-commands-PBQ36L4A.js → branch-commands-2BLOC2GR.js} +13 -11
- package/dist/bundle.d.ts +218 -0
- package/dist/bundle.js +143 -0
- package/dist/{capture-7PO6SGB4.js → capture-4WVJY4DQ.js} +2 -2
- package/dist/{chunk-OW2QZEWL.js → chunk-22TKBSDV.js} +2 -2
- package/dist/{chunk-ZOYMZZ3S.js → chunk-3INK4Y4E.js} +1 -1
- package/dist/{chunk-NDZFBZHC.js → chunk-3UABJA4X.js} +1 -1
- package/dist/{chunk-6Z5CNWFC.js → chunk-4IF54NU5.js} +37 -6
- package/dist/{chunk-KSV7VOEX.js → chunk-4Q7ZOHH7.js} +3 -3
- package/dist/{chunk-QTNO2WD6.js → chunk-7ZYW652H.js} +1 -1
- package/dist/{chunk-4PTSTDIG.js → chunk-AIZKXUNP.js} +2 -2
- package/dist/{chunk-P5C2YKAM.js → chunk-ANUDXFEX.js} +18 -27
- package/dist/{chunk-4BXJGVZ3.js → chunk-BC2C5GVI.js} +2 -69
- package/dist/chunk-BSK7ELHU.js +70 -0
- package/dist/chunk-CTD5ZCV6.js +28 -0
- package/dist/{chunk-5HCDZ2XN.js → chunk-DBFU47BJ.js} +2 -2
- package/dist/{chunk-OKNSR7MT.js → chunk-DIA7CT7J.js} +129 -150
- package/dist/chunk-EHP3WPEG.js +21 -0
- package/dist/{chunk-JQLR64UC.js → chunk-F6CYJ7TN.js} +21 -6
- package/dist/{chunk-BKDJN76G.js → chunk-FE5I6S6N.js} +1 -1
- package/dist/{chunk-5YQOIFT4.js → chunk-G4EJMQLD.js} +2 -2
- package/dist/{chunk-L6TPHWAU.js → chunk-HYBN4H3F.js} +60 -86
- package/dist/{chunk-QIWBCS7N.js → chunk-JGCWTCA7.js} +2 -2
- package/dist/{chunk-MDXR5E5Q.js → chunk-K5IOND4K.js} +2 -2
- package/dist/{chunk-4PE4ZAB7.js → chunk-KA6G2L7U.js} +5 -5
- package/dist/chunk-OHX6MIUZ.js +184 -0
- package/dist/{chunk-UDL46O35.js → chunk-OWGCOGKK.js} +155 -3
- package/dist/{chunk-QONOJO45.js → chunk-QK7ZQJLP.js} +140 -20
- package/dist/{chunk-3QW5NFZJ.js → chunk-QKM4U5UK.js} +3 -3
- package/dist/{chunk-7OXFMCTX.js → chunk-QYMAZRAU.js} +7 -7
- package/dist/{chunk-K3YSQDXJ.js → chunk-RCT7UX7B.js} +92 -75
- package/dist/{chunk-T4XPCJRF.js → chunk-TCFIPDB3.js} +1 -1
- package/dist/{chunk-ZZLPTHT5.js → chunk-UOZMSF4C.js} +15 -8
- package/dist/{chunk-7JDT4PBU.js → chunk-VAF6A3YD.js} +5 -179
- package/dist/{chunk-6DHBYBTO.js → chunk-VNQM3V2C.js} +1 -2
- package/dist/chunk-WGPXT2G2.js +22 -0
- package/dist/{chunk-6AAT2AYQ.js → chunk-WP4OZZV4.js} +2 -2
- package/dist/{chunk-OYMR5AMJ.js → chunk-XEOX6AM7.js} +2 -2
- package/dist/{chunk-MU3O43L2.js → chunk-YBC3IKMF.js} +2 -2
- package/dist/chunk-ZQ2PKR6R.js +40 -0
- package/dist/cli.d.ts +57 -31
- package/dist/cli.js +9 -7
- package/dist/codegen-command-FUT2KJB6.js +49 -0
- package/dist/codegen.d.ts +2 -1
- package/dist/{completion-A6BZ3XGU.js → completion-WF46272M.js} +2 -2
- package/dist/{config-NL33PN4D.js → config-476F3PT5.js} +1 -1
- package/dist/{deploy-command-5TTI4TUP.js → deploy-command-IP7V7GT4.js} +69 -100
- package/dist/{ephemeral-command-7TK52YQM.js → ephemeral-command-U4AQ3TXX.js} +25 -28
- package/dist/index.d.ts +6 -4
- package/dist/index.js +10 -9
- package/dist/init-command-NPVL32L6.js +34 -0
- package/dist/{onboard-command-EHOHKQQU.js → init-web-3JNFG6GI.js} +10 -10
- package/dist/internal.d.ts +4 -4
- package/dist/internal.js +86 -73
- package/dist/io-P2H75UV2.js +12 -0
- package/dist/{live-diff-QK4KC2FJ.js → live-diff-IXKBVG4K.js} +3 -3
- package/dist/{lock-GFXD6G2E.js → lock-46FWYE4D.js} +3 -2
- package/dist/{lock-commands-ET7NIOLH.js → lock-commands-ZZKZ4LZJ.js} +23 -21
- package/dist/{login-command-DFCURZLC.js → login-command-Z6CHTA57.js} +7 -7
- package/dist/{logout-command-WXVZQCAV.js → logout-command-ER6IKAYJ.js} +2 -2
- package/dist/{loop-7SAIGRCZ.js → loop-D5NPL4VH.js} +3 -3
- package/dist/{marketplace-command-BCOAAN2I.js → marketplace-command-P4IPLJ6J.js} +6 -6
- package/dist/{meta-client-57ZWVHST.js → meta-client-K2J4XH64.js} +6 -6
- package/dist/node.d.ts +5 -4
- package/dist/node.js +15 -13
- package/dist/{validate-command-SK5FPCV7.js → preflight-command-K346GPTY.js} +20 -18
- package/dist/{profile-command-QJAAUZIV.js → profile-command-LJSBDV2L.js} +5 -5
- package/dist/{release-command-4EXVLYV2.js → release-command-IMNTIVWK.js} +42 -38
- package/dist/response-BQVQ24l1.d.ts +844 -0
- package/dist/{routes-manifest-SP3ZXLMR.js → routes-manifest-PWZHDOI5.js} +2 -2
- package/dist/scaffold.js +2 -2
- package/dist/{static-host-D6KS7X45.js → static-host-3WMV7IZO.js} +1 -1
- package/dist/status-command-AL47VG7H.js +162 -0
- package/dist/{store-CUCBSYLj.d.ts → store-BLyNeQ8S.d.ts} +21 -8
- package/dist/{test-command-2F5LKMI6.js → test-command-YAZLKLGQ.js} +45 -28
- package/dist/{upgrade-command-QL523I62.js → upgrade-command-BN3EHAOI.js} +16 -14
- package/dist/{workspace-K72NP7SX.js → workspace-2COHDBM3.js} +1 -1
- package/dist/{workspace-command-HYA6JEKK.js → workspace-command-MNK7Y7MQ.js} +36 -34
- package/dist/{workspace-export-AJMGN3CQ.js → workspace-export-DURY5WYL.js} +2 -2
- package/dist/{response-CVAE2kMj.d.ts → xdo-BjJj5W_E.d.ts} +1 -837
- package/guides/README.md +2 -2
- package/guides/cli.md +17 -19
- package/guides/codegen.md +21 -5
- package/guides/coverage.md +1 -1
- package/guides/deploying.md +31 -34
- package/guides/environment.md +4 -4
- package/guides/object-kinds.md +3 -3
- package/guides/project-structure.md +1 -1
- package/guides/scaffold.md +25 -2
- package/guides/typed-frontend.md +2 -2
- package/llms/filters.md +11 -10
- package/llms/kinds-agent-mcp.md +1 -1
- package/llms/kinds-core.md +2 -2
- package/llms/kinds-realtime.md +1 -1
- package/llms/lock.md +5 -5
- package/llms/statements-data.md +3 -1
- package/llms/tests.md +1 -1
- package/llms/values.md +1 -1
- package/llms-full.txt +65 -63
- package/llms.txt +40 -41
- package/manifest.json +96 -427
- package/package.json +7 -2
- package/dist/chunk-3EYUR3TX.js +0 -100
- package/dist/chunk-JQJPFUZI.js +0 -118
- package/dist/chunk-YNZFIY4J.js +0 -78
- package/dist/codegen-command-YSCSCVT5.js +0 -43
- package/dist/env-target-4T7AT347.js +0 -16
- package/dist/init-command-OVFFW4QS.js +0 -30
- package/dist/io-M7XZEMK7.js +0 -11
- package/dist/sandbox-details-command-EDO56IY3.js +0 -18
- package/dist/sandbox-export-command-MO6DEZ7Z.js +0 -24
package/guides/README.md
CHANGED
|
@@ -11,10 +11,10 @@ typed, so your editor's autocomplete is usually the faster lookup — tab-comple
|
|
|
11
11
|
| [Object kinds](object-kinds.md) | Every authorable kind, and splitting a workspace across microservices |
|
|
12
12
|
| [Authoring reference](authoring.md) | Tables and fields, statements, values, inputs, middleware, request history |
|
|
13
13
|
| [CLI](cli.md) | Every command, shell completion, and what failures look like |
|
|
14
|
-
| [Signing in & deploying](deploying.md) | Auth, ephemerals, `--static`, releasing to production, `xanots
|
|
14
|
+
| [Signing in & deploying](deploying.md) | Auth, ephemerals, `--static`, releasing to production, `xanots preflight` |
|
|
15
15
|
| [Environment & identity](environment.md) | Every environment variable, and how `xano.lock` pins identity |
|
|
16
16
|
| [The typed frontend surface](typed-frontend.md) | Path resolution, input/response inference, bundle cost, the route manifest |
|
|
17
|
-
| [Pulling an existing workspace](codegen.md) | What `xanots
|
|
17
|
+
| [Pulling an existing workspace](codegen.md) | What `xanots init --from` writes, how faithful it is, and how to read its report |
|
|
18
18
|
| [Coverage & agent grounding](coverage.md) | What's covered, what's out of scope, and the files agents read |
|
|
19
19
|
|
|
20
20
|
## The agent-facing surface
|
package/guides/cli.md
CHANGED
|
@@ -24,14 +24,13 @@ xanots export ./xano/index.ts --lock # opt into xano.lock (created beside
|
|
|
24
24
|
xanots export ./xano/index.ts --frozen-lock # CI guard: fail if the export would change the lock
|
|
25
25
|
xanots lock rename --entry=xano/index.ts table users members # move a lock entry after renaming in code
|
|
26
26
|
xanots lock prune ./xano/index.ts --yes # drop lock entries nothing exports anymore
|
|
27
|
-
xanots lock prune --
|
|
28
|
-
xanots lock
|
|
27
|
+
xanots lock prune --identity-only --entry=xano/index.ts --yes dbo:notes # …or drop named keys without running the workspace
|
|
28
|
+
xanots lock import live-export.json --entry=xano/index.ts --yes # seed the lock from a live engine export
|
|
29
29
|
|
|
30
30
|
xanots login # OAuth sign-in (once) — pick the instance + workspace at consent
|
|
31
31
|
xanots login --paste # …when the browser can't reach this machine's 127.0.0.1 (remote shell, container, Codespace)
|
|
32
32
|
xanots workspace details # which instance/workspace am I bound to, and via which credential?
|
|
33
33
|
xanots deploy ./xano/index.ts # compile + import into a live ephemeral (the dev loop) → URL
|
|
34
|
-
xanots deploy ./xano/index.ts --dest sandbox # …or your throwaway singleton sandbox
|
|
35
34
|
xanots deploy ./xano/index.ts --static ./frontend/dist # also deploy a static frontend (onto the ephemeral)
|
|
36
35
|
xanots deploy ./xano/index.ts --static ./frontend/dist --static-env PK=pk_live_1 # + extra public config
|
|
37
36
|
xanots deploy --bundle ws.json # deploy an already-exported bundle
|
|
@@ -43,33 +42,32 @@ xanots ephemeral impersonate <tenant> # open it in the builder (--guest = r
|
|
|
43
42
|
xanots release ./xano/index.ts --dry-run # preview what promoting to your main workspace would change
|
|
44
43
|
xanots release ./xano/index.ts # promote: add + update, never delete, never touch table data
|
|
45
44
|
xanots release ./xano/index.ts --prune # …also delete objects this project released and no longer defines (needs xano.lock)
|
|
46
|
-
xanots
|
|
47
|
-
xanots
|
|
48
|
-
xanots
|
|
49
|
-
xanots sandbox details # print the sandbox base URL + tenant details (pretty on a TTY, JSON when piped)
|
|
45
|
+
xanots ephemeral export <tenant> # export a DEPLOYED ephemeral as a JSON bundle → ./<tenant>.json
|
|
46
|
+
xanots ephemeral export <tenant> --format multidoc --name backend # …or as XanoScript → backend.xs
|
|
47
|
+
xanots ephemeral export <tenant> --format multidoc --path - # …stream the multidoc to stdout (deploy first)
|
|
50
48
|
|
|
51
49
|
xanots workspace details # which workspace your token is scoped to (instance, id, name, guid)
|
|
52
50
|
xanots workspace export --path ws.json # your REAL workspace as a JSON bundle (`--path -` streams to stdout)
|
|
53
|
-
xanots
|
|
54
|
-
xanots
|
|
55
|
-
xanots
|
|
56
|
-
xanots codegen ws.json my-app # …or from a bundle already on disk (offline, no auth)
|
|
51
|
+
xanots init my-app --from workspace # …or as a runnable project (the pull direction)
|
|
52
|
+
xanots init my-app --from ephemeral:<tenant> # same, from an ephemeral (the env is named inside the flag)
|
|
53
|
+
xanots init my-app --from ./ws.json # …or from a bundle already on disk (offline, no auth)
|
|
57
54
|
|
|
58
|
-
xanots
|
|
59
|
-
xanots whoami #
|
|
55
|
+
xanots status # who am I, which workspace, and the env this project last deployed to
|
|
56
|
+
xanots whoami # print the scoped user + instance base URL (pretty on a TTY, JSON when piped)
|
|
57
|
+
xanots profile me # alias for `whoami`
|
|
60
58
|
xanots logout # revoke the refresh token + clear the shared cache (--local for the project one)
|
|
61
59
|
xanots version # print the installed @xanots/sdk version
|
|
62
60
|
xanots upgrade --check # is a newer @xanots/sdk published? exits 7 if so, 0 if current
|
|
63
61
|
xanots upgrade # install it (-g or -D, matching how this CLI is installed)
|
|
64
62
|
xanots help # grouped command reference (also the no-arg default)
|
|
65
63
|
xanots <command> --help # that command's usage, subcommands, and flags (`xanots deploy --help`)
|
|
66
|
-
xanots <noun> <verb> --help # scoped to one verb (`xanots workspace
|
|
64
|
+
xanots <noun> <verb> --help # scoped to one verb (`xanots workspace export --help`)
|
|
67
65
|
xanots <command> --json # force JSON on stdout (otherwise: whenever stdout isn't a terminal)
|
|
68
66
|
xanots completion zsh # shell completion script (also bash, fish) — see below
|
|
69
67
|
|
|
70
|
-
xanots
|
|
71
|
-
xanots
|
|
72
|
-
xanots
|
|
68
|
+
xanots preflight ./xano/index.ts # import into a live instance, diff each object back (needs auth + a throwaway tenant)
|
|
69
|
+
xanots preflight ./xano/index.ts --runtime # also run each deployed function on the engine
|
|
70
|
+
xanots preflight ./xano/index.ts --capture # write the fetched JSON as fixture candidates
|
|
73
71
|
```
|
|
74
72
|
|
|
75
73
|
## Bundle key names vs. the names everything else uses
|
|
@@ -97,7 +95,7 @@ correctly looks *missing* if you go looking for `payload.table`. And `agent` and
|
|
|
97
95
|
both land in `payload.toolset`, while the kind actually named `toolset` lands in
|
|
98
96
|
`payload.tool`; matching on the word alone will pick the wrong array.
|
|
99
97
|
|
|
100
|
-
This is also the format `xanots lock
|
|
98
|
+
This is also the format `xanots lock import` reads, so both directions are user-facing.
|
|
101
99
|
|
|
102
100
|
|
|
103
101
|
**Build warnings, and `--strict`.** `export`/`deploy` print a `xanots:` warning for the
|
|
@@ -113,7 +111,7 @@ The programmatic equivalents are `emitBundle(app, { strict: true })` and
|
|
|
113
111
|
## Shell completion
|
|
114
112
|
|
|
115
113
|
`xanots completion <bash|zsh|fish>` prints a completion script covering every command, verb, flag,
|
|
116
|
-
and closed value set (`--
|
|
114
|
+
and closed value set (`--env ephemeral|workspace`, `--format json|multidoc`, `--ai claude|codex|cursor|none`).
|
|
117
115
|
It is generated from the CLI's own command table, so it never drifts from what the CLI accepts — but it
|
|
118
116
|
is baked at generation time, so re-run it after upgrading.
|
|
119
117
|
|
package/guides/codegen.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# Pulling an existing workspace
|
|
2
2
|
|
|
3
|
-
What `xanots
|
|
3
|
+
What `xanots init --from` writes, how faithful the pull is, and how to read its report.
|
|
4
|
+
|
|
5
|
+
## Running it
|
|
6
|
+
|
|
7
|
+
A pull is `init` with `xano/` filled from an existing backend instead of the empty starter,
|
|
8
|
+
so it is the scaffold command with one extra flag:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
xanots init my-app --from workspace # your real workspace (the one your login is scoped to)
|
|
12
|
+
xanots init my-app --from ephemeral:my-env # a named ephemeral environment
|
|
13
|
+
xanots init my-app --from ./ws.json # a bundle already on disk — offline, no login
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Everything else about the project is unchanged: `--framework`, the theme flags, `--ai` and
|
|
17
|
+
`--web` mean the same thing here as they do without `--from`. The two flags that describe
|
|
18
|
+
the pull itself — `--report` and `--skip-roundtrip` — are refused without it rather than
|
|
19
|
+
silently ignored.
|
|
4
20
|
|
|
5
21
|
## The generated tree
|
|
6
22
|
|
|
@@ -49,7 +65,7 @@ it round-trips exactly.
|
|
|
49
65
|
|
|
50
66
|
Then it checks its own work: the project it just wrote is loaded, exported, and diffed
|
|
51
67
|
against the workspace it came from. A mismatch names the object and fails the command
|
|
52
|
-
(`--
|
|
68
|
+
(`--skip-roundtrip` opts out). So "it compiled" and "it means the same thing" are separate
|
|
53
69
|
claims, and you get both.
|
|
54
70
|
|
|
55
71
|
The findings above are printed either way. Verification runs after decoding is finished,
|
|
@@ -68,7 +84,7 @@ count and a collapsed object list, and each names the generated file it landed i
|
|
|
68
84
|
the same data is written into `xano/.xanots-codegen.json` on every pull, so parity is
|
|
69
85
|
trackable release over release and gateable in CI without scraping output.
|
|
70
86
|
|
|
71
|
-
Re-pulling is a real workflow: a second `
|
|
87
|
+
Re-pulling is a real workflow: a second `init --from` into the same directory refreshes
|
|
72
88
|
`xano/` and leaves the rest of the project — your `package.json`, your `frontend/` —
|
|
73
89
|
exactly as you left it. No `--force` needed, because the tree carries a marker saying it
|
|
74
90
|
was machine-written.
|
|
@@ -76,8 +92,8 @@ was machine-written.
|
|
|
76
92
|
> ⚠️ **`xano/` is a scratch surface, and there is no `xanots workspace deploy`.**
|
|
77
93
|
> Regenerating rewrites it (a directory that isn't a previous pull still needs `--force`),
|
|
78
94
|
> it carries schema only — no table rows — and deploying it is a *full replace* of the
|
|
79
|
-
> target. Pull from your real workspace, edit, and `deploy` to a disposable ephemeral
|
|
80
|
-
>
|
|
95
|
+
> target. Pull from your real workspace, edit, and `deploy` to a disposable ephemeral
|
|
96
|
+
> environment. Workspace env var **values** ride inline in `xano/workspace.ts` (that is what a
|
|
81
97
|
> deploy sends), so treat a pulled tree as secret-bearing before you commit it.
|
|
82
98
|
|
|
83
99
|
---
|
package/guides/coverage.md
CHANGED
|
@@ -34,7 +34,7 @@ runtime (XanoTS only compiles), and generating engine-side numeric ids/timestamp
|
|
|
34
34
|
|
|
35
35
|
**Deferred (by design)** — folder auto-discovery, and the `service` / `vault` / `branch`
|
|
36
36
|
payload sections. (Round-trip decompile is no longer deferred: that is
|
|
37
|
-
`xanots
|
|
37
|
+
`xanots init --from`, in the [CLI guide](cli.md); nor is `workflow_test` — it is a
|
|
38
38
|
first-class kind, see `workflowTest` in [Object kinds](object-kinds.md).) `InferResponse`
|
|
39
39
|
auto-derivation covers the object-literal and single-`db`-variable cases (matching the engine's
|
|
40
40
|
static walk), and follows a CALL into the object it invokes (`s.function.call`/`.run`,
|
package/guides/deploying.md
CHANGED
|
@@ -75,7 +75,7 @@ valid until you revoke it wherever you minted it. Save it with owner-only permis
|
|
|
75
75
|
**Project-local credentials** — pass `--local` to `login` to cache tokens in a
|
|
76
76
|
**project-local** `./.xano/auth.json` instead (which `login` **auto-adds to `.gitignore`**),
|
|
77
77
|
scoping the sign-in to that directory. Every command that **reads** credentials
|
|
78
|
-
(`deploy`/`details`, `
|
|
78
|
+
(`deploy`/`details`, `whoami`, token refresh) resolves them **project-local first, global
|
|
79
79
|
as a fallback**: it uses `./.xano/auth.json` when present, otherwise `~/.xanots/auth.json` —
|
|
80
80
|
so a `--local` project keeps working without repeating the flag. `login` and `logout` do
|
|
81
81
|
**not** fall back: they target the shared global cache unless you pass `--local`. An explicit
|
|
@@ -92,16 +92,13 @@ workspace blob is deliberately never dumped: it carries per-tenant secrets that
|
|
|
92
92
|
in shell history or CI logs.
|
|
93
93
|
|
|
94
94
|
**Where it goes** — the instance your **token is bound to** (the token's `aud`), never a
|
|
95
|
-
flag. `xanots deploy` create-or-refreshes an **ephemeral
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
flag. `xanots deploy` create-or-refreshes an **ephemeral**, which is the only environment it
|
|
96
|
+
writes to; production promotion is the separate `xanots release` path to your main instance
|
|
97
|
+
workspace, which merges rather than replacing.
|
|
98
98
|
|
|
99
99
|
**Static host** — `deploy --static <dir>` archives a directory and deploys it to a
|
|
100
|
-
static host after the backend import.
|
|
101
|
-
|
|
102
|
-
environment); with `--dest sandbox` it lands on your **own (parent) workspace**, since the
|
|
103
|
-
sandbox tenant does not serve static hosting. For the parent-workspace case the target is the
|
|
104
|
-
workspace your credential is already pinned to, and the CLI uploads the archive to
|
|
100
|
+
static host after the backend import. The frontend lands **on the ephemeral itself**, so the
|
|
101
|
+
backend and the frontend live in one environment. The CLI uploads the archive to
|
|
105
102
|
`/api:meta/workspace/{id}/static_host/default/build` with your ordinary bearer. That route
|
|
106
103
|
auto-creates the `default` host and **auto-deploys to `dev`**, returning the live URL — so
|
|
107
104
|
the static step is independent of the backend deploy (the backend still runs first because
|
|
@@ -115,7 +112,7 @@ matches the canonical returned for the build just pushed — then prints `Fronte
|
|
|
115
112
|
polls every second for the first 30s, then every two seconds out to 120s. An unconfirmed poll
|
|
116
113
|
is a **warning, not a failure** (the build uploaded fine and usually comes online moments
|
|
117
114
|
later; the exit code stays `0` and the summary records `"verified": false`). Verification is
|
|
118
|
-
skipped when the response carries no canonical to compare against. Pass **`--
|
|
115
|
+
skipped when the response carries no canonical to compare against. Pass **`--skip-liveness`** to
|
|
119
116
|
skip the wait entirely — useful for fast iterative deploys or when the deployed URL isn't
|
|
120
117
|
reachable from the machine running the CLI.
|
|
121
118
|
|
|
@@ -132,7 +129,7 @@ leaves every deep link and refresh running with the global unset — and the pag
|
|
|
132
129
|
Injection is skipped (reported as a warning, not a failure) when the archive has no document
|
|
133
130
|
with a `<head>` to anchor to, and any individual document that lacks one is named; values are
|
|
134
131
|
`<`-escaped so one containing `</script>` can't break out of the element. This is why a
|
|
135
|
-
prebuilt `frontend/dist` can retarget any
|
|
132
|
+
prebuilt `frontend/dist` can retarget any environment with no rebuild.
|
|
136
133
|
|
|
137
134
|
> **Caching — verify with a cache buster.** The static host serves `index.html` with
|
|
138
135
|
> `Cache-Control: public, max-age=3600`, so a browser (or CDN) that loaded the page before
|
|
@@ -143,10 +140,10 @@ prebuilt `frontend/dist` can retarget any sandbox with no rebuild.
|
|
|
143
140
|
> copy — `curl -s "$URL/?nocache=$(date +%s)"` — and check the fetched HTML for the injected
|
|
144
141
|
> `window.XANO_HOST` line rather than retrying the same cached URL.
|
|
145
142
|
|
|
146
|
-
`xanots
|
|
147
|
-
|
|
148
|
-
(`xanots
|
|
149
|
-
the
|
|
143
|
+
`xanots status` prints the same **environment base URL** out of band, for cases where you'd
|
|
144
|
+
rather bake it in at build time — along with the environment's name and expiry, without your
|
|
145
|
+
having to know either. (`xanots whoami` prints the *instance* base URL, i.e. the account's
|
|
146
|
+
origin rather than the environment.) A static failure after a committed backend deploy **does not roll
|
|
150
147
|
back**: it exits with code `3` and a resumable message telling you to re-run with `--static`
|
|
151
148
|
to retry just that step.
|
|
152
149
|
|
|
@@ -182,9 +179,9 @@ secret must not deploy against whatever happens to be on the runner.
|
|
|
182
179
|
> use** — a stored one is spent by its first exchange, so a job that runs twice fails the
|
|
183
180
|
> second time. Prefer the meta credential.
|
|
184
181
|
|
|
185
|
-
##
|
|
182
|
+
## Checking against a live instance (xanots preflight)
|
|
186
183
|
|
|
187
|
-
`xanots
|
|
184
|
+
`xanots preflight` proves your compiled output against a **real, running Xano
|
|
188
185
|
instance** — not a static snapshot. It compiles your workspace, imports it into a
|
|
189
186
|
**fresh ephemeral environment created for that run**, exports it back, and diffs it
|
|
190
187
|
against what you compiled, so you catch three classes of problem a local build can't:
|
|
@@ -195,7 +192,7 @@ against what you compiled, so you catch three classes of problem a local build c
|
|
|
195
192
|
|
|
196
193
|
It talks only to public meta API routes — the **same** archive import `xanots
|
|
197
194
|
deploy` uses, plus the workspace export — and never touches XanoScript. There is one
|
|
198
|
-
way into an instance, so a transport bug is one `
|
|
195
|
+
way into an instance, so a transport bug is one `preflight` reproduces rather than
|
|
199
196
|
routes around.
|
|
200
197
|
|
|
201
198
|
It is non-destructive: nothing you own is written to. Each run creates its own
|
|
@@ -217,11 +214,11 @@ XANO_VALIDATE_TOKEN=your-meta-bearer-token
|
|
|
217
214
|
```
|
|
218
215
|
|
|
219
216
|
```bash
|
|
220
|
-
xanots
|
|
221
|
-
xanots
|
|
222
|
-
xanots
|
|
223
|
-
xanots
|
|
224
|
-
xanots
|
|
217
|
+
xanots preflight ./xano/index.ts # import + round-trip diff, reports per object (every authored kind)
|
|
218
|
+
xanots preflight ./xano/index.ts --runtime # + run each deployed function
|
|
219
|
+
xanots preflight ./xano/index.ts --capture # + write fetched JSON to ./validate-out (fixture candidates)
|
|
220
|
+
xanots preflight ./xano/index.ts --instance http://localhost:8080 # override the target for one run
|
|
221
|
+
xanots preflight --bundle ws.json # check an already-exported bundle
|
|
225
222
|
```
|
|
226
223
|
|
|
227
224
|
Config comes from the environment (a `.env` is autoloaded; a real env var wins),
|
|
@@ -243,10 +240,10 @@ know the URL ahead of time:
|
|
|
243
240
|
const HOST = (typeof window !== "undefined" && window.XANO_HOST) || import.meta.env.VITE_XANO_HOST;
|
|
244
241
|
```
|
|
245
242
|
|
|
246
|
-
`window.XANO_HOST` is the **
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
`window.XANO_HOST` is the **environment** URL your deployed APIs answer at (the same value
|
|
244
|
+
`xanots status` prints as the environment's URL); it is *not* `xanots whoami`, which prints
|
|
245
|
+
your account's instance origin. Because injection happens at deploy time, a prebuilt
|
|
246
|
+
`frontend/dist` retargets any environment with **no rebuild** — ideal for headless agents.
|
|
250
247
|
Add your own public config (base URLs, *publishable* keys) with `--static-env KEY=VALUE`
|
|
251
248
|
(repeatable), exposed the same way as `window.<KEY>`. A static host serves these files
|
|
252
249
|
verbatim to the browser, so everything injected is **public** — never put secrets here;
|
|
@@ -275,13 +272,13 @@ exact string `window.XANO_HOST` (the dot form is valid to *read* the global in y
|
|
|
275
272
|
|
|
276
273
|
| Command | Where it goes |
|
|
277
274
|
|---|---|
|
|
278
|
-
| `xanots deploy` | A disposable **ephemeral** environment
|
|
275
|
+
| `xanots deploy` | A disposable **ephemeral** environment — create-or-refreshed each run, auto-expiring, with its own URL. There is no other destination and no flag to choose one. |
|
|
279
276
|
| `xanots release` | Your **main Xano instance** workspace — the production target. **Merges** by default: objects are updated in place or added, and your table data is never touched unless you ask. |
|
|
280
|
-
| `xanots test` | Nothing — it only reads. Runs the tests an already-deployed environment carries; `--
|
|
277
|
+
| `xanots test` | Nothing — it only reads. Runs the tests an already-deployed environment carries; `--env` picks which one, `workspace` included. |
|
|
281
278
|
|
|
282
|
-
Not every instance has ephemeral environments enabled. Where they are off,
|
|
283
|
-
|
|
284
|
-
|
|
279
|
+
Not every instance has ephemeral environments enabled. Where they are off, `deploy` says so
|
|
280
|
+
and names who can turn them on — there is no second destination to fall back to. `xanots
|
|
281
|
+
export` and `xanots preflight` still work meanwhile.
|
|
285
282
|
|
|
286
283
|
Every `deploy` is a **full replace** of the disposable environment — always fresh, no
|
|
287
284
|
merge mode, no flags to get wrong. A `release` is the opposite by design: it changes what
|
|
@@ -325,7 +322,7 @@ with no lock entry is an object the project never created — a table built in t
|
|
|
325
322
|
team's API group — and the release is refused rather than deleting it. A prune with no lock
|
|
326
323
|
at all is refused too: without one there is no record of what belongs to the project, so
|
|
327
324
|
every deletion would be a guess. To delete something that is genuinely yours to remove,
|
|
328
|
-
|
|
325
|
+
import its identities first (`xanots lock import`). Releasing a pre-exported `--bundle` carries no entry
|
|
329
326
|
file to find a lock beside, so name it with `--lock=<path>`.
|
|
330
327
|
|
|
331
328
|
**A dropped column is destructive, and does not need a flag to happen.** Removing a column
|
|
@@ -355,4 +352,4 @@ XANO_REFRESH_TOKEN=… XANO_CLIENT_ID=… npx @xanots/sdk deploy --bundle ws.jso
|
|
|
355
352
|
```
|
|
356
353
|
|
|
357
354
|
> ⚠️ A deploy is a full replace of the target environment, including its table records,
|
|
358
|
-
> before importing. The blast radius is your own disposable ephemeral
|
|
355
|
+
> before importing. The blast radius is your own disposable ephemeral — but anything
|
package/guides/environment.md
CHANGED
|
@@ -22,7 +22,7 @@ are what you get when the variable is unset.
|
|
|
22
22
|
| `XANO_ORIGIN` | OAuth host to sign in against, instead of the default — the same thing `--origin` does. |
|
|
23
23
|
| `XANO_NO_BROWSER` | Set to anything non-empty and `xanots login` will **not** launch a browser; it prints the authorize URL to stderr for you to open yourself. The loopback server still runs and still waits for the redirect, so the flow completes only if the browser you open it in can reach this machine's `127.0.0.1`. For a machine that has no browser to launch. When the browser is on a **different** machine, that redirect cannot arrive — use `xanots login --paste` instead. |
|
|
24
24
|
|
|
25
|
-
**`xanots
|
|
25
|
+
**`xanots preflight`** — its own target, deliberately separate from the deploy login
|
|
26
26
|
|
|
27
27
|
| Variable | What it does |
|
|
28
28
|
|---|---|
|
|
@@ -78,7 +78,7 @@ updated on every export (written atomically before the bundle). **Commit it next
|
|
|
78
78
|
code.** A project from `xanots init` is locked from its first export — both its `xano:export`
|
|
79
79
|
and `xano:deploy` scripts pass `--lock`, and `npm run xano:check` is the `--frozen-lock` CI
|
|
80
80
|
guard. Adopt it early either way: once identities have drifted, the only way back is
|
|
81
|
-
`lock
|
|
81
|
+
`lock import` against the deployed workspace.
|
|
82
82
|
|
|
83
83
|
Precedence at emit is always **explicit in-code value → lock entry → name derivation**.
|
|
84
84
|
|
|
@@ -92,7 +92,7 @@ losing it costs nothing.
|
|
|
92
92
|
The lock is load-bearing exactly where a live guid **diverges** from that derivation, which
|
|
93
93
|
happens two ways:
|
|
94
94
|
|
|
95
|
-
- **Adopted** objects — anything built in the Xano UI first and taken over with `lock
|
|
95
|
+
- **Adopted** objects — anything built in the Xano UI first and taken over with `lock import`.
|
|
96
96
|
The engine assigned those guids randomly; nothing in your code can re-derive them.
|
|
97
97
|
- **Renamed** objects — `lock rename` pins the original guid under the new name, so the
|
|
98
98
|
derivation no longer reproduces it.
|
|
@@ -125,7 +125,7 @@ try to create everything. `xanots release ./xano/index.ts --replace` now re-pins
|
|
|
125
125
|
workspace itself and tells you to commit it. If there is no lock to re-pin, it says so —
|
|
126
126
|
without one, the next release duplicates every object.
|
|
127
127
|
|
|
128
|
-
**
|
|
128
|
+
**Importing a live workspace's identities** — `xanots lock import <bundle.json>` seeds the lock from a
|
|
129
129
|
real engine `packageExport`, capturing the live workspace's random guids by `(type, name)`
|
|
130
130
|
so code takes over an existing workspace and the first sync updates in place instead of
|
|
131
131
|
duplicating.
|
package/guides/object-kinds.md
CHANGED
|
@@ -122,7 +122,7 @@ query({
|
|
|
122
122
|
|
|
123
123
|
A pull brings tests back, along with a query's saved request/response `example`. The one
|
|
124
124
|
thing it withholds is a test's auth `token` — that is an expiring credential rather than
|
|
125
|
-
authored configuration, so `xanots
|
|
125
|
+
authored configuration, so `xanots init --from` reports it as a deliberate omission instead of
|
|
126
126
|
writing it into a committed tree.
|
|
127
127
|
|
|
128
128
|
**Four of the `Run …` statements only run inside a workflow test.** `s.api.call`,
|
|
@@ -311,8 +311,8 @@ nothing checks that spelling, so prefer the def wherever there is one.
|
|
|
311
311
|
|
|
312
312
|
A container takes time to come up, so `xanots deploy` waits for it: after the import it
|
|
313
313
|
reads each microservice and reports whether it is ready, still starting, or failed, then
|
|
314
|
-
lists them. Skip the wait with `--
|
|
315
|
-
`xanots ephemeral get <env>`,
|
|
314
|
+
lists them. Skip the wait with `--skip-liveness`. The same report is available any time from
|
|
315
|
+
`xanots status`, `xanots ephemeral get <env>`, and `xanots workspace details`.
|
|
316
316
|
|
|
317
317
|
Two outcomes, and only one of them is a warning:
|
|
318
318
|
|
|
@@ -31,7 +31,7 @@ Paths are lower case throughout — an HTTP verb is the one exception, because i
|
|
|
31
31
|
the method rather than a word. Bindings keep the object's own casing, so a file name
|
|
32
32
|
and the symbol it exports can differ.
|
|
33
33
|
|
|
34
|
-
That is the shape `xanots
|
|
34
|
+
That is the shape `xanots init --from` writes, and its `index.ts` re-exports every object
|
|
35
35
|
by name — import from the tree's root rather than from a file, since a file path moves
|
|
36
36
|
when an object's parent or its `_shared.ts` placement changes. Hand-authored projects are
|
|
37
37
|
free to use any other layout; only `index.ts` registering the objects matters.
|
package/guides/scaffold.md
CHANGED
|
@@ -11,10 +11,13 @@ What `xanots init` writes, the two frontend presets, theming, add-ons, and the S
|
|
|
11
11
|
(repeatable; writes `CLAUDE.md`/`AGENTS.md`/Cursor rules — none by default),
|
|
12
12
|
`--marketplace <pkg>` (repeatable, comma-separated; installs add-ons and
|
|
13
13
|
registers them — below), `--force` (scaffold into a non-empty folder),
|
|
14
|
-
`--no-install` (skip `npm install`)
|
|
14
|
+
`--no-install` (skip `npm install`), `--web` (choose all of the above in a
|
|
15
|
+
browser instead — see [Choosing in a browser](#choosing-in-a-browser)),
|
|
16
|
+
`--from <source>` (fill `xano/` from an existing backend rather than the
|
|
17
|
+
starter — see [Pulling an existing workspace](codegen.md)).
|
|
15
18
|
In a terminal, `init` prompts for the framework and the AI files; every prompt
|
|
16
19
|
has a default, so pressing enter twice is a valid answer. The look is never
|
|
17
|
-
prompted for — it comes from the flags above, or from
|
|
20
|
+
prompted for — it comes from the flags above, or from `--web`.
|
|
18
21
|
The starter backend is empty but already compiles and deploys — grow it from the
|
|
19
22
|
walkthrough in `xano/EXAMPLE.md`.
|
|
20
23
|
|
|
@@ -63,6 +66,26 @@ project — the mistake npm answers by silently writing to the wrong `package.js
|
|
|
63
66
|
The package name is passed through exactly as typed, so version specifiers, tags,
|
|
64
67
|
and third-party packages all work.
|
|
65
68
|
|
|
69
|
+
## Choosing in a browser
|
|
70
|
+
|
|
71
|
+
`--web` collects the same choices against a live preview instead of on the
|
|
72
|
+
command line:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
xanots init my-app --web
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
It is a launcher, not a second scaffolder: a configurator is downloaded on
|
|
79
|
+
demand, serves a local page, and finishes by running `init` with the flags your
|
|
80
|
+
choices imply — printing the equivalent command so the project stays
|
|
81
|
+
reproducible from a script. Everything after `--web` is passed to it untouched,
|
|
82
|
+
including `--help`, which is why that one form reaches the network. `xanots help
|
|
83
|
+
init` stays offline, like `init` itself.
|
|
84
|
+
|
|
85
|
+
Because the configurator is fetched at run time, `--web` needs the npm registry
|
|
86
|
+
before it can start. `init` on its own reaches out only to install the new
|
|
87
|
+
project's dependencies, which `--no-install` skips.
|
|
88
|
+
|
|
66
89
|
## The frontend preset
|
|
67
90
|
|
|
68
91
|
To point `npm run dev` at a real backend, copy `.env.example` to `.env.local` — both
|
package/guides/typed-frontend.md
CHANGED
|
@@ -94,7 +94,7 @@ on the query and every caller derives from that single source of truth:
|
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
96
|
const getPost = query({
|
|
97
|
-
verb: "GET", apiGroup: blog, name: "get_post",
|
|
97
|
+
verb: "GET", apiGroup: blog, name: "get_post/{id}", // one row → address it in the path
|
|
98
98
|
input: { id: input.int({ required: true }) },
|
|
99
99
|
stack: [s.db.query({ table: post, where: expr(col("id"), "=", inp("id")), as: "rows" })],
|
|
100
100
|
// A filtered response is opaque to the static walk, so derivation is `unknown`.
|
|
@@ -168,7 +168,7 @@ is paid by importing any def at all.
|
|
|
168
168
|
almost no bundle cost:
|
|
169
169
|
|
|
170
170
|
```bash
|
|
171
|
-
xanots
|
|
171
|
+
xanots routes ./xano/index.ts --emit xano/routes.gen.ts
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
The emitted file is plain data plus one interpolator and imports nothing at all — the same
|
package/llms/filters.md
CHANGED
|
@@ -39,12 +39,13 @@ database with a single `s.db.direct_query` UPDATE (`SET clicks = clicks + 1 WHER
|
|
|
39
39
|
which the DB applies atomically. Reserve the pipeline form for low-contention counters
|
|
40
40
|
where a rare lost update is acceptable.
|
|
41
41
|
⚠ `direct_query` needs the table's PHYSICAL Postgres name, which the typed surface
|
|
42
|
-
does NOT expose: the engine derives
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
the
|
|
47
|
-
|
|
42
|
+
does NOT expose: the engine derives it from ids assigned at import — not knowable from a
|
|
43
|
+
`table()` def (identity is a name + guid, not the numeric id) — and `sql_name` persists
|
|
44
|
+
empty. The derived name is also NOT STABLE: a deploy is a full replace, so every table is
|
|
45
|
+
created afresh and the id in its name moves each time, on the same unchanged project. So
|
|
46
|
+
the safe counter drops out of the typed surface: resolve the physical name from
|
|
47
|
+
`information_schema` inside the request that uses it, and never store, cache or hardcode
|
|
48
|
+
one. A typed atomic path needs an engine change.
|
|
48
49
|
|
|
49
50
|
- `fl.add(value: decimal): decimal`
|
|
50
51
|
- `fl.append(value: <T>, path: text): <T>[]`
|
|
@@ -136,11 +137,11 @@ engine change.
|
|
|
136
137
|
- `fl.prepend(value: <T>, path: text): <T>[]`
|
|
137
138
|
- `fl.range(start: int, stop: int): int[]`
|
|
138
139
|
- `fl.reduce(initial_value: int, code: text, timeout?: int): any[]` — `code` is a JS body run per element; the ACCUMULATOR is `$result` (there is no `$acc`) and `initial_value` is REQUIRED — omitting it would slot the code as the initial value
|
|
139
|
-
- `fl.regex_match(subject: text): text[]`
|
|
140
|
-
- `fl.regex_match_all(subject: text): text[]`
|
|
140
|
+
- `fl.regex_match(subject: text): text[]` — piped value is the PATTERN, the arg is the subject — see `regex_test`
|
|
141
|
+
- `fl.regex_match_all(subject: text): text[]` — piped value is the PATTERN, the arg is the subject — see `regex_test`
|
|
141
142
|
- `fl.regex_quote(delimiter?: text): text`
|
|
142
|
-
- `fl.regex_replace(replacement: text, subject: text): text`
|
|
143
|
-
- `fl.regex_test(subject: text): bool`
|
|
143
|
+
- `fl.regex_replace(replacement: text, subject: text): text` — piped value is the PATTERN, `subject` is the text searched — see `regex_test`. The replacement comes FIRST
|
|
144
|
+
- `fl.regex_test(subject: text): bool` — piped value is the PATTERN (build it with `c.regex(...)`); the arg is the subject — the REVERSE of `contains`/`starts_with`. Swapped, it answers false for every input with no error, so write `withFilters(c.regex("^a+$"), fl.regex_test(inp("s")))` (or name the arg: `fl.regex_test({ subject: inp("s") })`). A pattern found in the subject slot is refused at build time
|
|
144
145
|
- `fl.round(precision?: int): decimal`
|
|
145
146
|
- `fl.rtrim(mask?: text): text`
|
|
146
147
|
- `fl.secureid_decode(salt: text): int`
|
package/llms/kinds-agent-mcp.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Read when the workspace defines an `agent()` or an `mcpServer()`.
|
|
4
4
|
|
|
5
|
-
- `mcpServer({ name, guid?, description?, instructions?, docs?, enabled?, canonical?, spec?, tags?, history?, tools?, llm?, output? })` — an MCP toolset. `llm?`/`output?` are the same blocks `agent()` takes and are usually absent: an MCP server and an agent are ONE stored object distinguished by `type`. Returns a handle with `getPath()`/`getUrl(baseUrl)` for the Streamable-HTTP endpoint — `getUrl` is NOT idempotent, a `baseUrl` already carrying an `/x2/mcp/<…>/stream` path THROWS, so resolve ONCE from the instance base URL. Fires on an ephemeral
|
|
5
|
+
- `mcpServer({ name, guid?, description?, instructions?, docs?, enabled?, canonical?, spec?, tags?, history?, tools?, llm?, output? })` — an MCP toolset. `llm?`/`output?` are the same blocks `agent()` takes and are usually absent: an MCP server and an agent are ONE stored object distinguished by `type`. Returns a handle with `getPath()`/`getUrl(baseUrl)` for the Streamable-HTTP endpoint — `getUrl` is NOT idempotent, a `baseUrl` already carrying an `/x2/mcp/<…>/stream` path THROWS, so resolve ONCE from the instance base URL. Fires on an ephemeral.
|
|
6
6
|
- `tools?`: a `ToolsetToolEntry[]`. Pass the `tool()` HANDLES directly (`tools: [saveNote]`), like every other collection in the SDK; use the `{ tool, enabled?, auth? }` wrapper only when a tool needs `enabled: false` or per-tool `auth`. `auth` names an auth **table** (a `table({ auth: true })` handle or its name) — Xano's ONLY MCP auth surface (per-tool; there is no server-level gate). An entry that names no tool (no handle, no `id`) THROWS at export rather than emitting the `id: 0` null reference it used to; a deliberate raw `id: 0` warns and is carried through, so a pulled workspace still round-trips.
|
|
7
7
|
- `agent({ name, guid?, description?, docs?, enabled?, canonical?, tags?, history?, llm, tools?, output? })` — an LLM orchestrator. No top-level `instructions`/`prompt`/`spec` — the prompt lives under `llm`. Invoke from a stack with `s.ai.agent.run({ agent, args })`.
|
|
8
8
|
- `llm` (REQUIRED): typed provider settings, a discriminated union on `type` (`"xano-free" | "anthropic" | "openai" | "google-genai"`). Shared fields: `systemPrompt?`, `maxSteps?` (default `5`), and `prompt?` XOR `messages?` (genuinely exclusive: both is a type error and throws — the engine stores ONE `prompt_type`, so one would be dropped); plus provider fields (`apiKey?`, `model?`, `temperature?`, `reasoningEffort?`, …). String fields accept Twig placeholders — `{{ $args.x }}` for run inputs (the `args` of `s.ai.agent.run`), `{{ $env.NAME }}` for env vars.
|
package/llms/kinds-core.md
CHANGED
|
@@ -13,7 +13,7 @@ to survive a rename).
|
|
|
13
13
|
- `verb`: `"GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD"` (required), UPPERCASE. Anything else — most often a lowercase `"post"` — makes `query()` THROW, because Xano does NOT reject it: it stores the verb as NULL, a null verb serves as GET, and the endpoint then answers on the wrong method while the one you meant 404s `Unable to locate request.`
|
|
14
14
|
- `apiGroup`: an `apiGroup()` def handle (or its name) — binds by guid, stable across syncs. Raw numeric `apiGroupId?` is the escape hatch and wins if both given.
|
|
15
15
|
- `auth`: `false` (no auth) or an auth-table id; `responseType`: `"standard" | "stream"` (default `standard`) — any other spelling THROWS, since Xano stores an unrecognized one as NULL and a null buffers as `standard`, so a misspelled stream quietly does not stream.
|
|
16
|
-
- `name` is the endpoint PATH within the group. A `{param}` segment is a URL PATH PARAM bound to the input of the same name, and segments chain: `name: "blog/{slug}/review/{review_id}"` + `input: { slug: input.text(), review_id: input.int() }`. Read it with `inp("slug")` like any other input. Every `{param}` MUST have a matching input or `query()` THROWS — Xano treats an unbound marker as inert route text, so the endpoint would answer on the path and see nothing. A `{param}` need NOT be a whole segment (`"blog/post-{slug}"` routes fine), but its type must fit one segment (no object/list/json/file/geo/vector); there are no wildcards or patterns. `required: true` is NOT demanded (the engine's editor leaves path inputs unmarked). Inputs absent from the path are ordinary query-string/body params. Name charset is ONLY `A-Za-z0-9_-/{}`, max 200: a `.` (`"export.zip"`) is NOT rejected by Xano — it stores an EMPTY name that deploys clean then 404s forever, so `query()` THROWS. Use `"export_zip"` and set the extension in the response headers.
|
|
16
|
+
- `name` is the endpoint PATH within the group. A `{param}` segment is a URL PATH PARAM bound to the input of the same name, and segments chain: `name: "blog/{slug}/review/{review_id}"` + `input: { slug: input.text(), review_id: input.int() }`. Read it with `inp("slug")` like any other input. Every `{param}` MUST have a matching input or `query()` THROWS — Xano treats an unbound marker as inert route text, so the endpoint would answer on the path and see nothing. A `{param}` need NOT be a whole segment (`"blog/post-{slug}"` routes fine), but its type must fit one segment (no object/list/json/file/geo/vector); there are no wildcards or patterns. `required: true` is NOT demanded (the engine's editor leaves path inputs unmarked). The CONVERSE is warned, not enforced: an input a `GET`/`DELETE`/`HEAD` looks ONE ROW up by (`s.db.get`/`get_by_id`/`has`/by-field edit/patch/delete) belongs in the path — `export()` warns `query.path-segment-candidate`. It still serves `?blog_id=1`, but the route is not addressable and `getPath()` types STATIC, so a caller cannot pass the value positionally. A segment is any value naming WHICH resource is wanted, not just an id (`"shop/{country}"`). An input that NARROWS A LIST (`s.db.query`) stays a query-string param. Inputs absent from the path are ordinary query-string/body params. Name charset is ONLY `A-Za-z0-9_-/{}`, max 200: a `.` (`"export.zip"`) is NOT rejected by Xano — it stores an EMPTY name that deploys clean then 404s forever, so `query()` THROWS. Use `"export_zip"` and set the extension in the response headers.
|
|
17
17
|
- **Client recipe:** `q.getPath({ params: { slug: "hello" } })` → `/api:<canonical>/blog/hello` — never interpolate by hand. `getPath` percent-encodes each value (so `?`/`#`/spaces stay in their segment) and throws on what encoding cannot contain: a `/`, and a value that IS `.`/`..` (a URL parser drops those before routing — `%2e` counts — addressing a different endpoint). The keys are typed from the literal `name`, so a typo is a compile error. The HANDLE's `q.toSearchParams(input)` drops path params for a GET; the free `query.toSearchParams(input)` has no view of the route and keeps every key.
|
|
18
18
|
- `apiGroup({ name, guid?, canonical?, description?, docs?, swagger?, apiGroupEnabled?, documentation?, cors? })` — a query container; register it and bind queries to it via their `apiGroup`.
|
|
19
19
|
- `cors?`: `{ mode?, allowOrigins?: string[], allowHeaders?: string[], allowCredentials?, maxAge?, allowMethods?: { get?, post?, put?, patch?, delete?, head? } }`.
|
|
@@ -21,7 +21,7 @@ to survive a rename).
|
|
|
21
21
|
- ⚠ Under `"custom"`, `allowOrigins` is matched as EXACT strings (scheme+host+port, no wildcard or subdomain expansion) and `"*"` is compared as a literal origin — it matches NOTHING. An unmatched origin gets no `access-control-*` headers at all, so the call fails in the browser on a missing `access-control-allow-origin` while export, deploy and the preflight all look fine. Name each origin, or use `mode: "default"` for any-origin. `allowMethods` gates the REAL response too: a verb left off gets no CORS headers back even though its preflight passes. Export warns on an empty origin list, a `"*"` entry, and a policy with no method enabled.
|
|
22
22
|
- `defineFunction`/`query`/`apiGroup` above cover the queries+tables core; the four below are the "reach past that" primitives (tasks, workflow tests, middleware, tools). Agents and MCP servers are the same family and live in `llms/kinds-agent-mcp.md`. Same envelope conventions (`guid?`, `description?`, `docs?`, `tags?`, `history?`) unless noted.
|
|
23
23
|
- `task({ name, guid?, description?, docs?, datasource?, active?, tags?, history?, schedule?, stack?, middleware? })` — a scheduled background job (function-like `stack`, no `input`/`response`).
|
|
24
|
-
- `schedule?`: a `ScheduleDef[]` (NOT a single object) — `{ startsOn, freq?, repeatEnabled?, endsOn?, endsEnabled? }`. `startsOn`/`endsOn` are **timestamp strings** validated at encode time — `"2026-01-01T00:00:00Z"`, or the space-separated `"2026-01-01 00:00:00+0000"` a pulled workspace carries — never epoch numbers, and never zoneless; `freq` is the repeat interval **in seconds** (default `86400` = daily); `endsOn` present ⇒ the schedule has an end. `endsEnabled?` defaults to that and is recovery-only — state it to reproduce a stored schedule that remembers an end date with the gate OFF. Fires on an ephemeral
|
|
24
|
+
- `schedule?`: a `ScheduleDef[]` (NOT a single object) — `{ startsOn, freq?, repeatEnabled?, endsOn?, endsEnabled? }`. `startsOn`/`endsOn` are **timestamp strings** validated at encode time — `"2026-01-01T00:00:00Z"`, or the space-separated `"2026-01-01 00:00:00+0000"` a pulled workspace carries — never epoch numbers, and never zoneless; `freq` is the repeat interval **in seconds** (default `86400` = daily); `endsOn` present ⇒ the schedule has an end. `endsEnabled?` defaults to that and is recovery-only — state it to reproduce a stored schedule that remembers an end date with the gate OFF. Fires on an ephemeral (see Gotchas).
|
|
25
25
|
- `workflowTest({ name, guid?, description?, docs?, datasource?, active?, tags?, stack? })` — an end-to-end test. NO `input`/`response`: `.call` something with an `as`, then assert on that var — `s.function.call({ fn, input, as: "r" })`, `s.expect.to_equal({ expr: ref("r"), value: c.int(42) })`. `s.expect.*` belongs here — it is not inert elsewhere (a failure 500s the request), so treat one in a query/function/task as a mistake to remove. `active?` defaults `true`; chain tests with `s.workflow_test.call({ workflowTest: <def handle> })`.
|
|
26
26
|
- `datasource?`: **the trap.** Default `""` is an EMPTY datasource (recommended), not "no datasource". Any non-empty name makes the engine CLONE it before EVERY run — against production-sized data, slow enough to fail the run. `"live"` warns at compile time; other names don't.
|
|
27
27
|
- `middleware({ name, guid?, description?, docs?, resultStrategy?, exceptionPolicy?, tags?, history?, input?, stack?, response?, responseShape?, tests? })` — a pre/post interceptor (function-like `stack`); attach it via a host's `middleware: { pre, post }`. ⚠ `input` ENCODES but an ATTACHED middleware never has it bound — the host request binds its own inputs, so `inp()` inside pre/post fails at runtime with `Unable to locate input` and a declared default does not stand in (`export()` warns). Read the request body with `s.util.get_all_input` instead; it yields a `{ type, vars }` envelope. `s.middleware.call` is the one path that DOES bind the declared map.
|
package/llms/kinds-realtime.md
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
- **Tenant instances (isolated DB):** a tenant's realtime objects live in the TENANT's database, so BOTH halves of a client must name the tenant.
|
|
47
47
|
- Socket: `server.getUrl(base, { tenant })` → `/ws/<tenant>:<canonical>`. ⚠ A bare canonical on a tenant host resolves against the INSTANCE workspace instead.
|
|
48
48
|
- That colon form is PECULIAR TO THE SOCKET. Every other tenant URL gives the tenant its OWN segment — the HTTP half of the same client is `https://<host>/tenant/<tenant>/api:<canonical>/…`. NO request header is required for either.
|
|
49
|
-
- Because the shapes differ, `getUrl` TRANSLATES a tenant base URL instead of concatenating: pass the `https://<host>/tenant/<name>` that `
|
|
49
|
+
- Because the shapes differ, `getUrl` TRANSLATES a tenant base URL instead of concatenating: pass the `https://<host>/tenant/<name>` that `xanots status` prints (and that deploy injects as `window.XANO_HOST`) and the tenant is LIFTED into the socket form. So `getUrl(window.XANO_HOST)` needs no `{ tenant }`, and a CONFLICTING `{ tenant }` alongside it throws.
|
|
50
50
|
- ⚠ `getUrl`/`socketUrl` are NOT idempotent — a `baseUrl` that already carries a `/ws/<…>` path (an earlier result of either) THROWS. Resolve ONCE from the http(s) base; pass that result to `new WebSocket`, never back in as a base.
|
|
51
51
|
- Still pass `{ tenant }` explicitly for a tenant on its OWN DOMAIN — the hostname carries it for HTTP, but there is nothing in the URL for the socket to lift.
|
|
52
52
|
- ⚠ Tokens are tenant-scoped (audience `<tenant>:<license>`, not the bare license), so one minted through the instance workspace is REJECTED by a tenant's realtime server — authenticate and dial through the same tenant.
|
package/llms/lock.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lock file
|
|
2
2
|
|
|
3
|
-
> Read when a `xano.lock` exists or should — renaming/pruning/
|
|
3
|
+
> Read when a `xano.lock` exists or should — renaming/pruning/importing identities, seeding the lock programmatically, or asking which commands write it.
|
|
4
4
|
|
|
5
5
|
`xano.lock` pins each object's guid and each api-group/toolset canonical, so renames
|
|
6
6
|
stay renames (guids otherwise derive from `(type, name)`; a query's from `(api group,
|
|
@@ -13,11 +13,11 @@ old key drops automatically once its guid re-lands under the composed one).
|
|
|
13
13
|
- `xanots lock rename <kind> <old> <new>` — `kind` is the payload key (or `table`/`api_group`).
|
|
14
14
|
Run it after renaming in code; the next export emits the original guid under the new name.
|
|
15
15
|
- `xanots lock prune <entry-file> [keys…] --yes` — drops orphaned entries. Finding orphans
|
|
16
|
-
RUNS the entry's module scope (env assertions included); `--
|
|
16
|
+
RUNS the entry's module scope (env assertions included); `--identity-only --yes <kind:name>…`
|
|
17
17
|
prunes named keys with no evaluation and no orphan check.
|
|
18
|
-
- `xanots lock
|
|
18
|
+
- `xanots lock import <live-bundle.json> [--yes]` — seed the lock from an engine
|
|
19
19
|
packageExport when taking over an existing workspace.
|
|
20
|
-
- Every lock subcommand accepts `--lock=<path>`. `rename`/`
|
|
20
|
+
- Every lock subcommand accepts `--lock=<path>`. `rename`/`import` take no entry file, so
|
|
21
21
|
from outside the lock's directory pass `--lock` (or `--entry=<entry-file>` to derive it).
|
|
22
22
|
- Programmatic use: call `seedLockOverrides(readLockFile(path))` BEFORE importing any def
|
|
23
23
|
module — references bake guids at import time, so late seeding is a silent no-op
|
|
@@ -25,7 +25,7 @@ old key drops automatically once its guid re-lands under the composed one).
|
|
|
25
25
|
|
|
26
26
|
What writes the lock: `export`/`deploy` of an ENTRY FILE update it via the shared compile
|
|
27
27
|
step — only when a lock exists or `--lock` is passed. Nothing from a DEPLOY is written
|
|
28
|
-
back beyond that (an ephemeral
|
|
28
|
+
back beyond that (an ephemeral is a separate workspace, so its identities must
|
|
29
29
|
not pollute yours). The one write-back is `release --replace`, which mints fresh
|
|
30
30
|
identities in the workspace the lock describes: it re-pins the lock from the rebuilt
|
|
31
31
|
workspace, because otherwise the next release matches nothing and duplicates every
|
package/llms/statements-data.md
CHANGED
|
@@ -12,7 +12,7 @@ primary key `id`):
|
|
|
12
12
|
|
|
13
13
|
- `s.db.get({ table, fieldName?, fieldValue, lock?, output?, as? })` — one row by field match; `output` restricts returned columns (and overrides column visibility — it can pull `internal` columns like a password hash).
|
|
14
14
|
- `s.db.get_by_id({ table, id, output?, addon?, tableAlias?, as? })` — get by primary key. Takes `id`, NOT `fieldName`/`fieldValue`; binds the row or `null` for an id that names no row. Both spellings are live in pulled workspaces.
|
|
15
|
-
- ⚠ `id` is validated `>= 1`, so the `0` sentinel an optional `f.tableRef` stores fails the request with HTTP 400 `Value is less than the minimum value of 1` — it does NOT bind `null`. The throw is not scoped to the lookup: inside a `foreach` it kills the whole request, so one unset FK loses every other row's work. Read a nullable FK with the field-match form, which binds `null` on `0` and lets the loop finish: `s.db.get({ table, fieldName: "id", fieldValue: ref("row.fk"), as })`.
|
|
15
|
+
- ⚠ `id` is validated `>= 1`, so the `0` sentinel an optional `f.tableRef` stores fails the request with HTTP 400 `Value is less than the minimum value of 1` — it does NOT bind `null`. The throw is not scoped to the lookup: inside a `foreach` it kills the whole request, so one unset FK loses every other row's work. Read a nullable FK with the field-match form, which binds `null` on `0` and lets the loop finish: `s.db.get({ table, fieldName: "id", fieldValue: ref("row.fk"), as })`. `export()` warns when the `id` is statically a `0` — the literal `c.int(0)`, an `inp()` whose declared input default is `0`, or a `ref()` to a column declared `default: 0` — and `--strict` fails the build.
|
|
16
16
|
- `s.db.has({ table, fieldName?, fieldValue, as? })` — existence test.
|
|
17
17
|
- `s.db.del({ table, fieldName?, fieldValue, as? })` — delete by field match.
|
|
18
18
|
- `s.db.add({ table, row?, data?, output?, as? })` — insert; `row` is a partial keyed by column.
|
|
@@ -54,6 +54,8 @@ primary key `id`):
|
|
|
54
54
|
- `distinct` — `"auto"` (default) | `"yes"` | `"no"`, riding `context.return.<list|stream>.distinct`.
|
|
55
55
|
- `s.db.truncate({ table, reset?, as? })` · `s.db.schema({ table, path, as? })`.
|
|
56
56
|
- `s.db.direct_query({ sql, responseType?, args?, parser?, as? })` — `sql` is a **raw string** (not a `Value`); binds go in `args: Value[]`. `parser: "template_engine"` renders the body as a template first — how a query interpolates a column or table name a bound arg cannot carry; omit it for the default.
|
|
57
|
+
- Template placeholders are Twig over the request scope: `{{ $input.name }}` for an input, `{{ $var.name }}` for a stack variable. ⚠ A BARE `{{ name }}` renders as the empty string — HTTP 200, no error, a query that silently ran with a blank where the value belonged. A bound `?` arg carries a VALUE without the template at all.
|
|
58
|
+
- ⚠ A table's PHYSICAL name is **not stable across deploys**. A deploy is a full replace, so every table is created afresh and the id in its name moves every time — the same unchanged project redeployed three times gave one table three different names. Never store, cache, hardcode or fixture one: resolve it from `information_schema` inside the same request that uses it.
|
|
57
59
|
- `s.db.external.<engine>.direct_query({ sql, connectionString, responseType?, args?, parser?, as? })` — same shape against an EXTERNAL database; `<engine>` is `postgres`/`mysql`/`mssql`/`oracle`/`snowflake`. `connectionString` is a `Value` — reach for `env(...)`, not a literal — stored as `context.connection_string_flex`. A bare string stores the older `context.connection_string` instead (an env-var name unless it looks like a URL); each form round-trips as itself.
|
|
58
60
|
- `s.db.transaction({ body, as? })` — run a `Statement[]` atomically. `as` binds whatever the block returned.
|
|
59
61
|
- `s.db.bulk.add({ table, items, allowIdField?, as? })` / `s.db.bulk.update` / `s.db.bulk.patch` — `items` is an array `Value`.
|