@xano/sdk 1.0.0
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 +788 -0
- package/LICENSE +21 -0
- package/README.md +541 -0
- package/dist/.build-fingerprint +1 -0
- package/dist/agent-file-refresh-ZEMK33AX.js +38 -0
- package/dist/answer-shape-K67TAAPY.js +33 -0
- package/dist/backend-dir-2U53CIAL.js +39 -0
- package/dist/backend-slot-7QXW2QYC.js +28 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +255 -0
- package/dist/branch-NCRNWQBQ.js +61 -0
- package/dist/branch-commands-ACIRCEQJ.js +79 -0
- package/dist/bundle.d.ts +223 -0
- package/dist/bundle.js +153 -0
- package/dist/capture-WAXPD7DR.js +41 -0
- package/dist/chunk-27GWNP2I.js +23 -0
- package/dist/chunk-2CMJZDCL.js +177 -0
- package/dist/chunk-2EZ3ZJZZ.js +302 -0
- package/dist/chunk-2GNFDZA2.js +390 -0
- package/dist/chunk-2MNLRIOX.js +11 -0
- package/dist/chunk-2TFRR6JV.js +104 -0
- package/dist/chunk-2ZUEE4EK.js +408 -0
- package/dist/chunk-36JUQZVQ.js +22 -0
- package/dist/chunk-3DRXWLJ5.js +83 -0
- package/dist/chunk-3JDU4VRL.js +137 -0
- package/dist/chunk-3KZCLN4Z.js +241 -0
- package/dist/chunk-3LDULPRE.js +168 -0
- package/dist/chunk-3M4LFYNQ.js +111 -0
- package/dist/chunk-3XI5CTUE.js +22 -0
- package/dist/chunk-3YW23UFH.js +50 -0
- package/dist/chunk-45UIT5T2.js +862 -0
- package/dist/chunk-4BW2SG2N.js +46 -0
- package/dist/chunk-4KHEMC32.js +79 -0
- package/dist/chunk-4POWHT2K.js +321 -0
- package/dist/chunk-4Q2GN7NW.js +192 -0
- package/dist/chunk-5A2PLKZB.js +70 -0
- package/dist/chunk-5FBYXK5P.js +159 -0
- package/dist/chunk-5HVXPTU4.js +323 -0
- package/dist/chunk-5QU2P5CA.js +304 -0
- package/dist/chunk-5V5XU32D.js +191 -0
- package/dist/chunk-5ZSO4T2G.js +10 -0
- package/dist/chunk-622HUCZT.js +10 -0
- package/dist/chunk-65HWOMGV.js +177 -0
- package/dist/chunk-6ELFRN3P.js +365 -0
- package/dist/chunk-6HO3NCSJ.js +1055 -0
- package/dist/chunk-6IUMKYWI.js +457 -0
- package/dist/chunk-6KEB3C3U.js +65 -0
- package/dist/chunk-6MR5TI5V.js +987 -0
- package/dist/chunk-6NNTNXN7.js +157 -0
- package/dist/chunk-6PBR37LN.js +97 -0
- package/dist/chunk-6QXLMMT3.js +155 -0
- package/dist/chunk-6ZGJCBZS.js +224 -0
- package/dist/chunk-73IVBKMA.js +250 -0
- package/dist/chunk-7G3CTU7I.js +286 -0
- package/dist/chunk-A3UDOZB6.js +222 -0
- package/dist/chunk-A6E77GDW.js +85 -0
- package/dist/chunk-AHJWCYQO.js +660 -0
- package/dist/chunk-AKNCFVKZ.js +36 -0
- package/dist/chunk-AM4FKL4J.js +237 -0
- package/dist/chunk-AN6BL47T.js +26 -0
- package/dist/chunk-AOSYVMUS.js +147 -0
- package/dist/chunk-APWXY5SL.js +182 -0
- package/dist/chunk-AWEIIR2U.js +218 -0
- package/dist/chunk-AWQ7BQTQ.js +73 -0
- package/dist/chunk-BAUZOWGX.js +393 -0
- package/dist/chunk-BHACRS6R.js +940 -0
- package/dist/chunk-BWPNT55T.js +680 -0
- package/dist/chunk-C6RIMRU4.js +187 -0
- package/dist/chunk-CGVQZXYG.js +263 -0
- package/dist/chunk-CIMQZRRE.js +162 -0
- package/dist/chunk-CJHQKJU4.js +113 -0
- package/dist/chunk-CTMO46PK.js +1897 -0
- package/dist/chunk-D53J55C5.js +106 -0
- package/dist/chunk-D7VGG3WS.js +90 -0
- package/dist/chunk-DAD3ONIS.js +245 -0
- package/dist/chunk-DGWHAVRG.js +162 -0
- package/dist/chunk-DX4QR4B2.js +483 -0
- package/dist/chunk-EHP3WPEG.js +21 -0
- package/dist/chunk-ELP2HMXC.js +432 -0
- package/dist/chunk-ERVIASGL.js +21 -0
- package/dist/chunk-ET4Y4XCB.js +865 -0
- package/dist/chunk-ETO6LGLD.js +4750 -0
- package/dist/chunk-EUUUMY7J.js +24 -0
- package/dist/chunk-FQJ2TTHP.js +218 -0
- package/dist/chunk-FQOF7E2E.js +133 -0
- package/dist/chunk-FWD7AL6N.js +21 -0
- package/dist/chunk-FXFV3KU6.js +138 -0
- package/dist/chunk-FXRQOWTE.js +171 -0
- package/dist/chunk-G554NZNI.js +210 -0
- package/dist/chunk-GSGDWJB5.js +13 -0
- package/dist/chunk-H2V3N23D.js +66 -0
- package/dist/chunk-H5C7MBCR.js +159 -0
- package/dist/chunk-HHN734BU.js +371 -0
- package/dist/chunk-HRVBLCVJ.js +243 -0
- package/dist/chunk-HVRUQXGD.js +1264 -0
- package/dist/chunk-I4F5TCJH.js +130 -0
- package/dist/chunk-IDOTCMDV.js +57 -0
- package/dist/chunk-IGOLHKWF.js +427 -0
- package/dist/chunk-JGE7CAWB.js +148 -0
- package/dist/chunk-JTUKLCXS.js +92 -0
- package/dist/chunk-JZGVVJSI.js +66 -0
- package/dist/chunk-K4WX6UUU.js +923 -0
- package/dist/chunk-K5BBGTYZ.js +217 -0
- package/dist/chunk-K6RXBGUB.js +427 -0
- package/dist/chunk-KTSDSD36.js +165 -0
- package/dist/chunk-L5JYN3NM.js +1038 -0
- package/dist/chunk-LIEOKXKD.js +354 -0
- package/dist/chunk-LLNZGEFK.js +51 -0
- package/dist/chunk-LPECSJ2J.js +55 -0
- package/dist/chunk-LQCMKAJ7.js +413 -0
- package/dist/chunk-LRN4ZJCQ.js +1145 -0
- package/dist/chunk-MJ4ZVHKN.js +17 -0
- package/dist/chunk-MXHSQOC4.js +3061 -0
- package/dist/chunk-N5GKCWE5.js +367 -0
- package/dist/chunk-NA3PXMEF.js +10 -0
- package/dist/chunk-NAQDMEHV.js +76 -0
- package/dist/chunk-NWKXM5RD.js +109 -0
- package/dist/chunk-NZOWWHU5.js +196 -0
- package/dist/chunk-OHX6MIUZ.js +184 -0
- package/dist/chunk-OOXWYAA4.js +15 -0
- package/dist/chunk-OPHB5ORL.js +1990 -0
- package/dist/chunk-OXA6PSZT.js +1725 -0
- package/dist/chunk-P2HAGF6H.js +73 -0
- package/dist/chunk-PTHVMXZI.js +3036 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q325JKYB.js +189 -0
- package/dist/chunk-Q7DGTQ5G.js +255 -0
- package/dist/chunk-QCNYHBL7.js +601 -0
- package/dist/chunk-QO6BVGS5.js +145 -0
- package/dist/chunk-QXTKSOFI.js +76 -0
- package/dist/chunk-ROY4YAEQ.js +66 -0
- package/dist/chunk-RTJRZFFY.js +447 -0
- package/dist/chunk-SAVJUS2X.js +128 -0
- package/dist/chunk-SFLHVCKX.js +173 -0
- package/dist/chunk-STSFNCY4.js +127 -0
- package/dist/chunk-SWBF2LTK.js +170 -0
- package/dist/chunk-T5KELPAR.js +109 -0
- package/dist/chunk-TFFGOFVZ.js +1481 -0
- package/dist/chunk-TLHIFK4M.js +256 -0
- package/dist/chunk-TLQE7ZN3.js +156 -0
- package/dist/chunk-TPHWJ7UG.js +1981 -0
- package/dist/chunk-TRJCALW3.js +291 -0
- package/dist/chunk-TTEIYD5P.js +133 -0
- package/dist/chunk-U64O2MXY.js +407 -0
- package/dist/chunk-UCQ2VUTG.js +630 -0
- package/dist/chunk-UTI5G6OJ.js +44 -0
- package/dist/chunk-UVHDMMIP.js +188 -0
- package/dist/chunk-UZ6QQWJT.js +120 -0
- package/dist/chunk-VAX3UZOY.js +686 -0
- package/dist/chunk-W3HBUKFO.js +57 -0
- package/dist/chunk-WKQLGKCL.js +2823 -0
- package/dist/chunk-WQFNASK4.js +560 -0
- package/dist/chunk-WRBKLYSJ.js +224 -0
- package/dist/chunk-WTPZRR4I.js +17 -0
- package/dist/chunk-WZDJ2LLX.js +2385 -0
- package/dist/chunk-X2NL4KUO.js +14 -0
- package/dist/chunk-X4DVXBFY.js +38 -0
- package/dist/chunk-X677DMZM.js +7050 -0
- package/dist/chunk-XBKMWY7F.js +612 -0
- package/dist/chunk-XNCP4KYO.js +78 -0
- package/dist/chunk-XVBGLISP.js +35 -0
- package/dist/chunk-XW6KISBB.js +1192 -0
- package/dist/chunk-YGZHHAAB.js +8156 -0
- package/dist/chunk-YHEFQUQB.js +186 -0
- package/dist/chunk-YLDALTWZ.js +653 -0
- package/dist/chunk-YU4HSQW7.js +229 -0
- package/dist/chunk-ZDPP6RKB.js +27 -0
- package/dist/chunk-ZFDZKNGG.js +194 -0
- package/dist/chunk-ZIY7RNUT.js +49 -0
- package/dist/chunk-ZYJP6BOO.js +30 -0
- package/dist/cli.d.ts +1111 -0
- package/dist/cli.js +165 -0
- package/dist/codegen-command-VR2GWR6C.js +173 -0
- package/dist/codegen.d.ts +115 -0
- package/dist/codegen.js +19 -0
- package/dist/commands-GJMJ4XYZ.js +76 -0
- package/dist/completion-ZQLO6PB6.js +738 -0
- package/dist/config-MBYGCAFK.js +136 -0
- package/dist/deploy-command-JVZPWHMK.js +180 -0
- package/dist/deploy-source-3W45CXMP.js +81 -0
- package/dist/dev-env-sync-HN44K4D5.js +188 -0
- package/dist/diagnostics-UEAAHWVF.js +30 -0
- package/dist/display-name-hint-ABBPVQIR.js +46 -0
- package/dist/emit-CyhBDxli.d.ts +7007 -0
- package/dist/env-example-refresh-ET5HMMDW.js +126 -0
- package/dist/env-pull-command-TNMNB3CV.js +323 -0
- package/dist/env-target-O4HRKTGZ.js +40 -0
- package/dist/env-var-command-ZIRDCAZA.js +427 -0
- package/dist/ephemeral-P7F3PMAD.js +54 -0
- package/dist/ephemeral-command-CLJJKOFT.js +592 -0
- package/dist/ephemeral-state-PYCBSOGX.js +61 -0
- package/dist/file-lock-PT74GWYW.js +10 -0
- package/dist/generate-command-AYEOLA26.js +510 -0
- package/dist/gitignore-3SJ3WNRG.js +51 -0
- package/dist/guards-4EBNCZDC.js +162 -0
- package/dist/hosted-file-PSHJB3HX.js +28 -0
- package/dist/impersonate-command-5JAS3WSV.js +152 -0
- package/dist/import-EQSFU6JC.js +19 -0
- package/dist/index.d.ts +5470 -0
- package/dist/index.js +371 -0
- package/dist/init-command-X2FR4UAK.js +125 -0
- package/dist/init-modules-RY7UHAOL.js +50 -0
- package/dist/init-web-M4YZSZK6.js +42 -0
- package/dist/internal.d.ts +179 -0
- package/dist/internal.js +2634 -0
- package/dist/io-RC56QSN5.js +26 -0
- package/dist/keep-data-merge-7MPLVAVI.js +1119 -0
- package/dist/lambda-config-reconcile-OUZ4OYAB.js +116 -0
- package/dist/landed-753YLJ5H.js +40 -0
- package/dist/landing-record-SU52SKXU.js +61 -0
- package/dist/live-diff-YONW2VI3.js +65 -0
- package/dist/local-engine-choice-CH5X3W7E.js +79 -0
- package/dist/local-engine-command-Z3OL7VOD.js +766 -0
- package/dist/local-engine-config-5U3SC434.js +39 -0
- package/dist/local-engine-deploy-PCQ4SNPB.js +39 -0
- package/dist/local-engine-process-EHTE5LLU.js +63 -0
- package/dist/local-engine-state-A4FTRFAF.js +32 -0
- package/dist/local-engine-update-check-6L645VBL.js +162 -0
- package/dist/lock-MRRHW5GS.js +87 -0
- package/dist/lock-commands-2QG25HL7.js +963 -0
- package/dist/login-command-QPYRWOXZ.js +538 -0
- package/dist/logout-command-WOIQLDP6.js +56 -0
- package/dist/loop-ALIZLOSX.js +191 -0
- package/dist/marketplace-command-K5YL42NO.js +977 -0
- package/dist/marketplace-resolve-LY32JES5.js +40 -0
- package/dist/meta-client-YXGPW53C.js +244 -0
- package/dist/module-manifest-JFGHU2TC.js +38 -0
- package/dist/near-key.d.ts +13 -0
- package/dist/near-key.js +8 -0
- package/dist/node.d.ts +95 -0
- package/dist/node.js +969 -0
- package/dist/operation-outcome-TZSESNC7.js +51 -0
- package/dist/output-target-AL3I44GG.js +47 -0
- package/dist/package-manager-PRAHUSCN.js +34 -0
- package/dist/plan-disclosure-6G4MLZKD.js +114 -0
- package/dist/plugin-contributions-WRNY5ABG.js +28 -0
- package/dist/plugin-prompt-4VA3B4FF.js +20 -0
- package/dist/plugin-questions-JTSYA74Q.js +24 -0
- package/dist/plugin.d.ts +396 -0
- package/dist/plugin.js +1 -0
- package/dist/preflight-command-2T5Z36MI.js +321 -0
- package/dist/profile-command-AH3PIM4X.js +800 -0
- package/dist/profile-pointer-GRWJU3YD.js +31 -0
- package/dist/profile-select-74TRDEPD.js +23 -0
- package/dist/project-dependency-KSH63ODT.js +35 -0
- package/dist/project-sdk-REPPOIBD.js +24 -0
- package/dist/promote-command-PCWT5N6S.js +140 -0
- package/dist/prompt-II3F3LND.js +27 -0
- package/dist/publish-command-TTDUUJI5.js +414 -0
- package/dist/pull-command-VBO7C4S3.js +161 -0
- package/dist/pull-superseded-GJWLG7XO.js +20 -0
- package/dist/release-command-N4ZPKMHR.js +146 -0
- package/dist/release-ns-command-3KFDMZ6G.js +1745 -0
- package/dist/response-HlXahv4K.d.ts +925 -0
- package/dist/retry-command-AS5EVREX.js +34 -0
- package/dist/routes-manifest-SNWUVBT6.js +43 -0
- package/dist/runtime-XETHVF33.js +14 -0
- package/dist/scaffold.d.ts +1911 -0
- package/dist/scaffold.js +107 -0
- package/dist/secrets-file-LVZD6IIM.js +56 -0
- package/dist/secrets-fill-command-TTCPSVXN.js +218 -0
- package/dist/seed-VU7C3QYS.js +46 -0
- package/dist/sent-writes-N7X4OVXT.js +22 -0
- package/dist/source-resolve-S2XA3XZF.js +83 -0
- package/dist/static-host-B2Y6AG3W.js +352 -0
- package/dist/status-command-RUDXTF7I.js +554 -0
- package/dist/store-6VNCD4D7.js +20 -0
- package/dist/store-ARM6VMO5.js +98 -0
- package/dist/store-DuYfzKlW.d.ts +3144 -0
- package/dist/suggest-6FOOYKHG.js +12 -0
- package/dist/sveltekit.d.ts +154 -0
- package/dist/sveltekit.js +109 -0
- package/dist/sync-contributions-UCEROO26.js +415 -0
- package/dist/table-ENAVMITR.js +42 -0
- package/dist/tables-command-GUPXXSB5.js +156 -0
- package/dist/tenant-IIHPOHGP.js +42 -0
- package/dist/tenant-command-D5HGI2X4.js +965 -0
- package/dist/test-command-TIKZMDPZ.js +476 -0
- package/dist/tests-BGGIOK3L.js +26 -0
- package/dist/token-NICJUTMT.js +72 -0
- package/dist/toolchain-hooks-GHKLUJOH.js +96 -0
- package/dist/toolchain-modules-5EAZUONB.js +24 -0
- package/dist/tracked-backend-EIBUMDZA.js +80 -0
- package/dist/update-check-2W2NLDTC.js +136 -0
- package/dist/upgrade-command-QHNFWM7O.js +531 -0
- package/dist/verify-rollout-DRCK5TDD.js +61 -0
- package/dist/whoami-command-STGINTXH.js +51 -0
- package/dist/workspace-2CLHU2LK.js +93 -0
- package/dist/workspace-binding-56PIIPCP.js +54 -0
- package/dist/workspace-command-JEXQETJD.js +535 -0
- package/dist/workspace-env-F2RFMSC5.js +70 -0
- package/dist/workspace-export-OJSH2HIW.js +21 -0
- package/dist/writable-VA7OISSK.js +25 -0
- package/dist/xano-ICFUSPFR.js +42 -0
- package/dist/xanosdk-import-GN42TO2B.js +56 -0
- package/dist/xanosdk-project-S6DXJONQ.js +41 -0
- package/dist/xdo-CxDr2Q1C.d.ts +1371 -0
- package/guides/README.md +33 -0
- package/guides/authoring.md +921 -0
- package/guides/cli.md +401 -0
- package/guides/codegen.md +177 -0
- package/guides/codes.md +404 -0
- package/guides/coverage.md +74 -0
- package/guides/deploying.md +1525 -0
- package/guides/environment.md +400 -0
- package/guides/marketplace.md +415 -0
- package/guides/module-contract.md +558 -0
- package/guides/object-kinds.md +433 -0
- package/guides/project-structure.md +43 -0
- package/guides/scaffold.md +351 -0
- package/guides/typed-frontend.md +242 -0
- package/llms/client.md +63 -0
- package/llms/errors.md +56 -0
- package/llms/fields.md +176 -0
- package/llms/filters.md +183 -0
- package/llms/kinds-agent-mcp.md +30 -0
- package/llms/kinds-core.md +100 -0
- package/llms/kinds-knowledge.md +20 -0
- package/llms/kinds-realtime.md +76 -0
- package/llms/lambda.md +94 -0
- package/llms/legacy.md +44 -0
- package/llms/lock.md +40 -0
- package/llms/object-kinds.md +34 -0
- package/llms/statements-calls.md +62 -0
- package/llms/statements-catalog.md +320 -0
- package/llms/statements-data.md +66 -0
- package/llms/statements-runtime.md +68 -0
- package/llms/tests.md +59 -0
- package/llms/toolchain.md +16 -0
- package/llms/triggers.md +36 -0
- package/llms/values.md +29 -0
- package/llms-full.txt +2085 -0
- package/llms.txt +493 -0
- package/manifest.json +11224 -0
- package/package.json +162 -0
- package/types/lambda-globals.d.ts +129 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
# xanosdk v1.0.0
|
|
2
|
+
|
|
3
|
+
> TypeScript SDK that compiles a typed Xano workspace into the importable packageExport JSON bundle.
|
|
4
|
+
|
|
5
|
+
Xano is a hosted backend platform: one WORKSPACE serves a managed PostgreSQL
|
|
6
|
+
database, HTTP API endpoints, background tasks, realtime websocket channels, AI
|
|
7
|
+
agents and MCP servers, file storage, and redis. This SDK authors that workspace in
|
|
8
|
+
TypeScript — typed def objects registered on a `workspace(name)` — compiled to one
|
|
9
|
+
importable bundle by `xanosdk export ./xano/index.ts`, shipped live by `xanosdk deploy`.
|
|
10
|
+
|
|
11
|
+
Def modules execute at BUILD time only: `s.*` factories return data, and the engine
|
|
12
|
+
runs the compiled stack per request — statements in order, a statement's `as:`
|
|
13
|
+
naming a runtime variable, `response` the HTTP body. Every dynamic operand is a
|
|
14
|
+
TAGGED value — `ref("x")` a stack variable, `inp("x")` an input, `auth("id")` the
|
|
15
|
+
caller, `c.*` a constant — resolved at request time; JS operators over them do not
|
|
16
|
+
compute (see Gotchas). Requests share no memory — state persists in tables or redis.
|
|
17
|
+
|
|
18
|
+
Authorable: statement surfaces 218/218, filters 246 (246 typed).
|
|
19
|
+
Not authorable here: branch, market_item, realtime_channel, run.job, run.service, tablemap — these cannot be authored and do not survive a pull; reasons in `coverage.objectKinds.unmodeled`.
|
|
20
|
+
|
|
21
|
+
This file is the whole always-read surface: the mental model, the deploy contract,
|
|
22
|
+
every cross-cutting gotcha, and control flow. Per-surface detail lives in the topic files listed
|
|
23
|
+
below — open the one whose condition matches the task, skip the rest. For
|
|
24
|
+
exhaustive per-entry detail in NEITHER — a statement's field schema with engine
|
|
25
|
+
defaults, a filter's full argument list, the `storedName` mapping — do a TARGETED
|
|
26
|
+
lookup in the shipped `manifest.json` (a program imports it as
|
|
27
|
+
`@xano/sdk/manifest.json`, needing `with { type: "json" }` in Node ESM;
|
|
28
|
+
it is ~70k tokens, so never read it whole). Its top-level keys are `name`, `version`, `description`, `coverage`, `values`, `objectKinds`, `fieldTypes`, `statements`, `filters`, `cli`, `cliGlobalFlags`. `statements` and `filters` are ARRAYS, not maps — SELECT, do not index:
|
|
29
|
+
jq '.statements[] | select(.sPath=="db.get")' manifest.json
|
|
30
|
+
jq '.filters[] | select(.name=="json_decode")' manifest.json
|
|
31
|
+
⚠ `fields` is null on the 65 `declarative: false` statements (`db.get`, …): typed wrappers whose arguments are the factory's `.d.ts` signature. Null ≠ missing.
|
|
32
|
+
Select a statement on `sPath` (the `s.*` path you write), NOT `surface` (the XanoScript term): 24 of 218 differ — `var`→`set_var`, `break`→`foreach_break`, `foreach.remove`→`foreach_remove`, and every `expect.*`.
|
|
33
|
+
|
|
34
|
+
## Topic files
|
|
35
|
+
|
|
36
|
+
Paths are relative to this file (`node_modules/@xano/sdk/` once installed), so a
|
|
37
|
+
plain file read resolves them at the version you have.
|
|
38
|
+
`llms-full.txt` is everything concatenated — one fetch for a reader that cannot open files.
|
|
39
|
+
|
|
40
|
+
- [Object kinds](llms/object-kinds.md): Read when deciding WHAT to build — every authorable primitive, what each is, and which factory + register method builds it. Also when `register*` will not typecheck an array built by `.flatMap()`/`.concat()`.
|
|
41
|
+
- [Core def shapes](llms/kinds-core.md): Read when authoring a function, query, api group, task, workflow test, middleware, tool, or addon — and for the `response` and `expr` shapes every one of them uses.
|
|
42
|
+
- [Consuming defs from a client](llms/client.md): Read when a frontend imports a def (`getPath()`/`InferResponse`) or calls the deployed backend — what the import costs, `window.XANO_HOST`, local dev, file URLs, and a Node spot-check.
|
|
43
|
+
- [Toolchain modules](llms/toolchain.md): Read when `package.json` has a `"xanosdk"` block or you run `xanosdk marketplace`.
|
|
44
|
+
- [Error index](llms/errors.md): Read when a request, test, build, or pull fails with a message you did not write — the string, its cause, and where the fix is.
|
|
45
|
+
- [Saved unit tests, assertions, and mocks](llms/tests.md): Read when authoring a `workflowTest()` stack, when a query/function/middleware carries `tests`, when a statement needs a `mock`, or when running a deployed environment's tests.
|
|
46
|
+
- [Agent and MCP def shapes](llms/kinds-agent-mcp.md): Read when defining an `agent()`, `mcpServer()`, `prompt()` or `resource()`.
|
|
47
|
+
- [Knowledge def shape](llms/kinds-knowledge.md): Read when the workspace defines a `knowledge()` item for its AI agents.
|
|
48
|
+
- [Realtime def shapes](llms/kinds-realtime.md): Read when the workspace defines a `realtimeServer()`, `realtimeChannel()`, or `realtimeMessage()` handler.
|
|
49
|
+
- [Triggers](llms/triggers.md): Read when authoring any trigger. A trigger's `stack` is a callback rather than the plain array every other kind takes, so the shape does not carry over.
|
|
50
|
+
- [Array and database statements](llms/statements-data.md): Read when the stack reads or writes rows (`s.db.*`), or transforms an array in place (`s.array.map`, `s.array.union`).
|
|
51
|
+
- [Statement runtime behavior](llms/statements-runtime.md): Read when you need to know what a statement's `as:` output holds, or why a bound variable is not the shape expected.
|
|
52
|
+
- [Auth, cross-object calls, and microservices](llms/statements-calls.md): Read when the stack authenticates or verifies a webhook (recipes here), calls another object (`s.function.run`, `s.api.request`, `s.tool.call`), sends email (`s.util.send_email`), or reaches a microservice.
|
|
53
|
+
- [Value catalog](llms/values.md): Read when you need a literal, a reference, or a tag you have not used before — `c.*`, `ref`, `inp`, `auth`, `col`, and what each one encodes to.
|
|
54
|
+
- [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 methods, and the `s.precondition` error/status contract. Also `table({ seed })` rows and `use_xdo`.
|
|
55
|
+
- [Filter catalog](llms/filters.md): Read when piping a value through `fl.*` — the full catalog with each filter's argument types.
|
|
56
|
+
- [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`; each surface binds a different set of identifiers.
|
|
57
|
+
- [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.
|
|
58
|
+
- [Legacy paradigms and retired statements](llms/legacy.md): Read when the code was PULLED from an existing Xano instance — how a codegen'd tree reads, the shapes in it that must not be "fixed", and any `raw({ name: "mvp:…" })`, `realtimeTrigger()`, or name the catalogs do not list.
|
|
59
|
+
- [Statement catalog](llms/statements-catalog.md): Read for the field signature of a specific statement — every surface, grouped by `s.*` namespace.
|
|
60
|
+
|
|
61
|
+
## Quickstart
|
|
62
|
+
|
|
63
|
+
Authoring is **declarative def-objects** passed to factories — there is no
|
|
64
|
+
callback/chaining builder.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { workspace, apiGroup, query, table, input, f, ref, inp, auth, s } from "@xano/sdk";
|
|
68
|
+
|
|
69
|
+
const users = table({
|
|
70
|
+
name: "users",
|
|
71
|
+
auth: true, // backs authentication
|
|
72
|
+
// `id` (int PK) + `created_at` (epochms) are auto-injected — don't declare them.
|
|
73
|
+
schema: {
|
|
74
|
+
email: f.email({ required: true }),
|
|
75
|
+
name: f.text(),
|
|
76
|
+
},
|
|
77
|
+
// Indexes: { type, fields: [{ name, op? }] }. `"unique"` is shorthand for `"btree|unique"`.
|
|
78
|
+
// A DOTTED name indexes a key inside a column (`xdo.email`); only the part before the
|
|
79
|
+
// first dot is checked against the schema.
|
|
80
|
+
index: [{ type: "unique", fields: [{ name: "email" }] }], // also: btree, hash, gin, search, gist, vector
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const posts = table({
|
|
84
|
+
name: "posts",
|
|
85
|
+
schema: {
|
|
86
|
+
author: f.tableRef(users), // foreign key → users (NOT `ref`)
|
|
87
|
+
body: f.text({ required: true }),
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const api = apiGroup({ name: "blog", canonical: "blog" }); // canonical → the URL token
|
|
92
|
+
|
|
93
|
+
const createPost = query({
|
|
94
|
+
name: "create_post", verb: "POST", apiGroup: api, auth: users, // the auth table
|
|
95
|
+
input: { body: input.text({ required: true }) },
|
|
96
|
+
stack: [
|
|
97
|
+
s.db.add({ table: posts, row: { author: auth("id"), body: inp("body") }, as: "post" }),
|
|
98
|
+
],
|
|
99
|
+
response: ref("post"),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const listPosts = query({
|
|
103
|
+
name: "list_posts", verb: "GET", apiGroup: api,
|
|
104
|
+
stack: [s.db.query({ table: posts, sort: [{ sortBy: "created_at", dir: "desc" }], as: "rows" })],
|
|
105
|
+
response: ref("rows"),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export default workspace("my-blog")
|
|
109
|
+
.registerTables([users, posts])
|
|
110
|
+
.registerApiGroups([api])
|
|
111
|
+
.registerQueries([createPost, listPosts]);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Compile: `xanosdk export ./index.ts --out bundle.json` (or `writeBundle(app, path)`
|
|
115
|
+
from `@xano/sdk/node` in code). The default export must be the `Xano` registry. The entry must be an
|
|
116
|
+
ES module (Xano SDK defs are ESM-only): set `"type": "module"` in the nearest
|
|
117
|
+
package.json or name the entry `.mts`. `npm init -y` writes `"type": "commonjs"`,
|
|
118
|
+
which fails with a "must be ES modules" error until you switch it to module.
|
|
119
|
+
|
|
120
|
+
Set `canonical` on every `apiGroup`. The engine mints the URL token server-side, so
|
|
121
|
+
without one a group's client paths are unresolvable until a lock exists: the bundle
|
|
122
|
+
exports fine and `xanosdk routes` / `getPath()` then fail on the very queries it just
|
|
123
|
+
built. An explicit `canonical` resolves them from the source alone.
|
|
124
|
+
⚠ A canonical is unique across the whole INSTANCE, not per workspace — two
|
|
125
|
+
workspaces cannot serve the same one, and no flag shares it. One set in code is a PIN:
|
|
126
|
+
a merge refuses (exit 2) when another workspace holds it. One the lock minted is a
|
|
127
|
+
preference the instance may keep or replace with a random token, and `--static` is
|
|
128
|
+
refused when it does.
|
|
129
|
+
|
|
130
|
+
Build warnings: `export()` prints the shapes that deploy clean and then do the wrong
|
|
131
|
+
thing (a `bulk.update` zero-filling omitted columns, an `ignoreEmpty` on an empty
|
|
132
|
+
operand, a `ref()` nothing binds). Each has a legitimate use, so each only warns —
|
|
133
|
+
but nothing fails on a message no one reads, so in CI and in unattended agent builds
|
|
134
|
+
pass `--strict` (`emitBundle(app, { strict: true })` / `app.export({ strict: true })`):
|
|
135
|
+
every warning becomes a hard failure. Same bundle bytes either way.
|
|
136
|
+
Before calling the work done: `npm run typecheck`, `xanosdk export <entry> --strict`,
|
|
137
|
+
`xanosdk deploy <entry>`, then `xanosdk test run-all` against what deployed.
|
|
138
|
+
|
|
139
|
+
Identity: object guids derive from `(type, name)` — a query's from `(api group,
|
|
140
|
+
verb, name)` — so renames change identity.
|
|
141
|
+
EVERY build freezes every guid + api-group/toolset canonical in a lock file
|
|
142
|
+
BESIDE THE ENTRY FILE — `xano/xano.lock` for the standard scaffold,
|
|
143
|
+
NOT the project root — which you COMMIT (`--no-lock` opts out; CI guard
|
|
144
|
+
`--frozen-lock` fails instead of changing it).
|
|
145
|
+
To rename an object: rename in code, export (stderr prints the exact fix-up), run
|
|
146
|
+
`xanosdk lock rename <kind> <old> <new>`, export again — the original guid is emitted
|
|
147
|
+
under the new name, so the engine renames in place instead of delete+create. Taking
|
|
148
|
+
over an existing workspace: `xanosdk lock import <live-bundle.json>` first, then
|
|
149
|
+
export. Pruning, programmatic seeding, and which commands write the lock:
|
|
150
|
+
`llms/lock.md`.
|
|
151
|
+
|
|
152
|
+
## Deploy
|
|
153
|
+
|
|
154
|
+
`xanosdk init` → `xanosdk deploy` → URL. A deploy runs the same compile pipeline as
|
|
155
|
+
`export` (honoring `xano.lock`), imports the result, and prints the URL.
|
|
156
|
+
⚠ Every deploy is a FULL REPLACE of its destination — objects AND records.
|
|
157
|
+
|
|
158
|
+
**Backends** — one grammar: `workspace` | `ephemeral[:<name>]` | `local-engine[:<name>]`
|
|
159
|
+
(no credential) | `tenant:<name>` | `release:<name>` | a bundle path where a file fits. Reads
|
|
160
|
+
`--from`, writes `--to`, `test --on`; positional on `deploy`/`pull`/`generate`/`tables`/
|
|
161
|
+
`impersonate`. Omitted = the ephemeral or local engine this project last deployed to, never
|
|
162
|
+
workspace/tenant; bare `deploy` compiles the project onto an ephemeral instead (`--local-engine`:
|
|
163
|
+
an engine on this machine), and `generate`/`init --from` need one. A refusal, typed or tracked,
|
|
164
|
+
names the reason and the spellings (`publish`, `release create` refuse a local engine).
|
|
165
|
+
`release create --from` records a run: no bundle path, no `release:`,
|
|
166
|
+
and a `tenant:` only if throwaway (`standard`/`run` refused).
|
|
167
|
+
|
|
168
|
+
**Destinations** — ephemeral by default, real only when you say so:
|
|
169
|
+
|
|
170
|
+
- `xanosdk deploy [source]` → a NAMED, auto-expiring ephemeral (~1h; `--expires-hours`
|
|
171
|
+
1–24), tracked in `./.xano/ephemeral.json`, so deploying again REFRESHES it at the same
|
|
172
|
+
URL; if it expired or was swept, a fresh one is created and the new URL called out.
|
|
173
|
+
⚠ Only the BACKEND URL survives a refresh — the replace clears static hosting, so
|
|
174
|
+
`--static` publishes a NEW host every run and the previous URL stops serving
|
|
175
|
+
(not under `--keep-data`, which MERGES an env a deploy filled: rows kept, no re-seed,
|
|
176
|
+
removed objects deleted; ⚠ a rename loses values, a RETYPE reads unfit ones as null, env
|
|
177
|
+
VALUES are not applied, a table trigger refuses it — `--reset` replaces and re-seeds).
|
|
178
|
+
- `xanosdk release create <name>` cuts from a running source and LANDS in your workspace
|
|
179
|
+
— the record that it came up, and the only thing `promote`/`tenant deploy` take. A
|
|
180
|
+
taken name is refused, and so is one that addresses a LOCATION instead of naming: `/`,
|
|
181
|
+
`\`, `:`, or a leading `.`. Every export derives its FILENAME from the name it is given,
|
|
182
|
+
so `release|ephemeral|workspace export` refuse those same shapes unless `--path` names
|
|
183
|
+
the file. Carries EVERY table's SCHEMA either way; `--seed[=<guids>]` adds
|
|
184
|
+
ROWS — guids from `xanosdk tables <backend>`; a redeploy KEEPS them.
|
|
185
|
+
- `xanosdk release transfer <name> --to-profile <profile>` copies a release to another
|
|
186
|
+
workspace/instance by CONTENT (tar sha256): identical → reused; same name, other
|
|
187
|
+
bytes → exit 2.
|
|
188
|
+
- `xanosdk promote <release>` / `xanosdk tenant deploy <tenant> <release>` land it.
|
|
189
|
+
`promote` adds a branch; ⚠ its TABLE edits hit live
|
|
190
|
+
(`--allow-shared-schema-changes`). `tenant deploy` REPLACES: DROPS tables it lacks,
|
|
191
|
+
CONVERTS retypes (`--seed`: rows).
|
|
192
|
+
`promote` refuses a taken name/pinned slug (`SDK_IDENTITY_CONFLICT`), then
|
|
193
|
+
EXITS NON-ZERO if a declared object did not arrive or a pinned slug is not served;
|
|
194
|
+
`--set-live` waits on that (previous branch serving). Extra objects: reported;
|
|
195
|
+
a check missing an input reports `unverified`, exit 0. `--json` on every release write:
|
|
196
|
+
`completed` yes|no|unknown; UNKNOWN exits 9 — run `resolveWith`, never retry blind.
|
|
197
|
+
`--expect-live <label>` (with `--set-live`) refuses if live moved; not atomic. Inspect a staged branch with
|
|
198
|
+
`workspace export --branch <label>` or `workspace diff <entry> --branch <label>`.
|
|
199
|
+
⚠ The redis family, `s.lambda`, `s.db.direct_query`, `s.await`, `s.microservice.request`,
|
|
200
|
+
`s.zip.*`, the raw-file readers, Datadog and Webflow need an INSTANCE CAPABILITY: they
|
|
201
|
+
deploy clean and fail on the first request if the target lacks it. `export`/`deploy` list
|
|
202
|
+
what the bundle needs — compare it to the target yourself.
|
|
203
|
+
- Backend SECRETS stay out of the repo: declare the NAME, value empty, in `workspaceConfig({ env })`
|
|
204
|
+
passed to `registerWorkspace()`; the VALUE lives in `xano/.env` — gitignored, kept across a
|
|
205
|
+
pull, read by every build. `--backend-env-file <path>` replaces that default (CI has
|
|
206
|
+
no `xano/.env`), `--env-var KEY=VALUE` beats both, and `xanosdk env pull` fills the file from
|
|
207
|
+
a running backend — the only command that writes `xano/.env`.
|
|
208
|
+
⚠ A deploy REPLACES the env set: a declared name with NO value refuses the deploy rather
|
|
209
|
+
than clearing the live one — `--allow-empty-env=NAME` clears it on purpose.
|
|
210
|
+
ONE live value, no deploy: `xanosdk env set NAME` (value on stdin) / `env unset NAME`.
|
|
211
|
+
(`--static-env` is the other one — PUBLIC frontend config.)
|
|
212
|
+
- A DOCUMENTATION token is a secret too, in a SECOND ignored file: source declares only the
|
|
213
|
+
gate — `documentation: { require_token: true }` on `workspaceConfig` or an `apiGroup` — and
|
|
214
|
+
the value lives in `xano/.secrets.json`, keyed by the object it gates. A literal `token`
|
|
215
|
+
FAILS the export. `xanosdk pull` writes that file and every build reads it back;
|
|
216
|
+
`xanosdk secrets fill` MINTS one per gate that has none — a gate you AUTHORED has no value
|
|
217
|
+
to pull. CI, which does not have the file, passes `--secrets-file <path>` or
|
|
218
|
+
`--doc-token "<scope>=<value>"`
|
|
219
|
+
(scope = `workspace`, or the group's NAME).
|
|
220
|
+
⚠ OMISSION IS OPPOSITE PER SCOPE: off `workspaceConfig` the target's gate is left alone;
|
|
221
|
+
off an `apiGroup` it is CLEARED (an absent key there is written as the engine default). An
|
|
222
|
+
unsupplied gate refuses a `deploy` and fails the export for a publishing group.
|
|
223
|
+
- `xanosdk deploy --to workspace|tenant:<name>` skips the release flow to merge a LOCAL
|
|
224
|
+
build in, keeping the checks that need one (`--dry-run`, `--prune`, loss warnings) and
|
|
225
|
+
leaving nothing to roll back to. Prefer the release path.
|
|
226
|
+
⚠ Branches scope LOGIC: tables and microservices are shared by EVERY branch, so a
|
|
227
|
+
schema change reaches production on any branch — refused without
|
|
228
|
+
`--allow-shared-schema-changes` — and dropping a column destroys its data.
|
|
229
|
+
⚠ `--replace` CLEARS BY WORKSPACE, NOT BY BRANCH: every non-live branch goes, with the
|
|
230
|
+
saved versions that would restore one — unrecoverable. Refused without
|
|
231
|
+
`--allow-branch-deletion`; `--yes` does not imply it. The default merge leaves them alone.
|
|
232
|
+
- `xanosdk workspace reset-tables <entry> --table <guid>` puts NAMED tables back to their
|
|
233
|
+
compiled `table({ seed })` rows, touching no other table. Dry run unless `--write`.
|
|
234
|
+
⚠ ROWS BEFORE LOGIC: rows are workspace-shared like schema, so they reach production when
|
|
235
|
+
applied even while the logic reading them is staged — no ordering makes a half-data change
|
|
236
|
+
atomic. Reset rows FIRST, then `promote`: new rows under old logic break only a feature
|
|
237
|
+
being removed; new logic over old rows breaks the one you are shipping.
|
|
238
|
+
- `xanosdk pull` refreshes THIS project from a backend: rewrites decoded files,
|
|
239
|
+
keeps yours, lists and confirms (`--yes`).
|
|
240
|
+
- `xanosdk generate <source>`: the decoded tree ONLY, no project.
|
|
241
|
+
- `xanosdk status` names the env this project last deployed to, its URL and its expiry.
|
|
242
|
+
|
|
243
|
+
**Frontend wiring.** `--static <dir>` (or `xanosdk publish <dir>`) injects the env's backend URL as
|
|
244
|
+
`window.XANO_HOST` into EVERY html document in the build, before the app bundle runs,
|
|
245
|
+
so the frontend needs no rebuild to target an env. How to read it, local dev, verifying
|
|
246
|
+
the inject, and displaying a stored file: `llms/client.md`.
|
|
247
|
+
⚠ A static host serves its files verbatim, so everything injected is PUBLIC — base
|
|
248
|
+
URLs and publishable keys only, never secrets. Secrets go in backend env, read
|
|
249
|
+
server-side via `env(name)`.
|
|
250
|
+
⚠ `xanosdk preflight` uses the deploy credential unless `XANO_VALIDATE_INSTANCE` /
|
|
251
|
+
`XANO_VALIDATE_TOKEN` (+ optional `XANO_VALIDATE_WORKSPACE_ID`) are set.
|
|
252
|
+
|
|
253
|
+
**Full CLI surface:** `xanosdk <command> --help` — every command, flag, default and accepted
|
|
254
|
+
backend; `manifest.json` carries the same in its `cli` array.
|
|
255
|
+
**Style:** reach statements through the `s` namespace (`s.db.add`, `s.math.add`, …).
|
|
256
|
+
The flat aliases (`dbAdd`, `setVar`, …) are identical in output; prefer `s.*`.
|
|
257
|
+
|
|
258
|
+
## Gotchas
|
|
259
|
+
|
|
260
|
+
Non-obvious authoring rules:
|
|
261
|
+
|
|
262
|
+
### Values and references
|
|
263
|
+
|
|
264
|
+
- **No callback builder.** Flat def-objects + `register*`, not
|
|
265
|
+
`workspace(w => w.table(...))`. `workspace(name)` returns a named `new Xano()`;
|
|
266
|
+
tables are `table({ schema: { col: f.text() } })`.
|
|
267
|
+
- **Reference-helper picker:** `ref` = stack var (`as:` output), `inp` = input,
|
|
268
|
+
`col` = table column (in `db.query` `where`), `auth("id")` = the caller,
|
|
269
|
+
`c.*` = a constant. Pick by what you're pointing at.
|
|
270
|
+
- **Foreign key is `f.tableRef(table)`, not `ref`.** `ref(name)` references a
|
|
271
|
+
stack variable (a value); `f.tableRef` is the column constructor.
|
|
272
|
+
- **Tagged values are DATA — a JS template literal cannot compose them.** `${ref(...)}`
|
|
273
|
+
(or `"a" + ref(...)`) stringifies the tag object at BUILD time: ``c.text(`Hi ${ref("u.name")}`)``
|
|
274
|
+
type-checks and encodes the literal text `Hi [object Object]`, served verbatim.
|
|
275
|
+
`export()` warns; `--strict` fails. Compose at RUNTIME:
|
|
276
|
+
`withFilters(c.text("Hi "), fl.concat(ref("u.name")))`, an `obj({...})`/record member,
|
|
277
|
+
or `c.expression('"Hi, " ~ $var.u.name')`.
|
|
278
|
+
- **Declare inputs with `input.<type>()`, read them with `inp("name")`.** `inp()`
|
|
279
|
+
resolves ONLY against this def's own `input` block — a value produced earlier in
|
|
280
|
+
the stack is `ref("var.field")`, not `inp("field")`. An undeclared name fails at
|
|
281
|
+
runtime with `Unable to locate input` (`export()` warns).
|
|
282
|
+
- **`get_input`/`get_raw_input` read the whole payload**, not one named input
|
|
283
|
+
(args are `{ as?, encoding?, excludeMiddleware? }` — no `name`). For a single
|
|
284
|
+
input use `inp("name")`.
|
|
285
|
+
- **To match every row, omit `where` — never pass a constant.** `where: c.bool(true)` is
|
|
286
|
+
not "no filter": a bare scalar literal states no condition and the engine reads the
|
|
287
|
+
resulting `context.search` as garbage, so it throws. An absent `where` is how "every
|
|
288
|
+
row" is spelled. A raw `Value` `where` stays the escape hatch for a clause built
|
|
289
|
+
elsewhere — `inp("clause")`, `ref("built_where")`, or `c.text("id > 0")`.
|
|
290
|
+
|
|
291
|
+
### Tables and columns
|
|
292
|
+
|
|
293
|
+
- **System columns are auto-injected.** `id` + `created_at` are prepended to
|
|
294
|
+
every table (`system: true` by default); declaring them by hand is redundant.
|
|
295
|
+
`id` is an `int` PK by default; pass `idType: "uuid"` on the table for a uuid key.
|
|
296
|
+
Both are valid targets wherever a column name is accepted — `db.query` `sort`/
|
|
297
|
+
`output`, a `db.get`/`edit`/`del` `fieldName`, etc. (the column-name type is
|
|
298
|
+
`keyof schema | "id" | "created_at"`), and both appear in `InferRow<typeof table>`.
|
|
299
|
+
- **A column named `run` is reserved.** The table deploys and reads back fine, then
|
|
300
|
+
EVERY `s.db.add` into it 400s — at any column type, with or without a value — and
|
|
301
|
+
the error names the column while complaining about the VALUE. Use `run_id`. Exact,
|
|
302
|
+
case-sensitive, one name: `Run`/`runs`/`run_id` are fine. `--strict` fails on it.
|
|
303
|
+
- **An input named `run` is reserved when a call passes it**: the callee gets
|
|
304
|
+
defaults or a 400. HTTP binds it fine. `--strict` fails on it.
|
|
305
|
+
- **Numeric columns lose money quietly.** A normalized table's `f.decimal` reads
|
|
306
|
+
`0.12345678` back as `0.12346`; `f.int` saturates at `9223372036854775807`
|
|
307
|
+
instead of failing. Both at HTTP 200 and plausible on read, so it surfaces as
|
|
308
|
+
arithmetic that stops reconciling. Hold money as a count of the smallest unit;
|
|
309
|
+
exact bounds: `llms/fields.md`.
|
|
310
|
+
- **Self-referencing tables** need the bare-name form: inside `tweets`'s own
|
|
311
|
+
schema, write `f.tableRef("tweets", { type: "int" })` — the `const tweets`
|
|
312
|
+
handle isn't assigned yet, so the handle form throws "used before declaration".
|
|
313
|
+
- **Seed a table's starting rows with `table({ seed })`.** Rows typed against the
|
|
314
|
+
schema, inline (`seed: [{ name: "…" }]`) or from a file (`seedFile("./seed.json",
|
|
315
|
+
import.meta.url)`). Only `deploy` ships them, and a re-deploy re-seeds cleanly. Never
|
|
316
|
+
put secrets in `seed`. Row shape, `id` pinning, and the file rules: `llms/fields.md`.
|
|
317
|
+
- **`f.password()` defaults to `access: "internal"`, so `db.get` does NOT return it.**
|
|
318
|
+
A login stack that reads `ref("u.password")` after a plain `db.get` fails at runtime
|
|
319
|
+
with `Unable to locate var: u.password` — the column is simply absent from the row.
|
|
320
|
+
Name it in the read's `output` to pull it: `s.db.get({ table: users, fieldName: "email",
|
|
321
|
+
fieldValue: inp("email"), output: ["id", "email", "password"], as: "u" })`, then
|
|
322
|
+
`s.security.check_password`. `output` OVERRIDES column visibility — it is the only way to
|
|
323
|
+
read an `internal` column, and `export()` warns when a stack reads one a `db.get` did not
|
|
324
|
+
return.
|
|
325
|
+
- **Don't take a password through `input.password` on login — it double-hashes.**
|
|
326
|
+
An `f.password()` column hashes on write, and `input.password` *also* hashes the
|
|
327
|
+
submission on bind, so `s.security.check_password` compares two different hashes
|
|
328
|
+
and a correct password always fails (`ok:false` on a found row). Take the submitted
|
|
329
|
+
password as `input.text()` on both signup and login and pass the plaintext straight
|
|
330
|
+
to `check_password` (which does the comparison hash itself). `export()` warns.
|
|
331
|
+
|
|
332
|
+
### Reading and writing rows
|
|
333
|
+
|
|
334
|
+
- **DB reads are field-match, not `where`-expr.** `db.get`/`db.edit`/`db.del`/
|
|
335
|
+
`db.has`/`db.patch` match one field: `{ fieldName, fieldValue }` (`fieldName`
|
|
336
|
+
defaults to the PK `id`). Only `db.query` takes a `where`/`additionalWhere`
|
|
337
|
+
`expr(...)`; writes (`db.add`/`db.edit`/`db.add_or_edit`) take a `row`/`data`.
|
|
338
|
+
Signatures: `llms/statements-data.md`.
|
|
339
|
+
- **Single-field only — no composite match.** These ops match exactly ONE field;
|
|
340
|
+
there is no two-field form (the engine's by-field lookup takes a single
|
|
341
|
+
predicate). For a `(a, b)` existence/fetch — e.g. dedupe a `(habit, date)`
|
|
342
|
+
check-in — use `db.query({ where: [expr(col("habit"), "=", ...), expr(col("date"), "=", ...)], as })`
|
|
343
|
+
(a `where` array is ANDed) and branch on the result, rather than pushing the
|
|
344
|
+
check to the client.
|
|
345
|
+
- **Drilling into a maybe-null `db.get` result 500s — use `ref(path, { safe: true })`.**
|
|
346
|
+
`db.get` binds `null` on a no-match, but a nested `ref("owner.user_id")` raises a runtime
|
|
347
|
+
"Unable to locate var" (HTTP 500) when `owner` is null — an ownership guard throws instead
|
|
348
|
+
of failing cleanly. Two correct shapes:
|
|
349
|
+
- Guard existence first — `s.precondition({ expr: expr(ref("owner"), "!=", c.null()),
|
|
350
|
+
error_type: "notfound", error: c.text("Not found.") })` — then drill WITHOUT `safe`.
|
|
351
|
+
- Or drill null-safe inside an EXPRESSION or `obj()` operand:
|
|
352
|
+
`expr(ref("owner.user_id", { safe: true }), "=", auth("id"))` yields `null` (the guard
|
|
353
|
+
reads `false`) instead of 500ing. Works inside `obj({...})` too — no `s.set_var` hoist needed.
|
|
354
|
+
⚠ EXPRESSION and `obj()` operands ONLY — never a `db.*` match argument. `null` is not a
|
|
355
|
+
legal `fieldValue`/`id`, so a chained get (fetch the child row, then its parent to check
|
|
356
|
+
the owner) fails with HTTP 400 `Missing param: field_value` one statement BEFORE the guard.
|
|
357
|
+
There guarding existence first is MANDATORY. `export()` warns on a safe ref in that position.
|
|
358
|
+
⚠ That is for a base that EXISTS and may be null. If the base binds nothing at all (a typo),
|
|
359
|
+
`{ safe: true }` buries the bug as a silent `null` — `export()` warns with a did-you-mean;
|
|
360
|
+
fix the name instead.
|
|
361
|
+
- **`output` selects from what the statement BINDS, and a PAGED `db.query` binds the
|
|
362
|
+
envelope.** With `metadata: true` (the DEFAULT), the roots are `items.<column>` plus the
|
|
363
|
+
counters you want kept: `output: ["itemsReceived", "curPage", "items.id", "items.title"]`.
|
|
364
|
+
A bare column list there matches no envelope key, so ALL of them are dropped and the
|
|
365
|
+
endpoint answers `[]` at HTTP 200 with no error — and a `response` reading
|
|
366
|
+
`ref("rows.items")` then dies with `Unable to locate var: rows.items`. Prefix the columns,
|
|
367
|
+
or set `metadata: false` inside `paging` to select the rows directly.
|
|
368
|
+
`export()` warns; `--strict` fails.
|
|
369
|
+
|
|
370
|
+
### Stacks and calls
|
|
371
|
+
|
|
372
|
+
- **Block specials nest a `body`, not a `stack`.** `s.for`/`s.foreach`/
|
|
373
|
+
`s.while`/`s.switch`/`s.try_catch`/`s.db.transaction`/`s.expect.to_throw`
|
|
374
|
+
take their sub-stack as `body` (`try`/`catch`/`finally` for `try_catch`);
|
|
375
|
+
`s.group(body)` and `s.util.post_process(body)` take it **positionally**.
|
|
376
|
+
`s.for` is **count-bounded** (`{ as, count: <Value>, body }`), not from/to. See the
|
|
377
|
+
authored signatures in `llms/statements-data.md`.
|
|
378
|
+
- **`s.api.call` / `s.task.call` / `s.trigger.call` / `s.workflow_test.call` are
|
|
379
|
+
WORKFLOW-TEST ONLY.** Outside a `workflowTest({...})` stack the engine cannot reach
|
|
380
|
+
the target, so one in a query/function/task deploys clean and then answers the first
|
|
381
|
+
real request with `ERROR_FATAL: <Type> does not exist: <type>:<n>` — and not per
|
|
382
|
+
host kind, the same call fails identically from a function a query runs.
|
|
383
|
+
`export()` refuses them. `s.function.call`, `s.tool.call`, `s.middleware.call` and
|
|
384
|
+
`s.addon.call` run anywhere, as does `s.function.run` (the ordinary function
|
|
385
|
+
invocation). To share logic between endpoints, put it in a `defineFunction` and
|
|
386
|
+
`s.function.run` it from both.
|
|
387
|
+
- **`expect.*` is a unit-test assertion; `s.expect.*` is a workflow-test statement.**
|
|
388
|
+
A statement's `mock` is keyed by TEST NAME; an undeclared name throws. See `llms/tests.md`.
|
|
389
|
+
- **A helper returning `Statement[]` widens the stack and kills `InferResponse`.**
|
|
390
|
+
Spreading `...requireX()` where the helper is typed `Statement[]` drops the stack's TUPLE
|
|
391
|
+
type, so EVERY `ref()`/`as` in that stack — even ones after the spread — resolves to
|
|
392
|
+
`unknown` and the response types as `StackTupleWidened`. Nothing fails at the helper; it
|
|
393
|
+
surfaces where the response is consumed, often a frontend typecheck. Fix: return
|
|
394
|
+
`statements(s.a(...), s.b(...))` — its tuple survives the spread. A helper that builds
|
|
395
|
+
its array in a LOOP cannot be a tuple; declare `responseShape` on the query there.
|
|
396
|
+
- **Build regex-filter patterns with `c.regex(body, flags?)`, never `c.text`.**
|
|
397
|
+
The regex filters (`regex_test`/`regex_match`/`regex_replace`/…) are PHP `preg_*`: the
|
|
398
|
+
piped value is the PATTERN and must be delimiter-wrapped, and the ARGUMENT is the subject.
|
|
399
|
+
Correct: `withFilters(c.regex(/^[a-z-]+$/i), fl.regex_test(inp("slug")))`.
|
|
400
|
+
- A bare `c.text("^…$")` is an invalid pattern that matches *nothing* for every input,
|
|
401
|
+
so a precondition on it silently rejects all values. `c.regex(body, "i")` wraps +
|
|
402
|
+
escapes it for you (a JS `RegExp` too: `c.regex(/^…$/i)`).
|
|
403
|
+
- Reversed — subject piped, pattern in the argument — reads correctly, type-checks, and
|
|
404
|
+
answers false for EVERY input, so an `if (matches) reject` guard admits what it refuses.
|
|
405
|
+
- Build time refuses both: `withFilters` throws on a bare `c.text` pattern from ANY position
|
|
406
|
+
in the chain (a normalizer in front of the regex filter is refused too; nothing upstream
|
|
407
|
+
adds the delimiters) and on a pattern found in the subject slot; `s.expect.to_match` is the
|
|
408
|
+
same PATTERN slot, refused both ways; a `ref`/`inp` pattern is passed through untouched.
|
|
409
|
+
`export()` warns on a reversed pair in stored bytes.
|
|
410
|
+
- Better still: a native typed input (`input.email`) over hand-rolled validation.
|
|
411
|
+
- **An `auth()`-keyed limiter FAILS the request on any host with no caller identity —**
|
|
412
|
+
**at every tier.** `s.redis.ratelimit({ key: withFilters(c.text("rl:"), fl.concat(auth("id"))), error: c.text("…") })`
|
|
413
|
+
is the per-user form, and a public query, a task, or a function has no caller identity.
|
|
414
|
+
It does not degrade to one shared bucket: on a public endpoint the FIRST call 403s,
|
|
415
|
+
under `max`, and the host never runs. Key off `sys.remoteIp()` there. Attaching it
|
|
416
|
+
at `apiGroup({ middleware })` or the workspace tier inherits the same failure onto
|
|
417
|
+
every member endpoint. Export warns, naming the tier.
|
|
418
|
+
|
|
419
|
+
### Identity, build, and deploy
|
|
420
|
+
|
|
421
|
+
- **Same-name siblings collide — queries excepted: their identity carries group + verb.**
|
|
422
|
+
Guids derive from `(type, name)`, so two functions (or tables, toolsets, …) sharing
|
|
423
|
+
a name derive ONE guid and `export()` throws — give them DISTINCT names. A QUERY
|
|
424
|
+
derives from `(api group, verb, name)`, the engine's own uniqueness: `GET items` +
|
|
425
|
+
`POST items`, and `items` across two groups, coexist, and the lock keys the same
|
|
426
|
+
composed identity (`query:blog|GET|items`) — changing a query's verb or group
|
|
427
|
+
changes its identity. An explicit `guid` on a colliding pair clears the throw but
|
|
428
|
+
is NOT a lasting fix: a lock entry cannot hold two guids, so `export --lock`
|
|
429
|
+
refuses the pair (`export()` warns even unlocked); it is for pinning identity
|
|
430
|
+
across a rename, not for sharing a name.
|
|
431
|
+
- **`export()` vs `emitBundle()` vs `writeBundle()`:** `export()` returns the object,
|
|
432
|
+
`emitBundle()` the JSON, `writeBundle(app, path)` writes `xanosdk export`'s file. All
|
|
433
|
+
three run the SAME build-time checks, INCLUDING seed validation of a literal `seed: [...]`
|
|
434
|
+
array (row shape, unknown column, coercion, enum membership, the all-or-nothing `id`
|
|
435
|
+
rule). A DEFERRED seed — a thunk (`() => import("./seed.json")`) or `seedFile()` — is
|
|
436
|
+
checked only by the `xanosdk export`/`deploy` CLI path. Only `deploy` ships seed rows.
|
|
437
|
+
- Entry points: the `node:fs` writers (`writeBundle`/`writeArtifact`) and lock-file I/O
|
|
438
|
+
import from `@xano/sdk/node`, NOT the browser-safe `@xano/sdk` entry (a frontend
|
|
439
|
+
imports defs from it for `getPath()`/`InferInput`). `@xano/sdk/internal` is the
|
|
440
|
+
compiler machinery, never needed to author. READING a bundle back is `@xano/sdk/bundle`
|
|
441
|
+
— a statement walker (`2.if.0` paths), a structural hash, `mvp:*` catalog, `tableRefOf`.
|
|
442
|
+
- **Intra-workspace imports use `.js` specifiers** (`../tables/links.js`), not
|
|
443
|
+
extensionless — the defs compile under `moduleResolution: bundler`. Add the `.js`.
|
|
444
|
+
- **Agents authenticate with env vars — never `xanosdk login`.** `login` blocks on a
|
|
445
|
+
browser consent no agent can complete. Set all three of `$XANO_INSTANCE_URL`,
|
|
446
|
+
`$XANO_WORKSPACE_ID`, `$XANO_META_TOKEN` and run `deploy`/`promote` directly: no disk, no
|
|
447
|
+
rotation, so it survives repeated runs.
|
|
448
|
+
- The triple outranks every other credential (`--config`, `$XANO_PROFILE` and
|
|
449
|
+
`$XANO_REFRESH_TOKEN` included, each named on stderr when displaced). Setting SOME of the
|
|
450
|
+
three is a hard error, so a misspelled secret cannot fall through to whatever credential
|
|
451
|
+
the runner has. An explicit `--profile` against it is a hard error too, not a displacement.
|
|
452
|
+
- On disk: `xanosdk profile add <name> --instance … --workspace-id <n>` reads the token
|
|
453
|
+
from piped stdin, else PROMPTS (a flag would reach the process list); the instance must accept it.
|
|
454
|
+
`auth.json` holds a map of named profiles, selected by `--profile <name>`. The older
|
|
455
|
+
`$XANO_REFRESH_TOKEN` + `$XANO_CLIENT_ID` pair still works but ROTATES: single-use.
|
|
456
|
+
- **Event-driven objects fire on an EPHEMERAL.** A `task` (scheduled), an `mcpServer`,
|
|
457
|
+
and every trigger — `tableTrigger` included — run normally on an ephemeral env, which
|
|
458
|
+
is where `deploy` sends them. So test an event-driven design (screen-on-insert, cron
|
|
459
|
+
cleanup, MCP tool call) by deploying it and letting it run.
|
|
460
|
+
|
|
461
|
+
## Statements
|
|
462
|
+
|
|
463
|
+
Reachable through the `s` namespace: `s.<path>({...})`. Declarative statements
|
|
464
|
+
take one typed args object (field names match the engine); every surface's field
|
|
465
|
+
signature is in `llms/statements-catalog.md`. Specials (`[special]`) are
|
|
466
|
+
hand-authored: control flow is below, and the rest are in the `llms/statements-*`
|
|
467
|
+
topic files this doc's navigation names.
|
|
468
|
+
Wrap an `input`-routed `value` field in `ignored(...)` to store it but SKIP it at
|
|
469
|
+
runtime — the engine records `<name>:ignore` and the parameter falls back to its
|
|
470
|
+
default. Not the same as an empty value, and not the same as omitting the field
|
|
471
|
+
(which stores no entry at all). Mostly seen on a pulled workspace. A field written
|
|
472
|
+
into `context` instead (the `s.math.*`/`s.text.append` mutation family) has no slot
|
|
473
|
+
for the flag and THROWS rather than dropping it.
|
|
474
|
+
Fields marked `value` take a `Value` (`c.*`/`ref`/`inp`); `comparison` takes an
|
|
475
|
+
`expr(...)`. A `→ as: <type>` suffix names what the statement's `as:` output var
|
|
476
|
+
holds (curated, not exhaustive — absence means read the `[output]` flag and prose).
|
|
477
|
+
|
|
478
|
+
### Specials — authored signatures
|
|
479
|
+
|
|
480
|
+
Control flow & blocks (each nests a sub-stack; block specials name it `body`):
|
|
481
|
+
|
|
482
|
+
- `s.set_var(name, value)` · `s.update_var(name, value)` · `s.return(value)` · `s.comment(text)` — positional.
|
|
483
|
+
- **Every** statement takes `disabled?`/`description?` — annotations on the stack item, not args: `disabled: true` is Xano's "disable step" (kept in the stack, skipped at runtime), `description` the note beside it. Inline on object-arg factories; a trailing object on the positional ones (`s.set_var("x", v, { disabled: true })`).
|
|
484
|
+
- **Statements with an `as`** also take `asFilters?` — `fl.*` filters on the RESULT as it binds, in order, same slot as `disabled`: `s.set_var("x", v, { asFilters: [fl.trim(), fl.lower()] })`. Saves a follow-up `set_var`. Throws without an `as`. The bound variable is RETYPED by the chain (`db.query` + `[fl.count()]` → `number`); filters whose result the engine declares as `any` (`get`, `set`, `json_decode`, …) fold to `unknown`.
|
|
485
|
+
- `s.conditional({ when, then, elif?, else? })` — if/elif/else. `when` is a condition (`expr`/`cmp`/`and`/`or`); `elif` is an ordered `[{ when, then }]` (each an else-if branch); `then`/`else` are `Statement[]`.
|
|
486
|
+
- `s.for({ as, count, body })` — **count-bounded** loop (`as` is the index), NOT from/to; `count` is a `Value`, not a bare number.
|
|
487
|
+
- `s.foreach({ as, list, body })` — iterate `list`; `as` is the current item.
|
|
488
|
+
- `s.while({ when, body })` — `when` is a condition (`expr`/`cmp`/`and`/`or`).
|
|
489
|
+
- `s.switch({ on, cases: [{ when, body, break? }], default? })` — multi-way branch on a subject `Value` `on`; each `case`'s `when` is a literal `Value` matched against `on` (NOT a comparison — use `s.conditional` for `<`/`>`/ranges). ⚠ **Omitting `break: true` FALLS THROUGH** — the matched case also runs every LATER case body. Type-checks clean; only `export --strict` catches it.
|
|
490
|
+
- `s.try_catch({ try, catch?, finally? })` — three `Statement[]` blocks.
|
|
491
|
+
- `s.group(body)` / `s.util.post_process(body)` — take a `Statement[]` **positionally**.
|
|
492
|
+
- `s.foreach_break()` / `s.foreach_continue()` / `s.foreach_remove()` — nullary loop control.
|
|
493
|
+
- `s.expect.to_throw({ body, exception? })` — `body` is the statements expected to raise; `exception` is a `Value`, not a bare string.
|