@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
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# The scaffolded project
|
|
2
|
+
|
|
3
|
+
What `xanosdk init` writes, the two frontend presets, theming, add-ons, and the SvelteKit prerendering rules.
|
|
4
|
+
|
|
5
|
+
## init flags and add-ons
|
|
6
|
+
|
|
7
|
+
`init` flags: `--framework <react|svelte>` (default: `react`), `--name <name>`
|
|
8
|
+
(default: the folder name), `--theme <id>` / `--radius <len>` / `--dark <mode>` /
|
|
9
|
+
`--font <id>` / `--font-mono <id>` / `--font-heading <id>` / `--icons <id>`
|
|
10
|
+
(the look — see [Theming](#theming)), `--no-agents-md` (skip the `AGENTS.md`
|
|
11
|
+
agent brief, which every coding agent reads and `init` writes by default),
|
|
12
|
+
`--marketplace <pkg>` (repeatable, comma-separated; installs add-ons and
|
|
13
|
+
registers them — below), `--force` (scaffold into a non-empty folder; over an
|
|
14
|
+
existing project it lists the files it would overwrite and asks, `--yes` answering
|
|
15
|
+
without a terminal — files it does not write are kept),
|
|
16
|
+
`--no-install` (skip `npm install`), `--web` (choose all of the above in a
|
|
17
|
+
browser instead — see [Choosing in a browser](#choosing-in-a-browser)),
|
|
18
|
+
`--from <source>` (fill `xano/` from an existing backend — any
|
|
19
|
+
[backend spelling](cli.md#naming-a-backend) or a bundle path — rather than the
|
|
20
|
+
starter — see [Pulling an existing workspace](codegen.md); over an existing `xano/` it lists
|
|
21
|
+
what it replaces and asks, and `--yes` answers without a terminal).
|
|
22
|
+
In a terminal, `init` prompts for the framework; the prompt has a default, so
|
|
23
|
+
pressing enter is a valid answer. The look is never
|
|
24
|
+
prompted for — it comes from the flags above, or from `--web`.
|
|
25
|
+
The starter backend is empty but already compiles and deploys — grow it from the
|
|
26
|
+
walkthrough in `xano/EXAMPLE.md`.
|
|
27
|
+
|
|
28
|
+
A scaffold ships `@xano/sdk` and nothing else from the `@xano-sdk` scope. Add-ons
|
|
29
|
+
install on demand:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
xanosdk marketplace list # every published add-on
|
|
33
|
+
xanosdk marketplace search auth # …or narrow by keyword
|
|
34
|
+
xanosdk marketplace details @xano-sdk/auth # what it installs + how to register it
|
|
35
|
+
xanosdk marketplace install @xano-sdk/auth # add it to the project you're in
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The three read verbs hit a public catalogue, so they work before you log in.
|
|
39
|
+
Every add-on is optional and none is assumed by anything in the scaffold —
|
|
40
|
+
install one when you need it. [The marketplace](marketplace.md) covers all six
|
|
41
|
+
verbs, what each one does to a project, and how an add-on is configured.
|
|
42
|
+
|
|
43
|
+
`init` takes the same package names, so a project can be scaffolded with its
|
|
44
|
+
add-ons already wired:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
xanosdk init my-app --framework react --marketplace @xano-sdk/auth,@xano-sdk/vector
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That installs each package **and** writes its registration into `xano/index.ts`.
|
|
51
|
+
Installing alone would leave dependencies nothing imports — a different project
|
|
52
|
+
wearing the same name. A module declares how it registers in its own
|
|
53
|
+
`package.json` (`"xanosdk": { "register": "registerAuth" }`); one that has not
|
|
54
|
+
adopted the field is read for a single `register*` export, and two of those
|
|
55
|
+
without the field is a refusal rather than a guess.
|
|
56
|
+
|
|
57
|
+
The field is also where a module declares what its register call needs
|
|
58
|
+
(`"options"`). An add-on whose call takes an option nothing has declared — a URL
|
|
59
|
+
into your own frontend, your own role model — is installed but left
|
|
60
|
+
**unregistered**, named in `xano/index.ts` with the call to uncomment once you
|
|
61
|
+
have the value. A scaffold that compiles with an add-on switched off beats one
|
|
62
|
+
that does not compile at all.
|
|
63
|
+
|
|
64
|
+
Each `--marketplace` name is resolved through the marketplace first, exactly as
|
|
65
|
+
`xanosdk marketplace install` resolves it: a module's package or its slug
|
|
66
|
+
(`--marketplace auth` installs `@xano-sdk/auth`, never npm's unrelated `auth`). A
|
|
67
|
+
name the marketplace does not list stops `init` with **exit 8** before it writes
|
|
68
|
+
anything. So does a package name the npm registry does not have — the same rule
|
|
69
|
+
`--framework` and `--theme` follow, so a typo costs nothing and leaves nothing to
|
|
70
|
+
clean up. The check asks about the NAME, from the
|
|
71
|
+
directory the install will run in, so it reads the same `.npmrc` the install
|
|
72
|
+
does. (A package published minutes ago can 404 until the registry replicates
|
|
73
|
+
it; if the name is right, retry.) A version or tag that does not resolve is not
|
|
74
|
+
its business — that falls through to the install, whose error says which.
|
|
75
|
+
A local path (`./my-module`, `file:../my-module`) is read from where you typed it.
|
|
76
|
+
Under `--no-install` only a directory holding a `package.json` can be recorded (as
|
|
77
|
+
`file:`); a tarball, URL or git spec is refused, since its name is unknown until installed.
|
|
78
|
+
|
|
79
|
+
An install that fails for any other reason — an unreachable registry, a proxy —
|
|
80
|
+
is not fatal: the scaffold is written without that add-on, npm's own diagnosis
|
|
81
|
+
is printed, and the run ends on `Project ready, without 1 add-on.` with **exit
|
|
82
|
+
code 2**. The project is usable; add the missing package with `xanosdk
|
|
83
|
+
marketplace install <package>`. `--from` and `--marketplace` do not compose, and
|
|
84
|
+
saying both is a usage error rather than a silently add-on-less project.
|
|
85
|
+
|
|
86
|
+
When the project's OWN `npm install` fails (with or without add-ons), `init` also
|
|
87
|
+
exits **2**: every file is written and correct, but nothing in the project can be
|
|
88
|
+
loaded until its dependencies are, so the run does not end on `Project ready.`.
|
|
89
|
+
Fix what npm reported and run `npm install` in the project — nothing needs to be
|
|
90
|
+
scaffolded again.
|
|
91
|
+
|
|
92
|
+
Declaring `"options"` is all or nothing: it states what the whole argument is,
|
|
93
|
+
and is taken at its word without the function being read. A module that needs an
|
|
94
|
+
option only you can answer omits the key rather than declaring the rest, which is
|
|
95
|
+
what lets the CLI find the argument and leave the module out.
|
|
96
|
+
|
|
97
|
+
`@xano-sdk/auth` is **authentication, not authorization** — user/login/signup
|
|
98
|
+
tables and the endpoints over them. It ships no roles, permissions, or route
|
|
99
|
+
guards, so it is not the RBAC answer. Its `user` table has a `role` column, but
|
|
100
|
+
the tokens it mints carry no role claim, so a check on the token finds nothing.
|
|
101
|
+
Check the caller's row instead: spread `...guard.role(userTable, "admin")` into
|
|
102
|
+
the stack of each query whose `auth` is `userTable`. It loads the caller's row,
|
|
103
|
+
rejects a missing one (401), then checks the role (403).
|
|
104
|
+
|
|
105
|
+
`details` is the one to reach for when wiring an add-on: it prints the objects
|
|
106
|
+
the add-on puts on your workspace, what you have to supply, and the
|
|
107
|
+
`xano/index.ts` registration to copy. Piped, it emits JSON; `--prompt` emits
|
|
108
|
+
instructions written to be handed straight to a coding agent.
|
|
109
|
+
|
|
110
|
+
That is `npm install` with two additions: add-ons are discoverable from `xanosdk
|
|
111
|
+
--help`, and the command refuses before npm runs when you are not standing in a
|
|
112
|
+
project — the mistake npm answers by silently writing to the wrong `package.json`.
|
|
113
|
+
The package name is passed through exactly as typed, so version specifiers, tags,
|
|
114
|
+
and third-party packages all work.
|
|
115
|
+
|
|
116
|
+
## Git and CI
|
|
117
|
+
|
|
118
|
+
`init` writes a `.gitattributes` and a `.github/workflows/check.yml` (`xanosdk-<dir>.yml`
|
|
119
|
+
below the repository root, so moving it up never replaces another workflow).
|
|
120
|
+
|
|
121
|
+
The attributes file normalizes line endings, so any generated artifact the project
|
|
122
|
+
commits is byte-identical on every checkout and a `--frozen-lock` check cannot fail
|
|
123
|
+
over a contributor's `core.autocrlf` rather than over a real change.
|
|
124
|
+
|
|
125
|
+
The workflow installs with the project's package manager (npm, pnpm, yarn or bun, as
|
|
126
|
+
`init` detected it), frozen to the committed lockfile, then runs `xano:check` on every push
|
|
127
|
+
and pull request. In a monorepo workspace member it installs at the workspace root and checks
|
|
128
|
+
in the member's directory; move the file to the repository root's `.github/workflows/`, the
|
|
129
|
+
only place GitHub reads it. That script
|
|
130
|
+
runs `xanosdk export ./xano/index.ts --check --strict`: every check an export runs (a build warning fails it), under
|
|
131
|
+
`--frozen-lock`, writing nothing — no bundle, no lock. Because nothing is written, it
|
|
132
|
+
reads no `xano/.env` or `xano/.secrets.json`, so the workflow needs no repository
|
|
133
|
+
secrets, even for an API group whose docs are token-gated. It fails instead of
|
|
134
|
+
rewriting when `xano.lock` disagrees with the source, naming what would change. A
|
|
135
|
+
project with nothing to record yet (a fresh scaffold) passes without a lock. It also fails while the lock
|
|
136
|
+
carries an entry no exported object matches — the state a rename leaves until
|
|
137
|
+
`lock rename` moves the identity across, or `lock prune` drops it. It first runs
|
|
138
|
+
`xanosdk routes ./xano/index.ts --emit xano/routes.gen.ts --strict`, which fails when
|
|
139
|
+
the committed route manifest the frontend takes its paths from is stale or missing.
|
|
140
|
+
`dev`, `build` and `typecheck` regenerate that file, so after adding or renaming an
|
|
141
|
+
endpoint run `npm run xano:routes` (or any of them) and commit it. `init --from` and
|
|
142
|
+
`pull` write it with the decoded backend, and `init --marketplace` writes it (and
|
|
143
|
+
`xano/xano.lock`) for an add-on that brings endpoints, so each of those passes the check
|
|
144
|
+
as it stands. It installs with
|
|
145
|
+
`npm ci` whenever a lockfile is committed, since `package.json` allows a
|
|
146
|
+
`@xano/sdk` range that the lock pins and an open install would rebuild derived
|
|
147
|
+
state with a newer patch, failing the check on a change that touched nothing.
|
|
148
|
+
|
|
149
|
+
**Regenerate after any merge.** A clean merge is not evidence that derived state is
|
|
150
|
+
correct: two branches that change different objects merge with no conflict while one
|
|
151
|
+
side's derived files were written before the other's source change existed. Nothing
|
|
152
|
+
in git can see it, because at the text level the two sides never touched the same
|
|
153
|
+
file — the check is what catches it. Pair the workflow with "Require branches to be
|
|
154
|
+
up to date before merging" so a pull request approved against an older base is
|
|
155
|
+
re-checked before it lands.
|
|
156
|
+
|
|
157
|
+
## Toolchain modules
|
|
158
|
+
|
|
159
|
+
A **toolchain module** extends the CLI rather than the workspace. It adds no tables
|
|
160
|
+
or endpoints and is never registered in `xano/index.ts`; instead it contributes
|
|
161
|
+
questions, lines for the files above, and hooks that run on `export` and `deploy` —
|
|
162
|
+
after the compile, before anything is uploaded. Its per-project settings live in this
|
|
163
|
+
project's `package.json` under `"xanosdk"`, keyed by package name, so a choice made
|
|
164
|
+
once survives to every later run.
|
|
165
|
+
|
|
166
|
+
Those questions are asked wherever the project is reconciled — at `init`, and in a
|
|
167
|
+
project that already exists. **`xanosdk marketplace install <package>` is not just
|
|
168
|
+
`npm install`:** it runs the module's questionnaire and writes both its settings and
|
|
169
|
+
the `.gitattributes` lines it contributes. A module that arrived any other way — a
|
|
170
|
+
plain `npm install`, a merged pull request, a `git pull` that changed
|
|
171
|
+
`package.json` — is configured by running `xanosdk marketplace install` on it; until
|
|
172
|
+
then `export` and `deploy` report it as installed but never configured, and it runs
|
|
173
|
+
on its own defaults.
|
|
174
|
+
|
|
175
|
+
**`xanosdk marketplace reinstall <package>`** re-asks the questions, offering this
|
|
176
|
+
project's current settings as the defaults, and switches a module back on if it was
|
|
177
|
+
turned off. **`xanosdk marketplace remove <package>`** uninstalls it and drops its
|
|
178
|
+
settings and its contributed lines together. A plain `npm uninstall` leaves the
|
|
179
|
+
`"xanosdk"` block behind, and a package that block names but that is no longer
|
|
180
|
+
installed fails every `--frozen-lock` run until you run `reinstall` or `remove`.
|
|
181
|
+
|
|
182
|
+
A module that owns a generated directory also contributes the `.gitattributes`
|
|
183
|
+
lines that make it render and diff like source, and its own check rides on the
|
|
184
|
+
same `--frozen-lock` run, so a stale tree fails CI beside a stale lock.
|
|
185
|
+
|
|
186
|
+
Never hand-edit a file a module generates, and never hand-resolve a conflict in one.
|
|
187
|
+
The correct content is whatever the source renders to, so the only valid resolution
|
|
188
|
+
is to re-run the export.
|
|
189
|
+
|
|
190
|
+
Writing one is [The module contract](module-contract.md): the manifest fields, the
|
|
191
|
+
`@xano/sdk/plugin` types, every hook, and the shapes the SDK refuses.
|
|
192
|
+
|
|
193
|
+
## Choosing in a browser
|
|
194
|
+
|
|
195
|
+
`--web` collects the same choices against a live preview instead of on the
|
|
196
|
+
command line:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
xanosdk init my-app --web
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
It is a launcher, not a second scaffolder: a configurator is downloaded on
|
|
203
|
+
demand, serves a local page, and finishes by running `init` with the flags your
|
|
204
|
+
choices imply — printing the equivalent command so the project stays
|
|
205
|
+
reproducible from a script. Everything after `--web` is passed to it untouched,
|
|
206
|
+
including `--help`, which is why that one form reaches the network. `xanosdk help
|
|
207
|
+
init` stays offline, like `init` itself.
|
|
208
|
+
|
|
209
|
+
Because the configurator is fetched at run time, `--web` needs the npm registry
|
|
210
|
+
before it can start. `init` on its own reaches out only to install the new
|
|
211
|
+
project's dependencies, which `--no-install` skips — `--marketplace` add-ons
|
|
212
|
+
included: they are added to `package.json` without being installed, and wired
|
|
213
|
+
into `xano/index.ts` by hand after `npm install`.
|
|
214
|
+
|
|
215
|
+
## The frontend preset
|
|
216
|
+
|
|
217
|
+
To point `npm run dev` at a real backend, copy `.env.example` to `.env.local` — both
|
|
218
|
+
live at the **project root**, next to `vite.config.ts` — and set `VITE_XANO_HOST` to a
|
|
219
|
+
deployed URL. Deployed builds don't need it: `xanosdk deploy <entry> --static <dir>` injects the
|
|
220
|
+
backend URL as `window.XANO_HOST`, which takes precedence.
|
|
221
|
+
|
|
222
|
+
The frontend ships `Button` and `Card` already vendored, plus a pre-configured
|
|
223
|
+
`components.json`, so `npx shadcn@latest add dialog form input` (or
|
|
224
|
+
`npx shadcn-svelte@latest add …` on a Svelte scaffold) works immediately — no
|
|
225
|
+
`init` step for either CLI. Components are copied into your repo rather than
|
|
226
|
+
installed, so you own and edit them directly. [Lucide](https://lucide.dev/icons)
|
|
227
|
+
is installed on both scaffolds — `lucide-react` on React, `@lucide/svelte` on
|
|
228
|
+
Svelte — and the landing page already uses it.
|
|
229
|
+
|
|
230
|
+
## Theming
|
|
231
|
+
|
|
232
|
+
shadcn components carry no colors of their own: they are Tailwind utilities over
|
|
233
|
+
a fixed set of semantic tokens (`--primary`, `--muted-foreground`, `--border`,
|
|
234
|
+
the `--chart-*` ramp, the `--sidebar-*` set). Those tokens live at the top of
|
|
235
|
+
`frontend/src/index.css`, which is the whole theme — Tailwind v4 keeps it in CSS,
|
|
236
|
+
and there is no `tailwind.config.js`. That one stylesheet backs both frameworks.
|
|
237
|
+
|
|
238
|
+
`init` renders it from a theme you choose, using shadcn's own two-part model:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
xanosdk init my-app --theme zinc-blue # a base color, plus an accent over it
|
|
242
|
+
xanosdk init my-app --theme stone # a base color alone
|
|
243
|
+
xanosdk init my-app --theme zinc --radius 0
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Base colors — the full token set: `neutral` (default), `stone`, `zinc`, `mauve`,
|
|
247
|
+
`olive`, `mist`, `taupe`. Accents — a partial override of `primary`, `secondary`,
|
|
248
|
+
the chart ramp, and the sidebar primary: `amber`, `blue`, `cyan`, `emerald`,
|
|
249
|
+
`fuchsia`, `green`, `indigo`, `lime`, `orange`, `pink`, `purple`, `red`, `rose`,
|
|
250
|
+
`sky`, `teal`, `violet`, `yellow`. The values are shadcn's, verbatim, so
|
|
251
|
+
`--theme zinc-blue` is what ui.shadcn.com hands out for the same pair.
|
|
252
|
+
|
|
253
|
+
`--theme` also takes any shadcn **registry theme** — its own, a third-party
|
|
254
|
+
generator's, or your team's:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
xanosdk init my-app --theme https://ui.shadcn.com/r/themes/slate.json
|
|
258
|
+
xanosdk init my-app --theme ./brand-theme.json
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`--radius <len>` overrides the corner radius (a bare number is rem). Everything
|
|
262
|
+
else about the project is identical whichever theme you pick, and you can change
|
|
263
|
+
your mind later by editing the token values — or by applying another theme over
|
|
264
|
+
them with `npx shadcn@latest add <registry-theme-url>`.
|
|
265
|
+
|
|
266
|
+
### Dark mode
|
|
267
|
+
|
|
268
|
+
Every theme ships a complete dark palette. `--dark` decides what turns it on:
|
|
269
|
+
|
|
270
|
+
- `system` (default) — an inline script in the HTML entry applies the OS setting
|
|
271
|
+
before first paint, so the page never flashes light first. No UI.
|
|
272
|
+
- `toggle` — that, plus `frontend/src/lib/theme.ts` (the persisted mode) and a
|
|
273
|
+
mode toggle on the landing page cycling system → light → dark.
|
|
274
|
+
- `off` — light only. The `.dark` block is still there and still complete.
|
|
275
|
+
|
|
276
|
+
Whichever you pick, style with the token classes (`bg-primary`,
|
|
277
|
+
`text-muted-foreground`) rather than raw palette classes like `bg-gray-100`:
|
|
278
|
+
raw ones ignore the theme and are unreadable in dark mode. The scaffolded AI
|
|
279
|
+
instruction files say so too.
|
|
280
|
+
|
|
281
|
+
### Typefaces and icons
|
|
282
|
+
|
|
283
|
+
Fonts are opt-in and **self-hosted**: each choice installs an `@fontsource`
|
|
284
|
+
package rather than linking Google's CDN, which would be a third-party request
|
|
285
|
+
on every page load of your app, a failure behind a firewall, and a privacy
|
|
286
|
+
question someone inherits later.
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
xanosdk init my-app --font geist --font-heading instrument-serif --icons tabler
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
- `--font <id>` — body text (Tailwind's `--font-sans`, which v4 also uses as the
|
|
293
|
+
page default). Sans faces only: `geist`, `inter`, `figtree`, `manrope`,
|
|
294
|
+
`dm-sans`, `space-grotesk`, `outfit`, and 10 more.
|
|
295
|
+
- `--font-mono <id>` — code. `jetbrains-mono` or `geist-mono`.
|
|
296
|
+
- `--font-heading <id>` — headings. Accepts **any** face, sans or serif, since a
|
|
297
|
+
display serif over a sans body is the reason the slot exists. It emits a
|
|
298
|
+
base-layer rule, so headings pick it up without touching every `<h1>`.
|
|
299
|
+
- Omit a slot and it keeps Tailwind's default stack, installing nothing for it.
|
|
300
|
+
|
|
301
|
+
An unknown id fails at `init` naming the slot it was resolving, so `--font-mono
|
|
302
|
+
inter` is an error rather than a proportional face quietly rendering your code
|
|
303
|
+
blocks.
|
|
304
|
+
|
|
305
|
+
`--icons <id>` picks the icon set: `lucide` (default), `tabler`, or `phosphor`.
|
|
306
|
+
The binding covers the dark-mode toggle's icons as well as the landing page's —
|
|
307
|
+
without that, `--icons tabler --dark toggle` would emit a toggle importing a
|
|
308
|
+
library the project no longer installs, a build failure from a flag with nothing
|
|
309
|
+
to do with dark mode.
|
|
310
|
+
|
|
311
|
+
> On a Svelte scaffold `npm run typecheck` runs `svelte-kit sync && svelte-check`
|
|
312
|
+
> (after `xano:routes`) rather than `tsc`. It checks the backend and the components together — `tsc`
|
|
313
|
+
> cannot read `.svelte` files at all.
|
|
314
|
+
|
|
315
|
+
> **The SvelteKit scaffold prerenders every route.** `frontend/src/routes/+layout.ts`
|
|
316
|
+
> sets `prerender = true`, so each route becomes its own HTML document at build
|
|
317
|
+
> time and loads as a real page. Pages live in `frontend/src/routes/`, and `files`
|
|
318
|
+
> in the `sveltekit()` plugin config keeps the project single-rooted with `xano/`
|
|
319
|
+
> as a peer. That config lives in `vite.config.ts` — there is no
|
|
320
|
+
> `svelte.config.js`, matching where SvelteKit's own scaffold now puts it.
|
|
321
|
+
>
|
|
322
|
+
> There is still no server at runtime — Xano is the backend and `deploy --static`
|
|
323
|
+
> ships to a host with no runtime, so `+page.server.ts`, form actions, and server
|
|
324
|
+
> `load` have nothing to run on, and the build does not stop you. Treat them as
|
|
325
|
+
> unavailable rather than trusting a green build.
|
|
326
|
+
>
|
|
327
|
+
> Two things follow from prerendering. Because it renders at build time,
|
|
328
|
+
> module-scope `window`/`document` access fails the **build** rather than the
|
|
329
|
+
> browser — use `onMount`, or guard with `browser`. And a dynamic route like
|
|
330
|
+
> `/posts/[id]` **fails the build** until it says how it renders. For rows
|
|
331
|
+
> created at runtime (the usual detail page), render it in the browser:
|
|
332
|
+
>
|
|
333
|
+
> ```ts
|
|
334
|
+
> // frontend/src/routes/posts/[id]/+page.ts
|
|
335
|
+
> export const prerender = false;
|
|
336
|
+
> ```
|
|
337
|
+
>
|
|
338
|
+
> The scaffold builds with the SDK's adapter (`@xano/sdk/sveltekit`), which then
|
|
339
|
+
> writes `404.html` as the app shell: the host answers `/posts/42` with it, and it
|
|
340
|
+
> boots the app and renders the route, so a reload or a deep link works (with a
|
|
341
|
+
> 404 status). `export const entries = () => [{ id: "1" }]` fits only ids known at
|
|
342
|
+
> build time — no other id gets a page. While every route is prerendered,
|
|
343
|
+
> unmatched paths get a real 404 from `frontend/src/routes/404/+page.svelte`,
|
|
344
|
+
> which prerenders to `404.html`. It has to be a route: SvelteKit never
|
|
345
|
+
> prerenders `+error.svelte` to a file.
|
|
346
|
+
>
|
|
347
|
+
> One more build-time check comes with prerendering: a hash link to an id that is
|
|
348
|
+
> not on the page it renders on — `<a href="#pricing">` with no `id="pricing"` —
|
|
349
|
+
> **fails the build**, naming the route and the id. A hash nav in
|
|
350
|
+
> `+layout.svelte` is exempt on `/404` only, since that route inherits the layout
|
|
351
|
+
> and by definition carries none of the page's sections.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# The typed frontend surface
|
|
2
|
+
|
|
3
|
+
Every type and helper a frontend gets from a deployed def — path resolution, input and response inference, and the two ways to pay for it.
|
|
4
|
+
|
|
5
|
+
## Endpoint names, path params, and encoding
|
|
6
|
+
|
|
7
|
+
- **URL path params** → name the endpoint with `{param}` segments and declare an input per
|
|
8
|
+
segment. `getPath({ params })` fills them, with the keys typed from the name itself:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const getPost = query({
|
|
12
|
+
name: "blog/{slug}/review/{review_id}", // segments chain; no wildcards
|
|
13
|
+
verb: "GET",
|
|
14
|
+
apiGroup: api,
|
|
15
|
+
input: { slug: input.text(), review_id: input.int(), verbose: input.bool() },
|
|
16
|
+
stack: [s.db.get({ table: post, fieldName: "slug", fieldValue: inp("slug"), as: "row" })],
|
|
17
|
+
response: ref("row"),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
getPost.getPath({ params: { slug: "hello", review_id: 7 } });
|
|
21
|
+
// → "/api:<canonical>/blog/hello/review/7"
|
|
22
|
+
getPost.toSearchParams({ verbose: true }); // → URLSearchParams; String(...) is "verbose=true" (path params dropped)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Every `{param}` must have a matching input or `query()` throws — Xano treats an unbound
|
|
26
|
+
marker as inert route text, so the endpoint would answer on the path and see nothing.
|
|
27
|
+
Inputs that aren't in the path (`verbose`) stay ordinary query-string params. Never
|
|
28
|
+
interpolate the path by hand: `getPath` percent-encodes each value, so a `?`, `#` or space
|
|
29
|
+
stays inside its segment instead of restructuring the URL, and it throws on the two values
|
|
30
|
+
encoding can't contain — one holding `/`, and one that *is* `.` or `..` (a URL parser drops
|
|
31
|
+
those before routing, addressing a different endpoint). `realtimeChannel()` paths work
|
|
32
|
+
identically, minus the encoding: a channel address is matched literally, not parsed as a URL.
|
|
33
|
+
- **Sibling routes must not overlap.** Xano serves the first route that matches, in creation
|
|
34
|
+
order, and a literal route gets no priority over a `{param}` one. So `"runs/{id}"` created
|
|
35
|
+
before `"runs/trend"` answers `GET /runs/trend` itself. `export()` refuses any two routes in
|
|
36
|
+
one group and verb that a single path can reach (`query.route-shadowed`). Make the paths
|
|
37
|
+
disjoint (`"runs/by-id/{id}"`), or declare a numeric param `input.int({ required: true })`:
|
|
38
|
+
only a REQUIRED int/decimal segment matches digits only, and an optional one matches any text.
|
|
39
|
+
- **`InferRow<typeof post>`** → the table's row type. Rename or retype a column and every
|
|
40
|
+
consumer breaks at compile time — exactly where you want it.
|
|
41
|
+
- **`InferResponse<typeof someQuery>`** → the endpoint's **response** type, closing the round
|
|
42
|
+
trip. It auto-derives the common shapes with no codegen: an object-literal response yields
|
|
43
|
+
those keys, and a query that returns a variable filled by a db op derives that op's result —
|
|
44
|
+
the full row for `db.add`/`db.edit`/`db.patch`/`db.add_or_edit` (→ `Row` — each binds the
|
|
45
|
+
full written row rather than null, so it stays non-nullable; a genuine miss throws instead of
|
|
46
|
+
yielding null — `NotFound`/404 for `edit`/`patch`, a unique-constraint error for `add`, while
|
|
47
|
+
`add_or_edit` upserts and never misses), `Row | null` for `db.get` (it binds
|
|
48
|
+
`null` on a miss rather than throwing — handle the not-found path), a row list for
|
|
49
|
+
|
|
50
|
+
## Inferring the response type
|
|
51
|
+
|
|
52
|
+
`db.query`/`db.bulk.patch` (→ `Row[]`), a `boolean` for `db.has`, a `number` count for
|
|
53
|
+
`db.bulk.delete`, and a `get`/`query` `output: [...]` selection narrows to a `Pick` (still
|
|
54
|
+
`| null` for `get`). A dotted entry selects sub-keys of an object column
|
|
55
|
+
(`output: ["id", "meta.url"]`, on a statement or an addon); the narrowing keys off the
|
|
56
|
+
path's root, since an object column's sub-keys aren't declared in the schema.
|
|
57
|
+
Where the shape isn't statically knowable — a value reshaped by a filter/lambda, built by
|
|
58
|
+
control flow, or from an op the engine itself leaves untyped (`db.del`, `db.bulk.add`/`bulk.update`,
|
|
59
|
+
raw `direct_query`) — it resolves to `unknown`; declare `responseShape` to close it.
|
|
60
|
+
In an object-literal response the **keys** are always known, and a `c.*` constant **value**
|
|
61
|
+
types like an `s.set_var` of it: `response: { success: c.bool(true) }` derives
|
|
62
|
+
`{ success: boolean }`.
|
|
63
|
+
A **nested** member resolves by those same rules, to any depth, in either spelling —
|
|
64
|
+
`response: { user: obj({ id: ref("row.id") }) }` and the raw literal
|
|
65
|
+
`response: { user: { id: ref("row.id") } }` both derive `{ user: { id: number | null } }`.
|
|
66
|
+
A **call** carries the shape across: `s.function.call`/`s.function.run`, `s.tool.call` and
|
|
67
|
+
`s.api.call` (workflow-test only, above) given a def **handle** bind their `as` with the
|
|
68
|
+
target's own `InferResponse`,
|
|
69
|
+
so `ref("discount_result.discount_cents")` in the caller types to that field instead of
|
|
70
|
+
`unknown`. What the target resolves to is what propagates, so declaring `responseShape` on
|
|
71
|
+
the **target** fixes every caller at once. A target named by *string* has no def to read and
|
|
72
|
+
stays `unknown`, as does an async `s.function.run` (it binds a job handle, not the result).
|
|
73
|
+
The same derivation runs on every response-bearing kind — `query()`, `defineFunction()`,
|
|
74
|
+
`realtimeMessage()`, `tool()`, `middleware()`, and the response-bearing triggers — and each
|
|
75
|
+
of them takes `responseShape`. A trigger builds its stack and response through *callbacks*
|
|
76
|
+
(`stack: (t) => [...]`), and the trace follows through them. This matters most where a
|
|
77
|
+
handler's response is the only type a client has: a realtime message's broadcast payload,
|
|
78
|
+
or a channel `deliver` trigger's return, which is that recipient's copy of the message.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { listPosts, getPost } from "../xano/index.js";
|
|
82
|
+
import type { InferResponse } from "@xano/sdk";
|
|
83
|
+
|
|
84
|
+
type Posts = InferResponse<typeof listPosts>; // Post[] — derived from the db.query it returns
|
|
85
|
+
type Post = InferResponse<typeof getPost>; // Post | null — a db.get misses to null
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For a **computed or multi-key object response**, author it as a *record of values* —
|
|
89
|
+
`response: { success: c.bool(true), id: inp("id") }` — **not** `c.obj({ ... })`. `c.obj` builds a
|
|
90
|
+
*constant*, so a tagged value nested inside it would serialize as internal representation the
|
|
91
|
+
engine can't decode (a runtime 500); nesting one is now a compile error that points you at the
|
|
92
|
+
record form (issue #42). A **nested plain object** in a record
|
|
93
|
+
response (`response: { user: { id: ref("u"), age: 3 } }`) is auto-wrapped for you — no manual
|
|
94
|
+
`obj({ ... })` — and raw literals in a call/agent `input` map coerce too
|
|
95
|
+
(`s.function.run({ fn, input: { max_age_days: 3 } })` — no `c.int(3)`).
|
|
96
|
+
|
|
97
|
+
When a response is filtered, computed, or otherwise opaque to the static walk, declare it once
|
|
98
|
+
on the query and every caller derives from that single source of truth:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const getPost = query({
|
|
102
|
+
verb: "GET", apiGroup: blog, name: "get_post/{id}", // one row → address it in the path
|
|
103
|
+
input: { id: input.int({ required: true }) },
|
|
104
|
+
stack: [s.db.query({ table: post, where: expr(col("id"), "=", inp("id")), as: "rows" })],
|
|
105
|
+
// A filtered response is opaque to the static walk, so derivation is `unknown`.
|
|
106
|
+
response: withFilters(ref("rows"), fl.first()),
|
|
107
|
+
responseShape: null as InferRow<typeof post> | null, // declare the real shape once
|
|
108
|
+
});
|
|
109
|
+
type MaybePost = InferResponse<typeof getPost>; // InferRow<typeof post> | null
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
(A plain `response: ref("row")` off a `s.db.get` needs no `responseShape` — it already
|
|
113
|
+
derives `InferRow<typeof post> | null`, since `db.get` misses to `null`.)
|
|
114
|
+
|
|
115
|
+
**Factoring statements into a helper.** The trace walks the stack's *tuple*, so spreading a
|
|
116
|
+
helper typed `Statement[]` widens the stack and nothing in it resolves any more — the response
|
|
117
|
+
types as `StackTupleWidened`, which names the cause. Return `statements(...)` from the helper
|
|
118
|
+
and the tuple survives the spread:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { statements } from "@xano/sdk";
|
|
122
|
+
|
|
123
|
+
function assertOk(v: string) { // no `: Statement[]` annotation
|
|
124
|
+
return statements(s.lambda({ ... }), s.precondition({ ... }));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
stack: [...assertOk("res"), s.db.add({ table: post, row, as: "created" })],
|
|
128
|
+
response: ref("created"), // still traced
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Fixed arity only — a helper that builds its array in a loop can't be a tuple, so declare
|
|
132
|
+
`responseShape` there.
|
|
133
|
+
|
|
134
|
+
This mirrors how the Xano engine itself derives an endpoint's response schema (a static walk of
|
|
135
|
+
the stack), so what you get in the type is what the endpoint actually returns — and it degrades
|
|
136
|
+
to `unknown` in exactly the cases the engine can't resolve either.
|
|
137
|
+
|
|
138
|
+
A GET endpoint carries its inputs in the query string rather than a JSON body:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { getSnippet } from "../xano/index.js";
|
|
142
|
+
import { query, type InferInput } from "@xano/sdk";
|
|
143
|
+
|
|
144
|
+
const BASE = "https://your-instance.xano.io";
|
|
145
|
+
|
|
146
|
+
async function fetchSnippet(id: number) {
|
|
147
|
+
const params = { id } satisfies InferInput<typeof getSnippet>; // { id: number }
|
|
148
|
+
const res = await fetch(`${BASE}${getSnippet.getPath()}?${query.toSearchParams(params)}`);
|
|
149
|
+
return res.json();
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`toSearchParams` sends a list as `tags[]=a&tags[]=b` and an object as `range[min]=1`
|
|
154
|
+
(a list of objects indexes each item: `items[0][id]=1`; a list of lists goes as one
|
|
155
|
+
JSON value, `lol=[[1,2],[3]]`). A list item that sends nothing (`null`, `{}`) throws,
|
|
156
|
+
naming it. A repeated plain key (`tags=a&tags=b`) reaches the endpoint as its last
|
|
157
|
+
value only, so don't hand-roll it.
|
|
158
|
+
|
|
159
|
+
The `@xano/sdk` entry has **zero Node dependencies**, so importing your workspace
|
|
160
|
+
graph into a browser bundle just works. The `node:fs`-backed emitters live in the
|
|
161
|
+
separate `@xano/sdk/node` entry a frontend never pulls in.
|
|
162
|
+
|
|
163
|
+
## Bundle size, and the route manifest
|
|
164
|
+
|
|
165
|
+
**Bundle size & tree-shaking.** `@xano/sdk` is `sideEffects: false`, so a bundler drops
|
|
166
|
+
the SDK exports your frontend doesn't use. But importing a query **def** for its `getPath()`
|
|
167
|
+
also pulls whatever its `stack` builds — the `s.*`/`c.*` factory *calls* run at module load
|
|
168
|
+
to construct the def, so they can't be tree-shaken out. Types are free (`InferInput`/
|
|
169
|
+
`InferRow` erase to nothing — use `import type`). That cost is a **floor**, not a function of
|
|
170
|
+
how lean the def is. Measured on a Vite lib build against the published package: one
|
|
171
|
+
`apiGroup` + one empty `query`, imported for a single `getPath()`, is **267 kB minified
|
|
172
|
+
(65 kB gzipped)** against 56 B for a hand-written path string. A realistic def — two
|
|
173
|
+
tables, a foreign key, a typed input, a `db.query` with a `where` and a sort, plus a
|
|
174
|
+
second endpoint — measures 269 kB. That 2 kB spread is the point: the floor is the SDK
|
|
175
|
+
runtime itself, so splitting modules or simplifying a def does not move it, and the cost
|
|
176
|
+
is paid by importing any def at all.
|
|
177
|
+
|
|
178
|
+
**Generate a route manifest instead.** It keeps the derived-not-hardcoded contract at
|
|
179
|
+
almost no bundle cost:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
xanosdk routes ./xano/index.ts --emit xano/routes.gen.ts
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The emitted file is plain data plus one interpolator and imports nothing at all — the same
|
|
186
|
+
app builds to 1.3 kB, a ~200x saving, with no SDK code in the output. Route keys and their
|
|
187
|
+
`{param}` names are still checked at compile time, so a backend rename is a compile error
|
|
188
|
+
rather than a 404:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { routePath, ROUTES } from "../xano/routes.gen";
|
|
192
|
+
|
|
193
|
+
fetch(BASE + routePath("GET blog/{slug}", { slug }), {
|
|
194
|
+
method: ROUTES["GET blog/{slug}"].verb,
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
A route key is `"<VERB> <name>"`, which is the endpoint identity the engine itself uses —
|
|
199
|
+
so verb-differentiated siblings are ordinary, and a REST-shaped group emits without
|
|
200
|
+
renaming anything:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
routePath("GET listings"); // list
|
|
204
|
+
routePath("POST listings"); // create
|
|
205
|
+
routePath("PATCH listings/{id}", { id }); // update
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The verb comes first so the key is stable: adding a `POST` sibling never renames the `GET`
|
|
209
|
+
that was already there. When two api groups hold the same verb AND name (a `v1` and a `v2`
|
|
210
|
+
both serving `GET vehicles`), each of those endpoints is keyed by its group's name instead,
|
|
211
|
+
`"<group>:<VERB> <name>"`, and every other key stays short:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
routePath("v1:GET vehicles");
|
|
215
|
+
routePath("v2:GET vehicles");
|
|
216
|
+
routePath("GET vehicles"); // compile error: "... is in more than one api group - use v1:GET vehicles or v2:GET vehicles"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Adding the second group's endpoint is what renames the first one's key, so its call sites
|
|
220
|
+
stop compiling rather than silently addressing one group of the two.
|
|
221
|
+
|
|
222
|
+
Realtime is in the same file when the workspace has any: `socketUrl(server, baseUrl)` for the
|
|
223
|
+
websocket URL and `channelPath(channel, params)` for the path a frame's `channel` field takes,
|
|
224
|
+
both keyed and `{param}`-checked exactly like the routes. A channel path two servers both own
|
|
225
|
+
is keyed by server, `"<server>:<path>"` (`channelPath("staff:lobby")`), and the bare path is a
|
|
226
|
+
compile error naming both keys, as with api groups. `socketUrl` is the equivalent of
|
|
227
|
+
`realtimeServer().getUrl()` down to the tenant rule — a base URL that names a tenant
|
|
228
|
+
(`https://host/tenant/ab-cd`, what deploy injects as `window.XANO_HOST`) is rewritten to the
|
|
229
|
+
socket's own `wss://host/ws/ab-cd:<canonical>` form, which is the one address a frontend has no
|
|
230
|
+
way to reconstruct. Resolve once, from the `https://` base — feeding a resolved socket URL back
|
|
231
|
+
in as a `baseUrl` throws rather than append a second `/ws/…`:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { socketUrl, channelPath } from "../xano/routes.gen";
|
|
235
|
+
import { XANO_HOST } from "./lib/api"; // a string: the injected URL, else VITE_XANO_HOST
|
|
236
|
+
|
|
237
|
+
const ws = new WebSocket(socketUrl("chat", XANO_HOST), token);
|
|
238
|
+
ws.send(JSON.stringify({ action: "join", channel: channelPath("rooms/{room_id}", { room_id }) }));
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Add `--strict` in CI to fail when the committed manifest is out of date. A hand-typed
|
|
242
|
+
`ROUTES` table is the option that gives up both the bundle saving and the rename safety.
|
package/llms/client.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Consuming defs from a client
|
|
2
|
+
|
|
3
|
+
> 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.
|
|
4
|
+
|
|
5
|
+
Importing a def into a browser bundle or a Node script — for `getPath()`, `getUrl()`, `verb`, or `InferResponse` — runs its factory calls; these are the costs and the checks.
|
|
6
|
+
|
|
7
|
+
- **Client bundle size / tree-shaking.** `@xano/sdk` is `sideEffects: false` and pulls
|
|
8
|
+
no Node built-ins, so a bundler drops unused SDK exports. But importing a **def** for its
|
|
9
|
+
`getPath()`/`verb`/`getUrl()`/`getChannel()` also pulls whatever its `stack` references:
|
|
10
|
+
the `s.*`/`c.*` factory CALLS run at module load to BUILD it. Types are free.
|
|
11
|
+
⚠ A FLOOR — **~267 kB minified (~65 kB gzipped)** for the FIRST def; splitting modules
|
|
12
|
+
never removes it. The floor is the RUNTIME, not the def: a second or much richer def
|
|
13
|
+
adds ~2 kB, so trimming a def does not shrink it.
|
|
14
|
+
Fix: `xanosdk routes <entry> --emit xano/routes.gen.ts` (`paths` is an accepted alias) — verbs, paths, and sockets as
|
|
15
|
+
plain data importing NOTHING, still compile-checked: `routePath("GET blog/{slug}", { slug })`
|
|
16
|
+
`channelPath("rooms/{room_id}", { room_id })`, `socketUrl("chat", baseUrl)` (tenant base
|
|
17
|
+
URLs lifted to `wss://h/ws/<tenant>:<canonical>`). A rename is a type error, not a 404.
|
|
18
|
+
- **Verifying a def outside a bundler.** Inside a bundler (Vite/webpack) importing a
|
|
19
|
+
query def to read `getPath()`/`verb` works directly. To spot-check from Node, run a REAL
|
|
20
|
+
file with `tsx <file.ts>` **from inside the project root** — not `tsx -e "import …"`
|
|
21
|
+
(its CJS-preparse mis-resolves the package `exports` map → ERR_PACKAGE_PATH_NOT_EXPORTED),
|
|
22
|
+
and not bare `node file.ts` (chokes on the `.js`-specifier intra-workspace imports the
|
|
23
|
+
xanosdk CLI's own loader resolves). Running from outside the project root also breaks
|
|
24
|
+
the `@xano/sdk` specifier resolution.
|
|
25
|
+
|
|
26
|
+
**Reading the injected backend URL.** `xanosdk deploy <entry> --static <dir>` writes the deployed env's URL
|
|
27
|
+
into every html document: a prerendered build serves a different one per
|
|
28
|
+
route, and a route without the global renders fine while every call goes to the wrong
|
|
29
|
+
origin. Read it at runtime with a build-time fallback:
|
|
30
|
+
const HOST = (typeof window !== 'undefined' && window.XANO_HOST) || import.meta.env.VITE_XANO_HOST;
|
|
31
|
+
The scaffold's `lib/api.ts` types it (`interface Window { XANO_HOST?: string }` — `undefined` in dev)
|
|
32
|
+
and exports this as a `string`, `XANO_HOST`; outside a scaffold declare the global yourself.
|
|
33
|
+
In LOCAL DEV there is no injected global, so the fallback is what answers: set
|
|
34
|
+
`VITE_XANO_HOST` in a `.env.local` beside `.env.example` at the PROJECT ROOT. The
|
|
35
|
+
scaffold's vite config sets `envDir` there (its `root` is `frontend/`, and Vite
|
|
36
|
+
resolves `.env` files against `root`) — without it the var reads as undefined, the
|
|
37
|
+
host falls back to '', and every call 404s off the dev server.
|
|
38
|
+
⚠ It is INJECTED in bracket form — `window["XANO_HOST"]="…"` — so verifying a deploy
|
|
39
|
+
by grepping `window.XANO_HOST` matches nothing and reads as a failed inject. Grep the
|
|
40
|
+
bare `XANO_HOST` token.
|
|
41
|
+
**Frontend without the backend.** `xanosdk publish <dir> [--to <backend>]` uploads an
|
|
42
|
+
already-built directory and nothing else — no compile, no import (scaffold script
|
|
43
|
+
`npm run xano:deploy:frontend`). `publish` does not reach a local engine: after a local-engine
|
|
44
|
+
deploy, pass `--to ephemeral`, or give the engine the frontend with
|
|
45
|
+
`xanosdk deploy <entry> --local-engine --static <dir>` (served at `http://<prefix>.localhost:<port>`).
|
|
46
|
+
**Server-rendered SvelteKit.** `adapter()` from `@xano/sdk/sveltekit` (needs `esbuild`) writes
|
|
47
|
+
the static half at the build's root plus a server half in `.xano-ssr/`. A local engine renders every
|
|
48
|
+
path that is not a file through it (`load` reads the engine's own URL as `XANO_HOST` from
|
|
49
|
+
`$env/dynamic/private`; a rendered page gets the deploy's `window.XANO_HOST` too). Any other host
|
|
50
|
+
is not sent `.xano-ssr/`: it serves the static half and answers other paths with the `404.html`
|
|
51
|
+
shell, where a route with a server `load` fails (no `__data.json`). A scaffolded project configures
|
|
52
|
+
SvelteKit in `vite.config.ts`'s `sveltekit({ adapter })`, where `svelte.config.js` is ignored.
|
|
53
|
+
⚠ `.xano-ssr/server.js` inlines `$env/static/private` values; it is never served (`/.xano-ssr/` answers 404).
|
|
54
|
+
The injected `XANO_HOST` is the DESTINATION's URL, so one build published to two
|
|
55
|
+
destinations serves two different documents. `--release <name>` checks only that the
|
|
56
|
+
release exists; `--branch <label>` (workspace only) refuses unless that label is LIVE —
|
|
57
|
+
a label match, not a content comparison. `--json` reports `published` and `verified` separately.
|
|
58
|
+
**Displaying a stored file.** A file column comes back as `{ path, name, type, size,
|
|
59
|
+
meta, access, url }`. ⚠ Do NOT use its `url`: on a tenant-scoped environment that field
|
|
60
|
+
addresses the instance host WITHOUT the `/tenant/<name>` segment and 404s, silently —
|
|
61
|
+
as a broken `<img>`, with every API assertion still passing. Build the URL from `path`
|
|
62
|
+
instead: `fileUrl(row.avatar, HOST)` returns `null` for
|
|
63
|
+
an absent file and is correct on an ephemeral and an instance workspace alike.
|