@xanots/sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/agent-file-refresh-7XCACU34.js +95 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +184 -0
- package/dist/capture-HUV5BNTC.js +32 -0
- package/dist/chunk-26CNBZE2.js +963 -0
- package/dist/chunk-33ND6LKO.js +78 -0
- package/dist/chunk-3DUAGYGP.js +1550 -0
- package/dist/chunk-3IGNIP6R.js +89 -0
- package/dist/chunk-47WDWMBJ.js +14 -0
- package/dist/chunk-4BXJGVZ3.js +318 -0
- package/dist/chunk-4HT3BNZ7.js +675 -0
- package/dist/chunk-4YMD2OOZ.js +23 -0
- package/dist/chunk-5WGEURVI.js +156 -0
- package/dist/chunk-5YTBTF25.js +29 -0
- package/dist/chunk-6DHBYBTO.js +166 -0
- package/dist/chunk-76QBEIGO.js +66 -0
- package/dist/chunk-7DKX2SPN.js +100 -0
- package/dist/chunk-7REDODS2.js +468 -0
- package/dist/chunk-C56BC2FY.js +254 -0
- package/dist/chunk-DGSF2Q5H.js +60 -0
- package/dist/chunk-EMCAWMHC.js +3982 -0
- package/dist/chunk-EZG76F7R.js +159 -0
- package/dist/chunk-FJ6ZS5KB.js +3607 -0
- package/dist/chunk-GNPVYOPB.js +17 -0
- package/dist/chunk-HJPTWBLH.js +38 -0
- package/dist/chunk-IZW6MK3U.js +248 -0
- package/dist/chunk-LH6Z6KFV.js +247 -0
- package/dist/chunk-MZUI3JNX.js +93 -0
- package/dist/chunk-NDZFBZHC.js +167 -0
- package/dist/chunk-NOO35BQ2.js +37 -0
- package/dist/chunk-NUQCEOKA.js +13 -0
- package/dist/chunk-QTNO2WD6.js +56 -0
- package/dist/chunk-RVB7ITKN.js +49 -0
- package/dist/chunk-TRU5OHQ5.js +6904 -0
- package/dist/chunk-VKFQJEMW.js +814 -0
- package/dist/chunk-VTIL47DT.js +125 -0
- package/dist/chunk-WHOJWOSV.js +112 -0
- package/dist/chunk-WKP4D5MO.js +1692 -0
- package/dist/chunk-X4DVXBFY.js +38 -0
- package/dist/chunk-XQ22GLYS.js +118 -0
- package/dist/chunk-YDZYMZBE.js +670 -0
- package/dist/chunk-YGPIY3WT.js +1146 -0
- package/dist/chunk-YYRDLFSJ.js +602 -0
- package/dist/chunk-ZUTSMMAG.js +46 -0
- package/dist/cli.d.ts +331 -0
- package/dist/cli.js +33 -0
- package/dist/codegen-command-OOKK6AZK.js +40 -0
- package/dist/codegen.d.ts +114 -0
- package/dist/codegen.js +71 -0
- package/dist/completion-TIPPZN7H.js +298 -0
- package/dist/config-NL33PN4D.js +84 -0
- package/dist/deploy-command-4EQ7XX5G.js +451 -0
- package/dist/env-target-IFJ5QDHC.js +16 -0
- package/dist/ephemeral-command-3X3I4ULC.js +242 -0
- package/dist/index.d.ts +4477 -0
- package/dist/index.js +300 -0
- package/dist/init-command-R6H7ZXW5.js +27 -0
- package/dist/internal.d.ts +74 -0
- package/dist/internal.js +2065 -0
- package/dist/io-AMIKRLPC.js +11 -0
- package/dist/live-diff-Y3AKETHE.js +196 -0
- package/dist/lock-3CVKALKT.js +37 -0
- package/dist/lock-commands-XTR2YKLY.js +232 -0
- package/dist/login-command-ACJF6KWQ.js +167 -0
- package/dist/logout-command-MX3MJS5U.js +68 -0
- package/dist/loop-OM3P2Z6O.js +93 -0
- package/dist/marketplace-command-RATXTKXN.js +379 -0
- package/dist/meta-client-OW5WKWW7.js +170 -0
- package/dist/node.d.ts +59 -0
- package/dist/node.js +442 -0
- package/dist/profile-command-SWJ3SPKR.js +77 -0
- package/dist/release-command-VV5UVZKL.js +427 -0
- package/dist/response-CVAE2kMj.d.ts +1915 -0
- package/dist/routes-manifest-SP3ZXLMR.js +244 -0
- package/dist/runtime-V4C3AC3A.js +13 -0
- package/dist/sandbox-details-command-HJE5SPVG.js +18 -0
- package/dist/sandbox-export-command-QCJY4GMV.js +24 -0
- package/dist/static-host-D6KS7X45.js +138 -0
- package/dist/store-BQ97nKIF.d.ts +7760 -0
- package/dist/test-command-TSPBQHBM.js +383 -0
- package/dist/validate-command-4ERHJSCO.js +122 -0
- package/dist/verify-rollout-FAVQ6EJY.js +40 -0
- package/dist/workspace-K72NP7SX.js +49 -0
- package/dist/workspace-command-OF23DPNI.js +156 -0
- package/dist/workspace-export-AJMGN3CQ.js +10 -0
- package/llms/fields.md +104 -0
- package/llms/filters.md +183 -0
- package/llms/kinds-agent-mcp.md +11 -0
- package/llms/kinds-core.md +68 -0
- package/llms/kinds-realtime.md +76 -0
- package/llms/lambda.md +111 -0
- package/llms/legacy.md +43 -0
- package/llms/object-kinds.md +31 -0
- package/llms/statements-calls.md +23 -0
- package/llms/statements-catalog.md +309 -0
- package/llms/statements-data.md +58 -0
- package/llms/statements-runtime.md +48 -0
- package/llms/tests.md +58 -0
- package/llms/triggers.md +22 -0
- package/llms/values.md +29 -0
- package/llms-full.txt +1672 -0
- package/llms.txt +483 -0
- package/manifest.json +9592 -0
- package/package.json +110 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xano, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# XanoTS
|
|
4
|
+
|
|
5
|
+
### The official TypeScript SDK for [Xano](https://xano.com).
|
|
6
|
+
|
|
7
|
+
**Write your app in TypeScript — the database, the APIs, even AI agents. Or let an
|
|
8
|
+
AI write it for you. Then run one command and it's live on Xano's cloud, with its own
|
|
9
|
+
URL. No servers, no setup, no config. That's it.**
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/@xanots/sdk)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @xanots/sdk login # 1. sign in (opens your browser)
|
|
19
|
+
npx @xanots/sdk init my-app && cd my-app # 2. scaffold your backend + frontend
|
|
20
|
+
npm run build # 3. build your frontend → frontend/dist
|
|
21
|
+
npx xanots deploy ./xano/index.ts --static ./frontend/dist # 4. deploy both → live URLs
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
→ Deploying ./xano/index.ts → new ephemeral "my-app"
|
|
26
|
+
✓ Ephemeral e4f2-9ab1 deployed
|
|
27
|
+
! New ephemeral URL:
|
|
28
|
+
https://e4f2-9ab1.xano.io ← backend, live
|
|
29
|
+
Expires in 1h 0m
|
|
30
|
+
✓ Static host deployed
|
|
31
|
+
https://my-app.xano.io ← frontend, live
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
<div align="center">
|
|
35
|
+
|
|
36
|
+
**From an empty folder to a live full-stack app.** `init` sets up your project — a
|
|
37
|
+
TypeScript backend and a React or Svelte frontend. `npm run build` builds your frontend, then
|
|
38
|
+
`deploy` puts both online and hands you a live URL. Change your code — yourself or with
|
|
39
|
+
an AI — and deploy again; your app updates in seconds. No servers to set up, nothing to
|
|
40
|
+
configure, no glue code between your backend and your frontend.
|
|
41
|
+
|
|
42
|
+
[Why](#why-xanots) ·
|
|
43
|
+
[Quickstart](#quickstart) ·
|
|
44
|
+
[The model](#the-model) ·
|
|
45
|
+
[Deploying](#deploying) ·
|
|
46
|
+
[Type-safe frontend](#a-type-safe-frontend-for-free) ·
|
|
47
|
+
[Reference](#reference)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Why XanoTS
|
|
54
|
+
|
|
55
|
+
Xano gives you a genuinely scalable backend — Postgres, serverless functions, background
|
|
56
|
+
tasks, realtime, MCP servers, AI agents — without you running a single server. XanoTS is
|
|
57
|
+
Xano's **officially supported** TypeScript SDK, and it gives you that backend **as code you
|
|
58
|
+
own**:
|
|
59
|
+
|
|
60
|
+
- **📦 TypeScript is the source of truth.** Your whole workspace — tables, indexes, API
|
|
61
|
+
endpoints, functions, triggers, tasks, middleware, AI toolsets — is typed TS in your repo.
|
|
62
|
+
Version it, review it in PRs, diff it, roll it back. No more clicking through a dashboard
|
|
63
|
+
and hoping prod matches staging.
|
|
64
|
+
|
|
65
|
+
- **🚀 Deploy is built in.** `xanots deploy` compiles your code and ships it to a live Xano
|
|
66
|
+
environment over an authenticated connection, then prints its URL. Backend **and** static
|
|
67
|
+
frontend in one command — no export/import dance, no upload script to maintain.
|
|
68
|
+
|
|
69
|
+
- **⚡ Fast, safe iteration.** Ephemeral environments are disposable and auto-expiring, so
|
|
70
|
+
you rebuild as often as you like. Deploys are identity-stable: re-running never duplicates
|
|
71
|
+
objects, and with a committed `xano.lock` renames stay renames instead of
|
|
72
|
+
delete-and-recreate.
|
|
73
|
+
|
|
74
|
+
- **🧩 The types flow to your frontend.** Import a `query()` def into your React/Svelte/Angular
|
|
75
|
+
app and get the endpoint path, HTTP verb, and a fully-typed request payload — with **zero
|
|
76
|
+
codegen**. Rename a column and every consumer lights up red.
|
|
77
|
+
|
|
78
|
+
- **🤖 AI-first by design.** A deterministic, fully-typed authoring surface: an agent (or
|
|
79
|
+
you) emits well-typed TS that always compiles to a valid, importable workspace. It ships
|
|
80
|
+
with machine-readable grounding an agent reads instead of your source — see
|
|
81
|
+
[Coverage & agent grounding](https://github.com/xanots/sdk/blob/main/guides/coverage.md).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quickstart
|
|
86
|
+
|
|
87
|
+
The four commands at the top of this page are the whole loop. Here they are one at a time.
|
|
88
|
+
|
|
89
|
+
**1. Sign in.** OAuth in your browser — no API keys to copy around. The CLI refreshes tokens
|
|
90
|
+
for you, and the target instance comes from your token rather than a flag.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx @xanots/sdk login
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**2. Scaffold.** `init` writes a complete project: a Vite frontend under `frontend/` (React
|
|
97
|
+
19 + shadcn/ui by default, or SvelteKit with `--framework svelte`), a XanoTS backend under
|
|
98
|
+
`xano/`, and the `xano:export` / `xano:deploy` scripts already wired.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx @xanots/sdk init my-app && cd my-app
|
|
102
|
+
npm run dev # the frontend runs immediately
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The starter backend is empty but already compiles and deploys — grow it from the walkthrough
|
|
106
|
+
in `xano/EXAMPLE.md`. For the flags, the two presets, and add-ons, see
|
|
107
|
+
[The scaffolded project](https://github.com/xanots/sdk/blob/main/guides/scaffold.md).
|
|
108
|
+
|
|
109
|
+
**3. Build the frontend.**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run build # → frontend/dist
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**4. Deploy both.** One authenticated call ships your database schema, your APIs, your
|
|
116
|
+
functions and triggers, **and** your compiled web app.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx xanots deploy ./xano/index.ts --static ./frontend/dist
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
→ Deploying ./xano/index.ts → new ephemeral "my-app"
|
|
124
|
+
✓ Ephemeral e4f2-9ab1 deployed
|
|
125
|
+
! New ephemeral URL:
|
|
126
|
+
https://e4f2-9ab1.xano.io ← backend, live
|
|
127
|
+
Expires in 1h 0m
|
|
128
|
+
✓ Config injected into 1 document: window.XANO_HOST ← backend URL, wired in
|
|
129
|
+
✓ Static host deployed
|
|
130
|
+
https://my-app.xano.io ← frontend, live
|
|
131
|
+
✓ Frontend is live ← edge confirmed serving THIS build
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
That's the dev loop. Change your code and run step 4 again; the environment refreshes in
|
|
135
|
+
seconds. The deploy bakes the backend URL into your build as `window.XANO_HOST`, so the
|
|
136
|
+
frontend never needs to know it ahead of time — read it with a build-time fallback:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const HOST = (typeof window !== "undefined" && window.XANO_HOST) || import.meta.env.VITE_XANO_HOST;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
> Deploying again keeps the **backend** URL; the **frontend** one changes, because a deploy
|
|
143
|
+
> is a full replace and clears the environment's static hosting along with its workspace.
|
|
144
|
+
> Hand out the URL from the latest run. [Signing in & deploying](https://github.com/xanots/sdk/blob/main/guides/deploying.md) covers
|
|
145
|
+
> the injection rules, serving stored files, and headless CI runs.
|
|
146
|
+
|
|
147
|
+
**Prefer to wire it by hand?** Skip `init` — `npm install @xanots/sdk`, write your workspace
|
|
148
|
+
in `xano/index.ts`, and steps 1 and 4 are unchanged. The entry must be an ES module (XanoTS
|
|
149
|
+
defs are ESM-only); on Node ≥ 22.6 a `.ts` entry loads natively, and older Node wants
|
|
150
|
+
[`tsx`](https://tsx.is).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## The model
|
|
155
|
+
|
|
156
|
+
You author declarative def-objects, register them on one `Xano` instance, and XanoTS
|
|
157
|
+
compiles the whole thing into Xano's importable bundle.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { workspace, table, query, apiGroup, f, s, ref, c, expr, col } from "@xanots/sdk";
|
|
161
|
+
|
|
162
|
+
// A database table — `id` + `created_at` auto-inject, so declare only your own columns.
|
|
163
|
+
const user = table({
|
|
164
|
+
name: "user",
|
|
165
|
+
auth: true,
|
|
166
|
+
schema: {
|
|
167
|
+
email: f.email({ required: true, methods: ["trim", "lower"] }),
|
|
168
|
+
name: f.text(),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const post = table({
|
|
173
|
+
name: "post",
|
|
174
|
+
schema: {
|
|
175
|
+
title: f.text({ required: true }),
|
|
176
|
+
body: f.text(),
|
|
177
|
+
published: f.bool({ default: false }),
|
|
178
|
+
author: f.tableRef(user), // a real foreign key, type-checked
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// A public API group + endpoint. This query def is also the contract your frontend imports.
|
|
183
|
+
const blog = apiGroup({ name: "blog", canonical: "blog" });
|
|
184
|
+
|
|
185
|
+
const listPosts = query({
|
|
186
|
+
verb: "GET",
|
|
187
|
+
apiGroup: blog,
|
|
188
|
+
name: "list_posts",
|
|
189
|
+
stack: [
|
|
190
|
+
s.db.query({ table: post, where: expr(col("published"), "=", c.bool(true)), as: "rows" }),
|
|
191
|
+
],
|
|
192
|
+
response: ref("rows"),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export default workspace("blog")
|
|
196
|
+
.registerApiGroups([blog])
|
|
197
|
+
.registerTables([user, post])
|
|
198
|
+
.registerQueries([listPosts]);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Tab-complete `s.` to discover the entire statement catalog — `s.db.*`, `s.math.*`,
|
|
202
|
+
`s.array.*`, `s.text.*`, `s.storage.*`, `s.api.*`, `s.cloud.*`, control flow, AI agent runs,
|
|
203
|
+
and more. **All 214 engine statement surfaces are authorable** — every field name matches
|
|
204
|
+
the Xano engine, and the output is proven byte-for-byte against the engine's own golden
|
|
205
|
+
fixtures.
|
|
206
|
+
|
|
207
|
+
Give a table a `seed` array and those rows ship into the database on deploy, so a fresh
|
|
208
|
+
environment comes up with its lookup tables and fixtures already in place. Tables, fields,
|
|
209
|
+
statements, values, inputs, and middleware are all in the
|
|
210
|
+
[Authoring reference](https://github.com/xanots/sdk/blob/main/guides/authoring.md); every kind you can author is in
|
|
211
|
+
[Object kinds](https://github.com/xanots/sdk/blob/main/guides/object-kinds.md).
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Deploying
|
|
216
|
+
|
|
217
|
+
**Two targets**, so the dev loop and the production step stay distinct:
|
|
218
|
+
|
|
219
|
+
| Command | Where it goes |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `xanots deploy` | A disposable **ephemeral** environment (default) — create-or-refreshed each run, auto-expiring, with its own URL. `--dest sandbox` targets your throwaway singleton instead. |
|
|
222
|
+
| `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. |
|
|
223
|
+
|
|
224
|
+
Every `deploy` is a **full replace** of the disposable environment — always fresh, no merge
|
|
225
|
+
mode, no flags to get wrong. A `release` is the opposite by design: it changes what your code
|
|
226
|
+
defines and leaves the rest of the workspace, including every row in every table, alone. An
|
|
227
|
+
unchanged project is a no-op, which makes `release` safe to run on a schedule or in CI on
|
|
228
|
+
every merge.
|
|
229
|
+
|
|
230
|
+
Anything destructive is **previewed first** — the CLI fetches the plan, prints what would
|
|
231
|
+
change, and asks. `--yes` skips the prompt for CI but never skips the preview. Start with
|
|
232
|
+
`xanots release ./xano/index.ts --dry-run` to see the plan without committing to it.
|
|
233
|
+
|
|
234
|
+
> ⚠️ A deploy is a full replace of the target environment, **including its table records**,
|
|
235
|
+
> before importing. The blast radius is your own disposable ephemeral or sandbox — but
|
|
236
|
+
> anything you only ever created by hand in it is gone. That is exactly why production has a
|
|
237
|
+
> separate `release` path.
|
|
238
|
+
|
|
239
|
+
Every flag, the `--prune` rules, the identity model, and headless CI runs are in
|
|
240
|
+
[Signing in & deploying](https://github.com/xanots/sdk/blob/main/guides/deploying.md). Environment management is
|
|
241
|
+
`xanots ephemeral <list|get|delete|export>`.
|
|
242
|
+
|
|
243
|
+
### Testing
|
|
244
|
+
|
|
245
|
+
The tests you author — a `tests` entry on a query, function, or middleware, or a standalone
|
|
246
|
+
`workflowTest()` — run against a deployed environment:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npx xanots test run-all # the ephemeral you last deployed to
|
|
250
|
+
npx xanots test run-all --dest workspace # or your real workspace (this only reads)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`xanots test list` shows what is there without running it, and `xanots test run "<name>"`
|
|
254
|
+
runs one. A failing suite exits **5**, distinct from a crash, so CI can tell the two apart.
|
|
255
|
+
To deploy and prove it in one step, `xanots deploy ./xano/index.ts --test` — a failing test
|
|
256
|
+
exits 5 **without** retracting the deploy, so the environment is live either way.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## A type-safe frontend, for free
|
|
261
|
+
|
|
262
|
+
Because your API is a typed def, the code that *calls* it can reuse that def instead of
|
|
263
|
+
re-typing URLs and request bodies. Import the `query()` into your frontend:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { listPosts } from "../xano/index.js"; // the same def you deployed
|
|
267
|
+
import { post } from "../xano/tables.js";
|
|
268
|
+
import type { InferRow } from "@xanots/sdk";
|
|
269
|
+
|
|
270
|
+
const BASE = window.XANO_HOST; // injected by the deploy
|
|
271
|
+
|
|
272
|
+
type Post = InferRow<typeof post>; // { id: number; created_at: number; title: string; … }
|
|
273
|
+
|
|
274
|
+
async function fetchPosts(): Promise<Post[]> {
|
|
275
|
+
const res = await fetch(BASE + listPosts.getPath(), { method: listPosts.verb });
|
|
276
|
+
return res.json(); // typed end to end
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
- **`listPosts.getPath()`** → the endpoint path, resolved from your code (or the frozen
|
|
281
|
+
`xano.lock`). No hardcoded strings. `{param}` segments fill from `getPath({ params })`,
|
|
282
|
+
percent-encoded.
|
|
283
|
+
- **`listPosts.verb`** → the HTTP method, straight from the def.
|
|
284
|
+
- **`InferInput<typeof someQuery>`** → the request-payload type, derived from a query's
|
|
285
|
+
`input` map at compile time. Required inputs are required keys; enums become literal
|
|
286
|
+
unions. **No codegen, always in sync.**
|
|
287
|
+
- **`InferRow<typeof post>`** → the table's row type. Rename or retype a column and every
|
|
288
|
+
consumer breaks at compile time — exactly where you want it.
|
|
289
|
+
- **`InferResponse<typeof someQuery>`** → the endpoint's **response** type, closing the round
|
|
290
|
+
trip. It auto-derives the common shapes with no codegen, mirroring the static walk the
|
|
291
|
+
engine itself does, and degrades to `unknown` in exactly the cases the engine cannot
|
|
292
|
+
resolve either — declare `responseShape` there.
|
|
293
|
+
|
|
294
|
+
The `@xanots/sdk` entry has **zero Node dependencies**, so importing your workspace graph
|
|
295
|
+
into a browser bundle just works. The `node:fs`-backed emitters live in the separate
|
|
296
|
+
`@xanots/sdk/node` entry a frontend never pulls in.
|
|
297
|
+
|
|
298
|
+
Importing a def carries the SDK runtime with it. When that matters, emit a route manifest
|
|
299
|
+
instead — plain data plus one interpolator, importing nothing:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
xanots paths ./xano/index.ts --emit xano/routes.gen.ts
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Route names and their `{param}` keys are still checked at compile time, so a backend rename
|
|
306
|
+
is a compile error rather than a 404. The measured bundle numbers, the full response-inference
|
|
307
|
+
rules, endpoint-name constraints, and the realtime helpers are in
|
|
308
|
+
[The typed frontend surface](https://github.com/xanots/sdk/blob/main/guides/typed-frontend.md).
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Already have a Xano workspace?
|
|
313
|
+
|
|
314
|
+
`codegen` runs the loop the other way: it reads a workspace and writes it back out as
|
|
315
|
+
readable XanoTS source — real `s.db.query(...)`, `f.email()`, typed defs — not a JSON dump.
|
|
316
|
+
And not a loose pile of files either: you get the same runnable project `xanots init`
|
|
317
|
+
scaffolds, with the pulled workspace filling `xano/`. So a pull deploys:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
xanots workspace codegen my-app # your real workspace (the one your login is scoped to)
|
|
321
|
+
cd my-app
|
|
322
|
+
npm run build
|
|
323
|
+
npm run xano:deploy # → a live ephemeral URL
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The other three sources are the same command with a different origin — `xanots sandbox
|
|
327
|
+
codegen`, `xanots ephemeral codegen`, and `xanots codegen ws.json` for a bundle already on
|
|
328
|
+
disk (offline, no login).
|
|
329
|
+
|
|
330
|
+
Object identities are preserved, so cross-references stay intact, and a statement this SDK
|
|
331
|
+
does not model yet round-trips verbatim rather than breaking the pull. Then it checks its own
|
|
332
|
+
work: the tree it just wrote is loaded, exported, and diffed against the workspace it came
|
|
333
|
+
from, so "it compiled" and "it means the same thing" are separate claims and you get both.
|
|
334
|
+
|
|
335
|
+
> ⚠️ **`xano/` is a scratch surface.** Regenerating rewrites it, it carries schema only — no
|
|
336
|
+
> table rows — and deploying it is a *full replace* of the target. Pull from your real
|
|
337
|
+
> workspace, edit, and `deploy` to a disposable ephemeral or sandbox. Workspace env var
|
|
338
|
+
> **values** ride inline in `xano/workspace.ts`, so treat a pulled tree as secret-bearing
|
|
339
|
+
> before you commit it.
|
|
340
|
+
|
|
341
|
+
What the generated tree looks like, how faithful the pull is, and how to read its report are
|
|
342
|
+
in [Pulling an existing workspace](https://github.com/xanots/sdk/blob/main/guides/codegen.md).
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Reference
|
|
347
|
+
|
|
348
|
+
Every kind, statement, filter, and field type is typed, so your editor's autocomplete is
|
|
349
|
+
the fastest lookup — tab-complete `s.`, `f.`, `c.`, `fl.`, `input.`. For the written
|
|
350
|
+
reference, the guides carry the shape of a project and the behavior that will bite you:
|
|
351
|
+
|
|
352
|
+
| Guide | What's in it |
|
|
353
|
+
|---|---|
|
|
354
|
+
| [Project structure](https://github.com/xanots/sdk/blob/main/guides/project-structure.md) | How a `xano/` project is laid out, and why registration is explicit |
|
|
355
|
+
| [The scaffolded project](https://github.com/xanots/sdk/blob/main/guides/scaffold.md) | What `xanots init` writes, the two frontend presets, add-ons, SvelteKit rules |
|
|
356
|
+
| [Object kinds](https://github.com/xanots/sdk/blob/main/guides/object-kinds.md) | Every authorable kind, and splitting a workspace across microservices |
|
|
357
|
+
| [Authoring reference](https://github.com/xanots/sdk/blob/main/guides/authoring.md) | Tables and fields, statements, values, inputs, middleware, seed data |
|
|
358
|
+
| [CLI](https://github.com/xanots/sdk/blob/main/guides/cli.md) | Every command, shell completion, and what failures look like |
|
|
359
|
+
| [Signing in & deploying](https://github.com/xanots/sdk/blob/main/guides/deploying.md) | Auth, ephemerals, `--static`, releasing to production, `xanots validate` |
|
|
360
|
+
| [Environment & identity](https://github.com/xanots/sdk/blob/main/guides/environment.md) | Every environment variable, and how `xano.lock` pins identity |
|
|
361
|
+
| [The typed frontend surface](https://github.com/xanots/sdk/blob/main/guides/typed-frontend.md) | Path resolution, input/response inference, bundle cost, the route manifest |
|
|
362
|
+
| [Pulling an existing workspace](https://github.com/xanots/sdk/blob/main/guides/codegen.md) | What `xanots codegen` writes, how faithful it is, and how to read its report |
|
|
363
|
+
| [Coverage & agent grounding](https://github.com/xanots/sdk/blob/main/guides/coverage.md) | What is covered, what is out of scope, and the files agents read |
|
|
364
|
+
|
|
365
|
+
The package also ships two machine-readable descriptions of the same surface, generated from
|
|
366
|
+
the SDK's own sources so they cannot drift from it: `llms.txt` (the router — the mental model
|
|
367
|
+
and the gotchas, plus a map of the `llms/*.md` topic files) and `manifest.json` (per-entry
|
|
368
|
+
detail: field schemas with engine defaults, filter argument lists, stored-name mappings).
|
|
369
|
+
Both are readable by people too.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
<div align="center">
|
|
374
|
+
|
|
375
|
+
**Write TypeScript. Run `xanots deploy`. See it live.**
|
|
376
|
+
|
|
377
|
+
[npm](https://www.npmjs.com/package/@xanots/sdk) ·
|
|
378
|
+
[GitHub](https://github.com/xanots/sdk) ·
|
|
379
|
+
[Issues](https://github.com/xanots/sdk/issues) ·
|
|
380
|
+
[`llms.txt`](llms.txt) — the agent-facing router, mapping the `llms/` topic files
|
|
381
|
+
|
|
382
|
+
XanoTS is the official TypeScript SDK for [Xano](https://xano.com). MIT licensed.
|
|
383
|
+
|
|
384
|
+
</div>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AI_PRESETS,
|
|
3
|
+
CODEGEN_MARKER,
|
|
4
|
+
managedBlockVersion,
|
|
5
|
+
presetFilePath,
|
|
6
|
+
renderPreset,
|
|
7
|
+
upsertManagedBlock
|
|
8
|
+
} from "./chunk-VKFQJEMW.js";
|
|
9
|
+
import "./chunk-47WDWMBJ.js";
|
|
10
|
+
import {
|
|
11
|
+
warn
|
|
12
|
+
} from "./chunk-EZG76F7R.js";
|
|
13
|
+
|
|
14
|
+
// src/emit/agent-file-refresh.ts
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
16
|
+
import { basename, join } from "path";
|
|
17
|
+
var AGENT_ENV_VARS = [
|
|
18
|
+
"CLAUDECODE",
|
|
19
|
+
"CLAUDE_CODE",
|
|
20
|
+
"CURSOR_TRACE_ID",
|
|
21
|
+
"CURSOR_AGENT",
|
|
22
|
+
"CODEX_SANDBOX",
|
|
23
|
+
"AIDER_MODEL",
|
|
24
|
+
"GITHUB_COPILOT_AGENT"
|
|
25
|
+
];
|
|
26
|
+
function codingAgentDetected(env = process.env) {
|
|
27
|
+
return AGENT_ENV_VARS.some((name) => (env[name] ?? "") !== "");
|
|
28
|
+
}
|
|
29
|
+
var CI_ENV_VARS = [
|
|
30
|
+
"CI",
|
|
31
|
+
"GITHUB_ACTIONS",
|
|
32
|
+
"GITLAB_CI",
|
|
33
|
+
"BUILDKITE",
|
|
34
|
+
"TEAMCITY_VERSION"
|
|
35
|
+
];
|
|
36
|
+
function isCI(env = process.env) {
|
|
37
|
+
return CI_ENV_VARS.some((name) => (env[name] ?? "") !== "");
|
|
38
|
+
}
|
|
39
|
+
function guidanceModeFor(projectDir) {
|
|
40
|
+
return existsSync(join(projectDir, CODEGEN_MARKER)) ? "generated" : "authored";
|
|
41
|
+
}
|
|
42
|
+
function appNameFor(projectDir) {
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(readFileSync(join(projectDir, "package.json"), "utf8"));
|
|
45
|
+
if (typeof pkg.name === "string" && pkg.name.length > 0) return pkg.name;
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
return basename(projectDir);
|
|
49
|
+
}
|
|
50
|
+
function refreshAgentFiles(opts) {
|
|
51
|
+
const env = opts.env ?? process.env;
|
|
52
|
+
if (isCI(env) || !codingAgentDetected(env)) return [];
|
|
53
|
+
const mode = opts.mode ?? guidanceModeFor(opts.projectDir);
|
|
54
|
+
const appName = opts.appName ?? appNameFor(opts.projectDir);
|
|
55
|
+
const managed = [];
|
|
56
|
+
for (const preset of AI_PRESETS) {
|
|
57
|
+
const path = presetFilePath(preset);
|
|
58
|
+
const absolute = join(opts.projectDir, path);
|
|
59
|
+
if (!existsSync(absolute)) continue;
|
|
60
|
+
try {
|
|
61
|
+
const existing = readFileSync(absolute, "utf8");
|
|
62
|
+
const version = managedBlockVersion(existing);
|
|
63
|
+
if (version === null) continue;
|
|
64
|
+
managed.push({ preset, path, existing, version });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
warn(`could not read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const selected = managed.map((m) => m.preset);
|
|
70
|
+
const refreshed = [];
|
|
71
|
+
for (const { preset, path, existing, version } of managed) {
|
|
72
|
+
const absolute = join(opts.projectDir, path);
|
|
73
|
+
try {
|
|
74
|
+
if (version === opts.sdkVersion) continue;
|
|
75
|
+
const rendered = renderPreset(preset, appName, mode, {
|
|
76
|
+
selected,
|
|
77
|
+
version: opts.sdkVersion
|
|
78
|
+
});
|
|
79
|
+
const updated = upsertManagedBlock(existing, rendered);
|
|
80
|
+
if (updated === existing) continue;
|
|
81
|
+
writeFileSync(absolute, updated);
|
|
82
|
+
refreshed.push(path);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
warn(`could not refresh ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return refreshed;
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
CI_ENV_VARS,
|
|
91
|
+
codingAgentDetected,
|
|
92
|
+
isCI,
|
|
93
|
+
refreshAgentFiles
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=agent-file-refresh-7XCACU34.js.map
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readVersion,
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-YGPIY3WT.js";
|
|
6
|
+
import {
|
|
7
|
+
exitCodeOf,
|
|
8
|
+
reportFailure
|
|
9
|
+
} from "./chunk-IZW6MK3U.js";
|
|
10
|
+
import {
|
|
11
|
+
blank,
|
|
12
|
+
clearProgress,
|
|
13
|
+
detail,
|
|
14
|
+
error,
|
|
15
|
+
style,
|
|
16
|
+
warn
|
|
17
|
+
} from "./chunk-EZG76F7R.js";
|
|
18
|
+
import "./chunk-3DUAGYGP.js";
|
|
19
|
+
import "./chunk-YYRDLFSJ.js";
|
|
20
|
+
import "./chunk-EMCAWMHC.js";
|
|
21
|
+
import "./chunk-WHOJWOSV.js";
|
|
22
|
+
import "./chunk-LH6Z6KFV.js";
|
|
23
|
+
import "./chunk-HJPTWBLH.js";
|
|
24
|
+
import {
|
|
25
|
+
atomicWrite
|
|
26
|
+
} from "./chunk-GNPVYOPB.js";
|
|
27
|
+
import "./chunk-4HT3BNZ7.js";
|
|
28
|
+
|
|
29
|
+
// src/emit/update-check.ts
|
|
30
|
+
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
31
|
+
import { homedir } from "os";
|
|
32
|
+
import { dirname, join } from "path";
|
|
33
|
+
import { createRequire } from "module";
|
|
34
|
+
function registryUrl() {
|
|
35
|
+
return process.env.XANOTS_UPDATE_REGISTRY ?? "https://registry.npmjs.org/@xanots/sdk/latest";
|
|
36
|
+
}
|
|
37
|
+
function cachePath() {
|
|
38
|
+
return process.env.XANOTS_UPDATE_CACHE ?? join(homedir(), ".xanots", "update-check.json");
|
|
39
|
+
}
|
|
40
|
+
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
41
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
42
|
+
function isNewer(candidate, current) {
|
|
43
|
+
const a = parseSemver(candidate);
|
|
44
|
+
const b = parseSemver(current);
|
|
45
|
+
if (!a || !b) return false;
|
|
46
|
+
for (let i = 0; i < 3; i++) {
|
|
47
|
+
if (a.nums[i] !== b.nums[i]) return a.nums[i] > b.nums[i];
|
|
48
|
+
}
|
|
49
|
+
if (a.pre.length === 0) return b.pre.length > 0;
|
|
50
|
+
if (b.pre.length === 0) return false;
|
|
51
|
+
return comparePre(a.pre, b.pre) > 0;
|
|
52
|
+
}
|
|
53
|
+
function parseSemver(v) {
|
|
54
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(v.trim());
|
|
55
|
+
if (!m) return null;
|
|
56
|
+
return {
|
|
57
|
+
nums: [Number(m[1]), Number(m[2]), Number(m[3])],
|
|
58
|
+
pre: m[4] ? m[4].split(".") : []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function comparePre(a, b) {
|
|
62
|
+
const len = Math.max(a.length, b.length);
|
|
63
|
+
for (let i = 0; i < len; i++) {
|
|
64
|
+
if (i >= a.length) return -1;
|
|
65
|
+
if (i >= b.length) return 1;
|
|
66
|
+
const x = a[i];
|
|
67
|
+
const y = b[i];
|
|
68
|
+
const xn = /^\d+$/.test(x);
|
|
69
|
+
const yn = /^\d+$/.test(y);
|
|
70
|
+
if (xn && yn) {
|
|
71
|
+
if (Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1;
|
|
72
|
+
} else if (xn !== yn) {
|
|
73
|
+
return xn ? -1 : 1;
|
|
74
|
+
} else if (x !== y) {
|
|
75
|
+
return x < y ? -1 : 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
function readCache() {
|
|
81
|
+
const path = cachePath();
|
|
82
|
+
if (!existsSync(path)) return void 0;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
85
|
+
if (typeof parsed.latest === "string" && typeof parsed.checkedAt === "number") {
|
|
86
|
+
return { latest: parsed.latest, checkedAt: parsed.checkedAt };
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
function writeCache(cache) {
|
|
93
|
+
try {
|
|
94
|
+
const path = cachePath();
|
|
95
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
96
|
+
atomicWrite(path, JSON.stringify(cache, null, 2) + "\n");
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function fetchLatest() {
|
|
101
|
+
try {
|
|
102
|
+
const res = await fetch(registryUrl(), {
|
|
103
|
+
headers: { accept: "application/json" },
|
|
104
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) return null;
|
|
107
|
+
const data = await res.json();
|
|
108
|
+
return typeof data.version === "string" ? data.version : null;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function disabled() {
|
|
114
|
+
return Boolean(
|
|
115
|
+
process.env.XANOTS_NO_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER || process.env.CI
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
async function resolveUpdateNotice(opts) {
|
|
119
|
+
if (!opts?.force && disabled()) return null;
|
|
120
|
+
const current = opts?.current ?? readVersion();
|
|
121
|
+
if (current === "unknown") return null;
|
|
122
|
+
const cache = readCache();
|
|
123
|
+
let latest = cache?.latest;
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
|
|
126
|
+
const fetched = await fetchLatest();
|
|
127
|
+
if (fetched) {
|
|
128
|
+
latest = fetched;
|
|
129
|
+
writeCache({ latest: fetched, checkedAt: now });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return latest && isNewer(latest, current) ? { current, latest } : null;
|
|
133
|
+
}
|
|
134
|
+
function detectInstallMode() {
|
|
135
|
+
const override = process.env.XANOTS_INSTALL_MODE;
|
|
136
|
+
if (override === "global" || override === "local") return override;
|
|
137
|
+
try {
|
|
138
|
+
const requireFromCwd = createRequire(join(process.cwd(), "package.json"));
|
|
139
|
+
requireFromCwd.resolve("@xanots/sdk/package.json");
|
|
140
|
+
return "local";
|
|
141
|
+
} catch {
|
|
142
|
+
return "global";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function upgradeCommand(mode) {
|
|
146
|
+
return mode === "global" ? "npm i -g @xanots/sdk@latest" : "npm i -D @xanots/sdk@latest";
|
|
147
|
+
}
|
|
148
|
+
function printUpdateNotice(notice) {
|
|
149
|
+
blank();
|
|
150
|
+
warn(
|
|
151
|
+
`A new @xanots/sdk is available: ${style.dim(notice.current)} \u2192 ${style.green(notice.latest)}`
|
|
152
|
+
);
|
|
153
|
+
detail(`update: ${upgradeCommand(detectInstallMode())}`);
|
|
154
|
+
}
|
|
155
|
+
function runExitedClean(code = process.exitCode) {
|
|
156
|
+
return code === void 0 || code === 0;
|
|
157
|
+
}
|
|
158
|
+
async function maybeNotifyUpdate() {
|
|
159
|
+
try {
|
|
160
|
+
if (disabled() || process.stderr.isTTY !== true || !runExitedClean()) return;
|
|
161
|
+
const notice = await resolveUpdateNotice();
|
|
162
|
+
if (notice) printUpdateNotice(notice);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/emit/interrupt.ts
|
|
168
|
+
var EXIT_SIGINT = 130;
|
|
169
|
+
function handleInterrupt(exit = process.exit) {
|
|
170
|
+
clearProgress();
|
|
171
|
+
error("Cancelled.");
|
|
172
|
+
exit(EXIT_SIGINT);
|
|
173
|
+
}
|
|
174
|
+
function installInterruptHandler() {
|
|
175
|
+
process.on("SIGINT", () => handleInterrupt());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/emit/bin.ts
|
|
179
|
+
installInterruptHandler();
|
|
180
|
+
run(process.argv.slice(2)).then(() => maybeNotifyUpdate()).catch((err) => {
|
|
181
|
+
reportFailure(err, readVersion());
|
|
182
|
+
process.exit(exitCodeOf(err));
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=bin.js.map
|