@xanots/sdk 0.0.9 → 0.0.10
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 +17 -0
- package/README.md +28 -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 +8 -7
- package/dist/{branch-commands-PBQ36L4A.js → branch-commands-2BLOC2GR.js} +13 -11
- package/dist/{capture-7PO6SGB4.js → capture-4WVJY4DQ.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-7OXFMCTX.js → chunk-5R73LFWK.js} +3 -3
- package/dist/{chunk-OW2QZEWL.js → chunk-5YCQ2QHH.js} +2 -2
- package/dist/{chunk-5YQOIFT4.js → chunk-75Z74TA7.js} +2 -2
- package/dist/{chunk-QTNO2WD6.js → chunk-7ZYW652H.js} +1 -1
- package/dist/{chunk-K3YSQDXJ.js → chunk-ACCBOMCB.js} +91 -74
- package/dist/{chunk-4PTSTDIG.js → chunk-AIZKXUNP.js} +2 -2
- package/dist/{chunk-P5C2YKAM.js → chunk-ANUDXFEX.js} +18 -27
- 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-JQLR64UC.js → chunk-F6CYJ7TN.js} +21 -6
- package/dist/{chunk-BKDJN76G.js → chunk-FE5I6S6N.js} +1 -1
- package/dist/{chunk-L6TPHWAU.js → chunk-I7DQDJAM.js} +30 -48
- 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-QONOJO45.js → chunk-LBYWGMOA.js} +1 -1
- package/dist/{chunk-ZZLPTHT5.js → chunk-P6TAVLOX.js} +2 -2
- package/dist/{chunk-3QW5NFZJ.js → chunk-QKM4U5UK.js} +3 -3
- package/dist/{chunk-T4XPCJRF.js → chunk-TCFIPDB3.js} +1 -1
- package/dist/{chunk-UDL46O35.js → chunk-VKSOTZK3.js} +2 -2
- package/dist/{chunk-6DHBYBTO.js → chunk-VNQM3V2C.js} +1 -2
- package/dist/{chunk-KSV7VOEX.js → chunk-W24FJHPD.js} +3 -3
- package/dist/chunk-WGPXT2G2.js +22 -0
- package/dist/{chunk-6AAT2AYQ.js → chunk-WP4OZZV4.js} +2 -2
- package/dist/{chunk-MU3O43L2.js → chunk-YBC3IKMF.js} +2 -2
- package/dist/{chunk-6Z5CNWFC.js → chunk-YYFXVYPX.js} +37 -6
- package/dist/chunk-ZQ2PKR6R.js +40 -0
- package/dist/cli.d.ts +57 -31
- package/dist/cli.js +6 -5
- package/dist/codegen-command-GB3H2KQ7.js +46 -0
- 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-QTIVA22A.js} +67 -99
- package/dist/{ephemeral-command-7TK52YQM.js → ephemeral-command-U4AQ3TXX.js} +25 -28
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/{init-command-OVFFW4QS.js → init-command-MAXULNAD.js} +13 -10
- package/dist/{onboard-command-EHOHKQQU.js → init-web-3JNFG6GI.js} +10 -10
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +27 -36
- package/dist/{live-diff-QK4KC2FJ.js → live-diff-IXKBVG4K.js} +3 -3
- package/dist/{lock-commands-ET7NIOLH.js → lock-commands-DQ7CUMIV.js} +20 -19
- 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 +2 -2
- package/dist/node.js +9 -8
- package/dist/{validate-command-SK5FPCV7.js → preflight-command-GZ2GE5RN.js} +17 -16
- package/dist/{profile-command-QJAAUZIV.js → profile-command-LJSBDV2L.js} +5 -5
- package/dist/{release-command-4EXVLYV2.js → release-command-YUBTNHVX.js} +36 -33
- 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-BG1UPZ3Z.d.ts} +5 -5
- package/dist/{test-command-2F5LKMI6.js → test-command-YAZLKLGQ.js} +45 -28
- package/dist/{upgrade-command-QL523I62.js → upgrade-command-FB5QJ363.js} +13 -12
- package/dist/{workspace-K72NP7SX.js → workspace-2COHDBM3.js} +1 -1
- package/dist/{workspace-command-HYA6JEKK.js → workspace-command-43P42FBP.js} +31 -32
- package/dist/{workspace-export-AJMGN3CQ.js → workspace-export-DURY5WYL.js} +2 -2
- 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 +1 -1
- package/llms/kinds-agent-mcp.md +1 -1
- package/llms/kinds-core.md +1 -1
- package/llms/kinds-realtime.md +1 -1
- package/llms/lock.md +5 -5
- package/llms/tests.md +1 -1
- package/llms-full.txt +25 -34
- package/llms.txt +16 -25
- package/manifest.json +95 -426
- package/package.json +1 -1
- 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/sandbox-details-command-EDO56IY3.js +0 -18
- package/dist/sandbox-export-command-MO6DEZ7Z.js +0 -24
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
|
@@ -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/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
|
@@ -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/tests.md
CHANGED
|
@@ -51,7 +51,7 @@ The run is isolated in ways that make a correct test fail for reasons the failur
|
|
|
51
51
|
|
|
52
52
|
`xanots test run-all` runs the unit tests AND the `workflowTest()` objects an environment carries. It takes no entry file and compiles nothing: it runs what is DEPLOYED, so deploy before testing.
|
|
53
53
|
|
|
54
|
-
- `--
|
|
54
|
+
- `--env ephemeral` (DEFAULT — the one this project last deployed to), `--env ephemeral:<name>`, or `--env workspace`. Same grammar as `init --from`. `deploy` takes no `--env` at all; `test` does, and `workspace` is allowed here because running a test only reads.
|
|
55
55
|
- `xanots test list` shows what is there without running it; `xanots test run "<name>"` runs one. When a name is ambiguous the error prints the qualified `function:math/happy path` form, which `run` also accepts.
|
|
56
56
|
- `--kind unit|workflow` narrows to one family. `--concurrency <n>` defaults to 1: tests share the environment database.
|
|
57
57
|
- A failing suite exits 5, distinct from a crash. Tests that could not be REACHED exit 6 — retry that one, investigate the other. An environment with no tests is success, not failure.
|
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# xanots v0.0.
|
|
1
|
+
# xanots v0.0.10
|
|
2
2
|
|
|
3
3
|
> TypeScript SDK that compiles a typed Xano workspace into the importable packageExport JSON bundle.
|
|
4
4
|
|
|
@@ -50,7 +50,7 @@ installed), so a plain file read resolves them at the version you have.
|
|
|
50
50
|
- [Column and input types](llms/fields.md): Read when declaring a table column (`f.*`) or a function/query input (`input.*`) — a type's options and accessor methods, and the `s.precondition` error/status contract that rides the same catalog.
|
|
51
51
|
- [Filter catalog](llms/filters.md): Read when piping a value through `fl.*` — the full catalog with each filter's argument types.
|
|
52
52
|
- [Lambda bodies (JavaScript)](llms/lambda.md): Read when writing a JavaScript body, or weighing whether to reach for one at all — `lam.fn`, `fl.lambda`, `fl.reduce`, `s.lambda`. Lambda workers are a bounded shared resource, and each surface binds a different set of identifiers.
|
|
53
|
-
- [Lock file](llms/lock.md): Read when a `xano.lock` exists or should — renaming/pruning/
|
|
53
|
+
- [Lock file](llms/lock.md): Read when a `xano.lock` exists or should — renaming/pruning/importing identities, seeding the lock programmatically, or asking which commands write it.
|
|
54
54
|
- [Legacy paradigms and retired statements](llms/legacy.md): Read when the code was PULLED from an existing Xano instance rather than authored here — how a codegen'd tree reads and the three shapes in it that must not be "fixed", plus a `raw({ name: "mvp:…" })` statement, a `realtimeTrigger()`, or any name the catalogs do not list.
|
|
55
55
|
- [Statement catalog](llms/statements-catalog.md): Read for the field signature of a specific statement — every surface, grouped by `s.*` namespace. Look here after the control-flow core in the router does not cover what you need.
|
|
56
56
|
|
|
@@ -115,7 +115,7 @@ which fails with a "must be ES modules" error until you switch it to module.
|
|
|
115
115
|
|
|
116
116
|
Set `canonical` on every `apiGroup`. The engine mints the URL token server-side, so
|
|
117
117
|
without one a group's client paths are unresolvable until a lock exists: the bundle
|
|
118
|
-
exports fine and `xanots
|
|
118
|
+
exports fine and `xanots routes` / `getPath()` then fail on the very queries it just
|
|
119
119
|
built. An explicit `canonical` resolves them from the source alone.
|
|
120
120
|
|
|
121
121
|
Build warnings: `export()` prints the shapes that deploy clean and then do the wrong
|
|
@@ -134,7 +134,7 @@ api-group/toolset canonical in a lock file written BESIDE THE ENTRY FILE —
|
|
|
134
134
|
To rename an object: rename in code, export (stderr prints the exact fix-up), run
|
|
135
135
|
`xanots lock rename <kind> <old> <new>`, export again — the original guid is emitted
|
|
136
136
|
under the new name, so the engine renames in place instead of delete+create. Taking
|
|
137
|
-
over an existing workspace: `xanots lock
|
|
137
|
+
over an existing workspace: `xanots lock import <its-packageExport.json>` first, then
|
|
138
138
|
export. Pruning, programmatic seeding, and which commands write the lock:
|
|
139
139
|
`llms/lock.md`.
|
|
140
140
|
|
|
@@ -147,19 +147,19 @@ environment and prints its URL.
|
|
|
147
147
|
AND records — before importing. The blast radius is a disposable environment, not a
|
|
148
148
|
production workspace, but confirm with the user before the first run.
|
|
149
149
|
|
|
150
|
-
**
|
|
150
|
+
**One destination, and no flag for it.**
|
|
151
151
|
|
|
152
|
-
-
|
|
153
|
-
(~1h; `--expires-hours` 1–72 at create time)
|
|
152
|
+
- `xanots deploy` writes to a NAMED, workspace-scoped, auto-expiring ephemeral tenant
|
|
153
|
+
(~1h; `--expires-hours` 1–72 at create time), and to nothing else — an `--env` here
|
|
154
|
+
is a usage error, not a choice. The active env is tracked in
|
|
154
155
|
`./.xano/ephemeral.json`, so deploying again REFRESHES it and the URL is unchanged;
|
|
155
156
|
if it expired or was swept, a fresh one is created and the new URL is called out.
|
|
156
157
|
`--static` puts the frontend ON THE EPHEMERAL, so backend and frontend share one
|
|
157
158
|
disposable environment.
|
|
158
159
|
⚠ Only the BACKEND URL survives a refresh: the replace clears static hosting too,
|
|
159
160
|
so `--static` publishes a NEW host every run and the previous URL stops serving.
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
not serve static hosting.
|
|
161
|
+
- `xanots status` names the env this project last deployed to, its URL and its expiry,
|
|
162
|
+
without your having to remember which one it was.
|
|
163
163
|
- `xanots release` promotes to your INSTANCE workspace and MERGES, not replaces:
|
|
164
164
|
adds/updates what you define, deletes nothing, writes no rows. Destruction is
|
|
165
165
|
opt-in per flag, previewed + confirmed, and can drop a table WITH its rows.
|
|
@@ -182,7 +182,7 @@ host falls back to '', and every call 404s off the dev server.
|
|
|
182
182
|
⚠ It is INJECTED in bracket form — `window["XANO_HOST"]="…"` — so verifying a deploy
|
|
183
183
|
by grepping `window.XANO_HOST` matches nothing and reads as a failed inject. Grep the
|
|
184
184
|
bare `XANO_HOST` token.
|
|
185
|
-
⚠ `xanots
|
|
185
|
+
⚠ `xanots preflight` ignores the deploy login and reads `XANO_VALIDATE_INSTANCE` /
|
|
186
186
|
`XANO_VALIDATE_TOKEN` (+ optional `XANO_VALIDATE_WORKSPACE_ID`) from the environment.
|
|
187
187
|
**Displaying a stored file.** A file column comes back as `{ path, name, type, size,
|
|
188
188
|
meta, access, url }`. ⚠ Do NOT use its `url`: on a tenant-scoped environment that field
|
|
@@ -332,7 +332,7 @@ Non-obvious authoring rules:
|
|
|
332
332
|
never removes it. The floor is the RUNTIME, not the def: a second def, or a much richer
|
|
333
333
|
one, adds ~1 kB. So the cost is paid by importing ANY def at all, and reducing what a
|
|
334
334
|
def does will not reduce it.
|
|
335
|
-
Fix: `xanots
|
|
335
|
+
Fix: `xanots routes <entry> --emit xano/routes.gen.ts` (`paths` is an accepted alias) — verbs, paths, and sockets as
|
|
336
336
|
plain data importing NOTHING, still compile-checked: `routePath("blog/{slug}", { slug })`,
|
|
337
337
|
`channelPath("rooms/{room_id}", { room_id })`, `socketUrl("chat", baseUrl)` (tenant base
|
|
338
338
|
URLs lifted to `wss://h/ws/<tenant>:<canonical>`). A rename is a type error, not a 404.
|
|
@@ -419,19 +419,10 @@ Non-obvious authoring rules:
|
|
|
419
419
|
credential the runner has. As a file that triple is `{ "type": "token",
|
|
420
420
|
"instance_base_url": …, "workspace_id": <n>, "meta_api_token": … }`. The older
|
|
421
421
|
`$XANO_REFRESH_TOKEN` + `$XANO_CLIENT_ID` pair still works but ROTATES: single-use.
|
|
422
|
-
- **Event-driven objects fire on an EPHEMERAL
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
run.
|
|
427
|
-
⚠ Under `--dest sandbox` they import cleanly but their stacks NEVER execute, and there
|
|
428
|
-
is no way to fire one manually — an insert on a bound table does not run its
|
|
429
|
-
`tableTrigger`, and the design silently does nothing. Only synchronously-invoked objects
|
|
430
|
-
(queries, functions, and the agents an endpoint calls with `s.ai.agent.run`) run there.
|
|
431
|
-
If you must stay on the sandbox, verify the logic out of band: factor the body into a
|
|
432
|
-
`defineFunction` (or a callable `query`) and invoke it directly — a `tableTrigger` that
|
|
433
|
-
screens a row on insert should delegate to a function a `query` can also call via
|
|
434
|
-
`s.function.run`, and you assert against that.
|
|
422
|
+
- **Event-driven objects fire on an EPHEMERAL.** A `task` (scheduled), an `mcpServer`,
|
|
423
|
+
and every trigger — `tableTrigger` included — run normally on an ephemeral env, which
|
|
424
|
+
is where `deploy` sends them. So test an event-driven design (screen-on-insert, cron
|
|
425
|
+
cleanup, MCP tool call) by deploying it and letting it run.
|
|
435
426
|
- **Zero-based numeric keys make `c.obj` a LIST.** `c.obj({ "0": "a" })` evaluates to
|
|
436
427
|
`["a"]`: a numeric key IS an index in the engine's data model, so keys that are exactly
|
|
437
428
|
`0..n-1` come back as a list with HTTP 200 and no error. Write `c.array([...])` when you
|
|
@@ -539,7 +530,7 @@ to survive a rename).
|
|
|
539
530
|
- ⚠ 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.
|
|
540
531
|
- `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.
|
|
541
532
|
- `task({ name, guid?, description?, docs?, datasource?, active?, tags?, history?, schedule?, stack?, middleware? })` — a scheduled background job (function-like `stack`, no `input`/`response`).
|
|
542
|
-
- `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
|
|
533
|
+
- `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).
|
|
543
534
|
- `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> })`.
|
|
544
535
|
- `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.
|
|
545
536
|
- `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.
|
|
@@ -639,7 +630,7 @@ The run is isolated in ways that make a correct test fail for reasons the failur
|
|
|
639
630
|
|
|
640
631
|
`xanots test run-all` runs the unit tests AND the `workflowTest()` objects an environment carries. It takes no entry file and compiles nothing: it runs what is DEPLOYED, so deploy before testing.
|
|
641
632
|
|
|
642
|
-
- `--
|
|
633
|
+
- `--env ephemeral` (DEFAULT — the one this project last deployed to), `--env ephemeral:<name>`, or `--env workspace`. Same grammar as `init --from`. `deploy` takes no `--env` at all; `test` does, and `workspace` is allowed here because running a test only reads.
|
|
643
634
|
- `xanots test list` shows what is there without running it; `xanots test run "<name>"` runs one. When a name is ambiguous the error prints the qualified `function:math/happy path` form, which `run` also accepts.
|
|
644
635
|
- `--kind unit|workflow` narrows to one family. `--concurrency <n>` defaults to 1: tests share the environment database.
|
|
645
636
|
- A failing suite exits 5, distinct from a crash. Tests that could not be REACHED exit 6 — retry that one, investigate the other. An environment with no tests is success, not failure.
|
|
@@ -650,7 +641,7 @@ The run is isolated in ways that make a correct test fail for reasons the failur
|
|
|
650
641
|
|
|
651
642
|
> Read when the workspace defines an `agent()` or an `mcpServer()`.
|
|
652
643
|
|
|
653
|
-
- `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
|
|
644
|
+
- `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.
|
|
654
645
|
- `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.
|
|
655
646
|
- `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 })`.
|
|
656
647
|
- `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.
|
|
@@ -728,7 +719,7 @@ The run is isolated in ways that make a correct test fail for reasons the failur
|
|
|
728
719
|
- **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.
|
|
729
720
|
- Socket: `server.getUrl(base, { tenant })` → `/ws/<tenant>:<canonical>`. ⚠ A bare canonical on a tenant host resolves against the INSTANCE workspace instead.
|
|
730
721
|
- 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.
|
|
731
|
-
- Because the shapes differ, `getUrl` TRANSLATES a tenant base URL instead of concatenating: pass the `https://<host>/tenant/<name>` that `
|
|
722
|
+
- 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.
|
|
732
723
|
- ⚠ `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.
|
|
733
724
|
- 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.
|
|
734
725
|
- ⚠ 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.
|
|
@@ -1367,7 +1358,7 @@ TypeScript annotations survive in the body, and top-level `await` works.
|
|
|
1367
1358
|
|
|
1368
1359
|
# Lock file
|
|
1369
1360
|
|
|
1370
|
-
> Read when a `xano.lock` exists or should — renaming/pruning/
|
|
1361
|
+
> Read when a `xano.lock` exists or should — renaming/pruning/importing identities, seeding the lock programmatically, or asking which commands write it.
|
|
1371
1362
|
|
|
1372
1363
|
`xano.lock` pins each object's guid and each api-group/toolset canonical, so renames
|
|
1373
1364
|
stay renames (guids otherwise derive from `(type, name)`; a query's from `(api group,
|
|
@@ -1380,11 +1371,11 @@ old key drops automatically once its guid re-lands under the composed one).
|
|
|
1380
1371
|
- `xanots lock rename <kind> <old> <new>` — `kind` is the payload key (or `table`/`api_group`).
|
|
1381
1372
|
Run it after renaming in code; the next export emits the original guid under the new name.
|
|
1382
1373
|
- `xanots lock prune <entry-file> [keys…] --yes` — drops orphaned entries. Finding orphans
|
|
1383
|
-
RUNS the entry's module scope (env assertions included); `--
|
|
1374
|
+
RUNS the entry's module scope (env assertions included); `--identity-only --yes <kind:name>…`
|
|
1384
1375
|
prunes named keys with no evaluation and no orphan check.
|
|
1385
|
-
- `xanots lock
|
|
1376
|
+
- `xanots lock import <live-bundle.json> [--yes]` — seed the lock from an engine
|
|
1386
1377
|
packageExport when taking over an existing workspace.
|
|
1387
|
-
- Every lock subcommand accepts `--lock=<path>`. `rename`/`
|
|
1378
|
+
- Every lock subcommand accepts `--lock=<path>`. `rename`/`import` take no entry file, so
|
|
1388
1379
|
from outside the lock's directory pass `--lock` (or `--entry=<entry-file>` to derive it).
|
|
1389
1380
|
- Programmatic use: call `seedLockOverrides(readLockFile(path))` BEFORE importing any def
|
|
1390
1381
|
module — references bake guids at import time, so late seeding is a silent no-op
|
|
@@ -1392,7 +1383,7 @@ old key drops automatically once its guid re-lands under the composed one).
|
|
|
1392
1383
|
|
|
1393
1384
|
What writes the lock: `export`/`deploy` of an ENTRY FILE update it via the shared compile
|
|
1394
1385
|
step — only when a lock exists or `--lock` is passed. Nothing from a DEPLOY is written
|
|
1395
|
-
back beyond that (an ephemeral
|
|
1386
|
+
back beyond that (an ephemeral is a separate workspace, so its identities must
|
|
1396
1387
|
not pollute yours). The one write-back is `release --replace`, which mints fresh
|
|
1397
1388
|
identities in the workspace the lock describes: it re-pins the lock from the rebuilt
|
|
1398
1389
|
workspace, because otherwise the next release matches nothing and duplicates every
|